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