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}