package com.factor.util;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.Map.Entry;
/**
* Universal calculator. It can ecaluate math operations on numeric, string and object arguments
*
input: hello + Jonh result: helloJohn
*
input: 2+3 result: 3
*
input: 2+2/2 result: 3.5
*
input: replace(ab,xx,abbbbb) result: xxbbbb
* if we have some object w/ attribute name=John, so when input is: "2+name" output will be "2John"
*
* Feel free to send me any notes about how this class work
*
* @author Maxim Sukhomud(javaguru@mail.ru)
*/
public class Parser {
private static String operationsArray[] = {"+","-","/","*","^"};
private static String braketsArray[] = {"(",")"};
private static Map operationMap = new HashMap();
private static Map functionMap = new HashMap();
private static final Set operations = Collections.unmodifiableSet( new HashSet(Arrays.asList(operationsArray)));
private static final String delim=",";
static{
operationMap.put("+", 0);
operationMap.put("-", 0);
operationMap.put("/", 1);
operationMap.put("*", 1);
operationMap.put("^", 2);
functionMap.put("replace", 3);
}
private static final int compareOperations(String c1, String c2){
Integer i1 = operationMap.get( c1 );
Integer i2 = operationMap.get( c2 );
if ( null == i1 ) throw new NumberFormatException("Unknown operation: "+c1);
if ( null == i2 ) throw new NumberFormatException("Unknown operation: "+c2);
return i1 - i2;
}
private static String replace(String target, String replacement, String string){
if ( null != string )
return string.replace("'", "").replace(target.replace("'", ""), replacement.replace("'", ""));
return "";
}
private static Object invoke( String function, Object args[] ){
if ( function.equals("replace") ){
return replace((String)args[2],(String)args[1],(String)args[0]);
}
return "";
}
private static class State{
private String name;
public static final State operator = new State("operator");
public static final State func = new State("func");
public static final State letter = new State("letter");
public static final State bracketL = new State("bracket1");
public static final State bracketR = new State("bracket2");
public static final State digit = new State("digit");
// public static final State start = new State("start");
public static final State end = new State("end");
public static final State apostrophe= new State("apostrophe");
//public static final State apostrophe2= new State("apostrophe2");
private State(String name){
this.name = name;
}
private String getName() {return name;}
public boolean equals( State s ){
return name.equals(s.getName());
}
}
private static Object getValue( String valueName, Object src ){
String methodName = "get"+(new Character(valueName.charAt(0)).toString()).toUpperCase()+valueName.substring(1);
try {
Method method = src.getClass().getMethod(methodName, (Class[]) null );
return method.invoke( src, (Object[])null );
}
catch (Exception e) {
return null;
}
}
/**
* Does one operation w/ one character from input string
* @param letter character to analyze
* @param retVal
* @param unknownLetters
* @param stack
* @param readState
* @param src
* @return
*/
private static State doIteration(char letter, StringBuilder retVal,StringBuilder unknownLetters, List stack, State readState, Object src ){
//decimal separator is . (dot)
if ( readState.equals(State.apostrophe)){
//stack.add(Character.valueOf(letter).toString());
retVal.append( Character.valueOf(letter).toString() );
if ( Character.valueOf(letter).equals('\'') ){
readState = State.letter;
}
}
else if ( letter == '\n' && readState.equals( State.end ) )
{
if ( unknownLetters.length() > 0 ){
final String name = unknownLetters.toString();
Object value = getValue(name, src);
if ( null != value ) stack.add( value.toString() );
else {
if ( retVal.length() > 0 ) retVal.append(delim);
retVal.append( name );
}
unknownLetters.delete(0, unknownLetters.length());
}
}
else if ( (readState.equals(State.end) ||readState.equals( State.digit)) &&
(Character.isDigit( letter ) || Character.valueOf('\u002e').equals(letter))
){
retVal.append( letter );
readState = State.digit;
}
else if ( (readState.equals(State.end) || readState.equals( State.operator)|| readState.equals( State.bracketL))&& Character.isDigit( letter )){
if ( retVal.length() > 0 ) retVal.append(delim);
retVal.append( letter );
readState = State.digit;
}
else if ( Character.valueOf('\u002c').equals(letter) ){
boolean lbfound = false;
if ( unknownLetters.length() > 0 ){
final String maybeArg = unknownLetters.toString();
unknownLetters.delete(0, unknownLetters.length());
stack.add(maybeArg);
for ( int j = stack.size()-1; j>=0; --j ){
String op = stack.get( j );
if ( !op.equals( braketsArray[0] ) )
{
if ( retVal.length() > 0 ) retVal.append(delim);
retVal.append( op );
stack.remove(j);
}
else{
lbfound = true;
break;
}
}
if ( !lbfound ) throw new NumberFormatException("Wrong expression");
}
}
else if ( Character.valueOf(letter).equals('\'') ){
if ( retVal.length() > 0 ) retVal.append(delim);
retVal.append( Character.valueOf(letter).toString() );
readState = State.apostrophe;
}
else
{
String letterString = Character.valueOf(letter).toString();
if ( operations.contains(letterString) ){
if ( readState.equals( State.letter ) ){
final String name = unknownLetters.toString();
Object value = getValue(name, src);
if ( retVal.length() > 0 ) retVal.append(delim);
if ( null != value )
retVal.append( value.toString() );
else
retVal.append( name );
unknownLetters.delete(0, unknownLetters.length());
}
if ( stack.size() > 0 ){
for ( int j = stack.size() -1; j>=0; --j ){
String op = stack.get( j );
if ( operations.contains(op) ){
if ( compareOperations(letterString, op)<=0 ){
retVal.append(delim);
retVal.append( op );
stack.remove(j);
}
else
break;
}
else break;
}
}
stack.add( letterString );
readState = State.operator;
}
else if ( braketsArray[0].equals( letterString ) ){
//check that unknown letters is some function
if ( readState.equals( State.letter ) ){
final String mayBefunction = unknownLetters.toString();
if ( functionMap.containsKey( unknownLetters.toString() )){
stack.add( mayBefunction );
}
else
{
//WTF ???
throw new NumberFormatException("Unknown function: "+mayBefunction);
}
unknownLetters.delete(0, unknownLetters.length());
//may be
}
stack.add( letterString );
readState = State.bracketL;
}
else if ( braketsArray[1].equals( letterString ) ){
if ( stack.size() > 0 ){
for ( int j = stack.size()-1; j>=0; --j ){
String op = stack.get( j );
if ( !op.equals( braketsArray[0] ) )
{
retVal.append(",");
retVal.append( op );
stack.remove(j);
}
else{
stack.remove(j);
break;
}
}
}
else {
throw new NumberFormatException("Count of brackets!");
}
readState = State.bracketR;
}
else if ( letter == '\n' ){
/* if ( unknownLetters.length() > 0 ){
stack.add( unknownLetters.toString() );
}
*/
//WTF
throw new NumberFormatException("Wrong here.");
}
else {
unknownLetters.append(letterString);
readState = State.letter;
}
}
return readState;
}
/**
* Does Dijkstra transformation( infix to postfix ) for input string
* @param input string in infix notation
* @return string expression where input string will be converted to postfix notation
*/
public static String doDijkstraTransform(String input)
{
return doDijkstraTransform(input, null);
}
/**
* Does Dijkstra transformation( infix to postfix ) for input string
* @param input string in infix notation
* @param src object where we can find some fields if we use them in input
* @return string expression where input string will be converted to postfix notation
*/
public static String doDijkstraTransform(String input, Object src)
{
StringBuilder retVal = new StringBuilder();
StringBuilder unknownLetters = new StringBuilder();
List stack = new ArrayList();
State readState = State.end;
for ( int i=0; i 0 ){
if ( retVal.length() > 0 ){
for ( int j= stack.size()-1; j>=0; --j )
{
retVal.append(delim);
retVal.append(stack.get(j));
}
}
else
{
int j= stack.size()-1;
retVal.append(stack.get(j));
for ( j= stack.size()-2; j>=0; --j )
{
retVal.append(delim);
retVal.append(stack.get(j));
}
}
}
return retVal.toString();
}
/**
* Evaluate expression. Expression should be in infix notation. Example "2+2/2"
* @param expression expression to calculate in infix notation. Can contan fields from src object
* @param src object which fields could be used for calculation expression. Use null if expression doesn't use any other fields.
* @return result of calculation
*/
public static String doCalc(String expression, Object src){
return Parser.evalPostfixNotation( Parser.doDijkstraTransform(expression, src)).toString();
}
/**
* Evaluate expression. Expression should be in infix notation. Example "2+2/2"
* @param expression expression to calculate in infix notation.
* @param src object which fields could be used for calculation expression. Use null if expression doesn't use any other fields
* @return result of calculation
*/
public static String doCalc(String expression){
return doCalc(expression, null);
}
/**
* Evaluates expression in postfix notation
* @param postfixNotation - string where expressin is written in postfix notation
* @return result of expression
* @throws IllegalArgumentException
*/
public static Object evalPostfixNotation(String postfixNotation)
throws IllegalArgumentException
{
String pn[] = postfixNotation.split(delim);
Stack