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 stack = new Stack(); for( String eval: pn){ //check if digit if ( Character.isDigit( eval.charAt(0)) ) { try { int intOper = Integer.parseInt(eval); stack.add(intOper); } catch( NumberFormatException infex ){ try { float doubleOper = Float.parseFloat( eval ); stack.add(doubleOper); } catch( NumberFormatException dnfex ){ System.out.println(dnfex); } } } else{ if ( operations.contains(eval) ) { Object obj2 = stack.pop(); Object obj1 = stack.pop(); Number oper2, oper1; oper2 = oper1 = null; if (obj2 instanceof Number) { oper2 = (Number) obj2; } if (obj1 instanceof Number) { oper1 = (Number) obj1; } boolean o1double = false; boolean o2double = false; switch( eval.charAt(0) ){ case '^' : { stack.push(Math.pow(oper1.floatValue(), oper2.floatValue())); break; } case '*' : { if ( oper1 instanceof Float ) o1double = true; if ( oper2 instanceof Float ) o2double = true; if ( o1double || o2double ) stack.push( oper1.floatValue() * oper2.floatValue() ); else stack.push( oper1.intValue() * oper2.intValue() ); break; } case '/' : { if ( oper1 instanceof Float ) o1double = true; if ( oper2 instanceof Float ) o2double = true; if ( o1double || o2double ) stack.push( oper1.floatValue() / oper2.floatValue() ); else{ //if ( 0 == oper2 ) throws NumberFormatException( "Illegal operand :" ) stack.push( oper1.intValue() / oper2.floatValue() ); } break; } case '+' : { if ( oper1 instanceof Float ) o1double = true; if ( oper2 instanceof Float ) o2double = true; if ( o1double || o2double ) stack.push( oper1.floatValue() + oper2.floatValue() ); else if ( obj2 instanceof String || obj1 instanceof String ){ stack.push( obj1.toString() + obj2.toString() ); } else{ stack.push( oper1.intValue() + oper2.intValue() ); } break; } case '-' : { if ( oper1 instanceof Float ) o1double = true; if ( oper2 instanceof Float ) o2double = true; if ( o1double || o2double ) stack.push( oper1.floatValue() - oper2.floatValue() ); else stack.push( oper1.intValue() - oper2.intValue() ); break; } } } else if (functionMap.containsKey( eval )){ Integer paramsCount = functionMap.get( eval ); Object params[] = new Object[paramsCount]; for ( int i=0; i expressions = new HashMap(); expressions.put("2+2/2","3.0"); expressions.put("3+4*2/(1-5)^2","3.5"); expressions.put("name1+_+text+_+name1","name1_text_name1"); expressions.put("name+_+text+_+name","dst_class_text_dst_class"); expressions.put("replace(ab,xx,abbbbb)","xxbbbb"); expressions.put("replace('+',' ','IBZ+GJ')","IBZ GJ"); expressions.put("replace(ab,xx,abbbbb)","xxbbbb"); for (Entry entry : expressions.entrySet()) { if ( !doCalc(entry.getKey(), dst).equals( entry.getValue() )) throw new RuntimeException("you breeeeeeeak me: "+entry.getKey() + " != " + entry.getValue()); } } }