/*
* Copyright (c) 2012, 2013, Credit Suisse (Anatole Tresch), Werner Keil.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.javamoney.format;
import java.text.ParsePosition;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Context passed along to each {@link StyleableItemFormatToken} in-line, when parsing an
* input stream using a {@link ItemFormatBuilder}. It allows to inspect the
* next tokens, the whole input String, or just the current input substring,
* based on the current parsing position etc.
* <p>
* This class is mutable and intended for use by a single thread. A new instance
* is created for each parse.
*/
public final class ItemParseContext<T>{
/**
* The current position of parsing.
*/
private int index;
/**
* The error index position.
*/
private int errorIndex = -1;
/**
* The full input.
*/
private CharSequence originalInput;
/**
* Item factory to determine if the result was successfuylly parsed and to
* evaluate the result item.
*/
private ParseResultFactory<T> parseResultFactory;
/**
* The instances parsed and added to this {@link ItemParseContext}. This objects
* can be used by an according {@code MonetaryFunction<ItemParseContext,T>} to
* of an instance of T.
*/
private Map<Object,Object> results = new HashMap<Object,Object>();
/**
* The parse error message.
*/
private String errorMessage;
/**
* Creates a new {@link ItemParseContext} with the given input.
*
* @param text The test to be parsed.
*/
public ItemParseContext(CharSequence text, ParseResultFactory<T> parseResultFactory){
if(text == null){
throw new IllegalArgumentException("test is required");
}
if(parseResultFactory == null){
throw new IllegalArgumentException("parseResultFactory is required");
}
this.originalInput = text;
this.parseResultFactory = parseResultFactory;
}
/**
* Method allows to determine if the item being parsed is available from the
* {@link ItemParseContext}.
*
* @return true, if the item is available.
*/
public boolean isComplete(){
return parseResultFactory.isComplete(this);
}
/**
* Get the stored error message.
*
* @return the stored error message, or null.
*/
public String getErrorMessage(){
return this.errorMessage;
}
/**
* Get the parsed item.
*
* @return the item parsed.
*/
public T getItem(){
if(!isComplete()){
throw new IllegalStateException("Parsing is not yet complete.");
}
T item = this.parseResultFactory.createItemParsed(this);
if(item == null){
throw new IllegalStateException("Item is not available.");
}
return item;
}
/**
* Consumes the given token. If the current residual text to be parsed
* starts with the parsing index is increased by {@code token.size()}.
*
* @param token The token expected.
* @return true, if the token could be consumed and the index was increased
* by {@code token.size()}.
*/
public boolean consume(String token){
if(getInput().toString().startsWith(token)){
index += token.length();
return true;
}
return false;
}
/**
* Tries to consume one single character.
*
* @param c the next character being expected.
* @return true, if the character matched and the index could be increased
* by one.
*/
public boolean consume(char c){
if(originalInput.charAt(index) == c){
index++;
return true;
}
return false;
}
/**
* Skips all whitespaces until a non whitespace character is occurring. If
* the next character is not whitespace this method does nothing.
*
* @return the new parse index after skipping any whitespaces.
* @see Character#isWhitespace(char)
*/
public int skipWhitespace(){
for(int i = index; i < originalInput.length(); i++){
if(Character.isWhitespace(originalInput.charAt(i))){
index++;
}else{
break;
}
}
return index;
}
/**
* Gets the error index.
*
* @return the error index, negative if no error
*/
public int getErrorIndex(){
return errorIndex;
}
/**
* Sets the error index.
*
* @param index the error index
*/
public void setErrorIndex(int index){
this.errorIndex = index;
}
/**
* Sets the error index from the current index.
*/
public void setError(){
this.errorIndex = index;
}
/**
* Gets the current parse position.
*
* @return the current parse position within the input.
*/
public int getIndex(){
return index;
}
/**
* Gets the residual input text starting from the current parse position.
*
* @return the residual input text
*/
public CharSequence getInput(){
return originalInput.subSequence(index, originalInput.length() - 1);
}
/**
* Gets the full input text.
*
* @return the full input.
*/
public String getOriginalInput(){
return originalInput.toString();
}
/**
* Resets this instance; this will resetToFallback the parsing position, the error
* index and also all containing results.
*/
public void reset(){
this.index = 0;
this.errorIndex = -1;
this.results.clear();
}
/**
* Add a result to the results of this context.
*
* @param key The result key
* @param value The result value
*/
public void addParseResult(Object key, Object value){
this.results.put(key, value);
}
/**
* Access all results.
*
* @return the unmodifiable map of the results.
*/
public Map<Object,Object> getParseResults(){
return Collections.unmodifiableMap(this.results);
}
/**
* Checks if the parse has found an error.
*
* @return whether a parse error has occurred
*/
public boolean isError(){
return errorIndex >= 0;
}
/**
* Checks if the text has been fully parsed such that there is no more text
* to parse.
*
* @return true if fully parsed
*/
public boolean isFullyParsed(){
return index == this.originalInput.length();
}
/**
* Get a single result from the results stored.
*
* @param key the result key
* @param type the result type
* @return the result value, casted to T, or null.
*/
@SuppressWarnings("unchecked")
public <T> T getResult(Object key, Class<T> type){
return (T) results.get(key);
}
/**
* This method skips all whitespaces and returns the full text, until
* another whitespace area or the end of the input is reached. The method
* will not update any index pointers.
*
* @return the next token found, or null.
*/
public String lookupNextToken(){
skipWhitespace();
int start = index;
for(int end = index; end < originalInput.length(); end++){
if(Character.isWhitespace(originalInput.charAt(end))){
if(end > start){
return originalInput.subSequence(start, end).toString();
}
return null;
}
}
return null;
}
/**
* Converts the indexes to a parse position.
*
* @return the parse position, never null
*/
public ParsePosition toParsePosition(){
return new ParsePosition(index);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString(){
return "ItemParseContext [index=" + index + ", errorIndex=" + errorIndex + ", originalInput='" + originalInput +
"', results=" + results + "]";
}
}