001/* 002 * CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE 003 * CONDITION THAT YOU ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT. 004 * PLEASE READ THE TERMS AND CONDITIONS OF THIS AGREEMENT CAREFULLY. BY 005 * DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF THE 006 * AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE" 007 * BUTTON AT THE BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency 008 * API ("Specification") Copyright (c) 2012-2013, Credit Suisse All rights 009 * reserved. 010 */ 011package org.javamoney.moneta.format.internal; 012 013import java.text.ParsePosition; 014import java.util.Objects; 015 016import javax.money.CurrencyUnit; 017import javax.money.MonetaryAmount; 018import javax.money.format.MonetaryAmountFormat; 019 020/** 021 * Context passed along to each {@link FormatToken} in-line, when parsing an 022 * input stream using a {@link MonetaryAmountFormat}. It allows to inspect the 023 * next tokens, the whole input String, or just the current input substring, 024 * based on the current parsing position etc. 025 * <p> 026 * This class is mutable and intended for use by a single thread. A new instance 027 * is created for each parse. 028 */ 029public final class ParseContext { 030 /** The current position of parsing. */ 031 private int index; 032 /** The error index position. */ 033 private int errorIndex = -1; 034 /** The full input. */ 035 private CharSequence originalInput; 036 /** The currency parsed, used for creation of the {@link MonetaryAmount}. */ 037 private CurrencyUnit parsedCurrency; 038 /** The numeric part of the {@link MonetaryAmount} parsed. */ 039 private Number parsedNumber; 040 /** The parse error message. */ 041 private String errorMessage; 042 043 /** 044 * Creates a new {@link ParseContext} with the given input. 045 * 046 * @param text 047 * The test to be parsed. 048 */ 049 public ParseContext(CharSequence text) { 050 if (text == null) { 051 throw new IllegalArgumentException("test is required"); 052 } 053 this.originalInput = text; 054 } 055 056 /** 057 * Method allows to determine if the item being parsed is available from the 058 * {@link ParseContext}. 059 * 060 * @return true, if the item is available. 061 */ 062 public boolean isComplete() { 063 return parsedNumber != null && parsedCurrency != null; 064 } 065 066 /** 067 * Get the parsed item. 068 * 069 * @return the item parsed. 070 */ 071 public Number getParsedNumber() { 072 return parsedNumber; 073 } 074 075 /** 076 * Consumes the given token. If the current residual text to be parsed 077 * starts with the parsing index is increased by {@code token.size()}. 078 * 079 * @param token 080 * The token expected. 081 * @return true, if the token could be consumed and the index was increased 082 * by {@code token.size()}. 083 */ 084 public boolean consume(String token) { 085 if (getInput().toString().startsWith(token)) { 086 index += token.length(); 087 return true; 088 } 089 return false; 090 } 091 092 /** 093 * Tries to consume one single character. 094 * 095 * @param c 096 * the next character being expected. 097 * @return true, if the character matched and the index could be increased 098 * by one. 099 */ 100 public boolean consume(char c) { 101 if (originalInput.charAt(index) == c) { 102 index++; 103 return true; 104 } 105 return false; 106 } 107 108 /** 109 * Skips all whitespaces until a non whitespace character is occurring. If 110 * the next character is not whitespace this method does nothing. 111 * 112 * @see Character#isWhitespace(char) 113 * 114 * @return the new parse index after skipping any whitespaces. 115 */ 116 public int skipWhitespace() { 117 for (int i = index; i < originalInput.length(); i++) { 118 if (Character.isWhitespace(originalInput.charAt(i))) { 119 index++; 120 } else { 121 break; 122 } 123 } 124 return index; 125 } 126 127 /** 128 * Gets the error index. 129 * 130 * @return the error index, negative if no error 131 */ 132 public int getErrorIndex() { 133 return errorIndex; 134 } 135 136 /** 137 * Sets the error index. 138 * 139 * @param index 140 * the error index 141 */ 142 public void setErrorIndex(int index) { 143 this.errorIndex = index; 144 } 145 146 /** 147 * Get the stored error message. 148 * @return the stored error message, or null. 149 */ 150 public String getErrorMessage(){ 151 return this.errorMessage; 152 } 153 154 /** 155 * Sets the error index from the current index. 156 */ 157 public void setError() { 158 this.errorIndex = index; 159 } 160 161 /** 162 * Gets the current parse position. 163 * 164 * @return the current parse position within the input. 165 */ 166 public int getIndex() { 167 return index; 168 } 169 170 /** 171 * Gets the residual input text starting from the current parse position. 172 * 173 * @return the residual input text 174 */ 175 public CharSequence getInput() { 176 return originalInput.subSequence(index, originalInput.length()); 177 } 178 179 /** 180 * Gets the full input text. 181 * 182 * @return the full input. 183 */ 184 public String getOriginalInput() { 185 return originalInput.toString(); 186 } 187 188 /** 189 * Resets this instance; this will reset the parsing position, the error 190 * index and also all containing results. 191 */ 192 public void reset() { 193 this.index = 0; 194 this.errorIndex = -1; 195 this.parsedNumber = null; 196 this.parsedCurrency = null; 197 } 198 199 /** 200 * Sets the parsed numeric value into the context. 201 * 202 * @param number 203 * The result number 204 */ 205 public void setParsedNumber(Number number) { 206 this.parsedNumber = number; 207 } 208 209 /** 210 * Set the parsed currency into the context. 211 * 212 * @param currency 213 * The parsed currency 214 */ 215 public void setParsedCurrency(CurrencyUnit currency) { 216 this.parsedCurrency = currency; 217 } 218 219 /** 220 * Checks if the parse has found an error. 221 * 222 * @return whether a parse error has occurred 223 */ 224 public boolean hasError() { 225 return errorIndex >= 0; 226 } 227 228 /** 229 * Checks if the text has been fully parsed such that there is no more text 230 * to parse. 231 * 232 * @return true if fully parsed 233 */ 234 public boolean isFullyParsed() { 235 return index == this.originalInput.length(); 236 } 237 238 /** 239 * This method skips all whitespaces and returns the full text, until 240 * another whitespace area or the end of the input is reached. The method 241 * will not update any index pointers. 242 * 243 * @return the next token found, or null. 244 */ 245 public String lookupNextToken() { 246 skipWhitespace(); 247 int start = index; 248 for (int end = index; end < originalInput.length(); end++) { 249 if (Character.isWhitespace(originalInput.charAt(end))) { 250 if (end > start) { 251 return originalInput.subSequence(start, end).toString(); 252 } 253 return null; 254 } 255 } 256 if (start < originalInput.length()) { 257 return originalInput.subSequence(start, originalInput.length()) 258 .toString(); 259 } 260 return null; 261 } 262 263 /** 264 * Converts the indexes to a parse position. 265 * 266 * @return the parse position, never null 267 */ 268 public ParsePosition toParsePosition() { 269 return new ParsePosition(index); 270 } 271 272 /* 273 * (non-Javadoc) 274 * 275 * @see java.lang.Object#toString() 276 */ 277 @Override 278 public String toString() { 279 return "ParseContext [index=" + index + ", errorIndex=" + errorIndex 280 + ", originalInput='" + originalInput + "', parsedNumber=" 281 + parsedNumber + "', parsedCurrency=" + parsedCurrency 282 + "]"; 283 } 284 285 public CurrencyUnit getParsedCurrency() { 286 return parsedCurrency; 287 } 288 289 public void setErrorMessage(String message){ 290 Objects.requireNonNull(message); 291 this.errorMessage = message; 292 } 293}