/* * Copyright (c) 2008-2009, Matthias Mann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Matthias Mann nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.matthiasmann.twl.utils; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Matthias Mann */ public abstract class AbstractMathInterpreter implements SimpleMathParser.Interpreter { public interface Function { public Object execute(Object ... args); } private final ArrayList<Object> stack; private final HashMap<String, Function> functions; public AbstractMathInterpreter() { this.stack = new ArrayList<Object>(); this.functions = new HashMap<String, Function>(); registerFunction("min", new FunctionMin()); registerFunction("max", new FunctionMax()); } public final void registerFunction(String name, Function function) { if(function == null) { throw new NullPointerException("function"); } functions.put(name, function); } public Number execute(String str) throws ParseException { stack.clear(); SimpleMathParser.interpret(str, this); if(stack.size() != 1) { throw new IllegalStateException("Expected one return value on the stack"); } return popNumber(); } public int[] executeIntArray(String str) throws ParseException { stack.clear(); int count = SimpleMathParser.interpretArray(str, this); if(stack.size() != count) { throw new IllegalStateException("Expected " + count + " return values on the stack"); } int[] result = new int[count]; for(int i=count ; i-->0 ;) { result[i] = popNumber().intValue(); } return result; } public<T> T executeCreateObject(String str, Class<T> type) throws ParseException { stack.clear(); int count = SimpleMathParser.interpretArray(str, this); if(stack.size() != count) { throw new IllegalStateException("Expected " + count + " return values on the stack"); } if(count == 1 && type.isInstance(stack.get(0))) { return type.cast(stack.get(0)); } for(Constructor<?> c : type.getConstructors()) { Class<?>[] params = c.getParameterTypes(); if(params.length == count) { boolean match = true; for(int i=0 ; i<count ; i++) { if(!ClassUtils.isParamCompatible(params[i], stack.get(i))) { match = false; break; } } if(match) { try { return type.cast(c.newInstance(stack.toArray(new Object[count]))); } catch (Exception ex) { Logger.getLogger(AbstractMathInterpreter.class.getName()).log( Level.SEVERE, "can't instantiate object", ex); } } } } throw new IllegalArgumentException("Can't construct a " + type + " from expression: \"" + str + "\""); } protected void push(Object obj) { stack.add(obj); } protected Object pop() { int size = stack.size(); if(size == 0) { throw new IllegalStateException("stack underflow"); } return stack.remove(size-1); } protected Number popNumber() { Object obj = pop(); if(obj instanceof Number) { return (Number)obj; } throw new IllegalStateException("expected number on stack - found: " + ((obj != null) ? obj.getClass() : "null")); } public void loadConst(Number n) { push(n); } public void add() { Number b = popNumber(); Number a = popNumber(); boolean isFloat = isFloat(a) || isFloat(b); if(isFloat) { push(a.floatValue() + b.floatValue()); } else { push(a.intValue() + b.intValue()); } } public void sub() { Number b = popNumber(); Number a = popNumber(); boolean isFloat = isFloat(a) || isFloat(b); if(isFloat) { push(a.floatValue() - b.floatValue()); } else { push(a.intValue() - b.intValue()); } } public void mul() { Number b = popNumber(); Number a = popNumber(); boolean isFloat = isFloat(a) || isFloat(b); if(isFloat) { push(a.floatValue() * b.floatValue()); } else { push(a.intValue() * b.intValue()); } } public void div() { Number b = popNumber(); Number a = popNumber(); boolean isFloat = isFloat(a) || isFloat(b); if(isFloat) { if(Math.abs(b.floatValue()) == 0) { throw new IllegalStateException("division by zero"); } push(a.floatValue() / b.floatValue()); } else { if(b.intValue() == 0) { throw new IllegalStateException("division by zero"); } push(a.intValue() / b.intValue()); } } public void negate() { Number a = popNumber(); if(isFloat(a)) { push(-a.floatValue()); } else { push(-a.intValue()); } } public void accessArray() { Number idx = popNumber(); Object obj = pop(); if(obj == null) { throw new IllegalStateException("null pointer"); } if(!obj.getClass().isArray()) { throw new IllegalStateException("array expected"); } try { push(Array.get(obj, idx.intValue())); } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalStateException("array index out of bounds", ex); } } public void accessField(String field) { Object obj = pop(); if(obj == null) { throw new IllegalStateException("null pointer"); } Object result = accessField(obj, field); push(result); } protected Object accessField(Object obj, String field) { Class<? extends Object> clazz = obj.getClass(); try { if(clazz.isArray()) { if("length".equals(field)) { return Array.getLength(obj); } } else { Method m = findGetter(clazz, field); if(m == null) { for(Class<?> i : clazz.getInterfaces()) { m = findGetter(i, field); if(m != null) { break; } } } if(m != null) { return m.invoke(obj); } } } catch(Throwable ex) { throw new IllegalStateException("error accessing field '"+field+ "' of class '"+clazz+"'", ex); } throw new IllegalStateException("unknown field '"+field+ "' of class '"+clazz+"'"); } private static Method findGetter(Class<?> clazz, String field) { for(Method m : clazz.getMethods()) { if(!Modifier.isStatic(m.getModifiers()) && m.getReturnType() != Void.TYPE && Modifier.isPublic(m.getDeclaringClass().getModifiers()) && m.getParameterTypes().length == 0 && (cmpName(m, field, "get") || cmpName(m, field, "is"))) { return m; } } return null; } private static boolean cmpName(Method m, String fieldName, String prefix) { String methodName = m.getName(); int prefixLength = prefix.length(); int fieldNameLength = fieldName.length(); return methodName.length() == prefixLength + fieldNameLength && methodName.startsWith(prefix) && methodName.charAt(prefixLength) == Character.toUpperCase(fieldName.charAt(0)) && methodName.regionMatches(prefixLength+1, fieldName, 1, fieldNameLength-1); } public void callFunction(String name, int args) { Object[] values = new Object[args]; for(int i=args ; i-->0 ;) { values[i] = pop(); } Function function = functions.get(name); if(function == null) { throw new IllegalArgumentException("Unknown function"); } push(function.execute(values)); } protected static boolean isFloat(Number n) { return !(n instanceof Integer); } public abstract static class NumberFunction implements Function { protected abstract Object execute(int ... values); protected abstract Object execute(float ... values); public Object execute(Object... args) { for(Object o : args) { if(!(o instanceof Integer)) { float[] values = new float[args.length]; for(int i=0 ; i<values.length ; i++) { values[i] = ((Number)args[i]).floatValue(); } return execute(values); } } int[] values = new int[args.length]; for(int i=0 ; i<values.length ; i++) { values[i] = ((Number)args[i]).intValue(); } return execute(values); } } static class FunctionMin extends NumberFunction { @Override protected Object execute(int... values) { int result = values[0]; for(int i=1 ; i<values.length ; i++) { result = Math.min(result, values[i]); } return result; } @Override protected Object execute(float... values) { float result = values[0]; for(int i=1 ; i<values.length ; i++) { result = Math.min(result, values[i]); } return result; } } static class FunctionMax extends NumberFunction { @Override protected Object execute(int... values) { int result = values[0]; for(int i=1 ; i<values.length ; i++) { result = Math.max(result, values[i]); } return result; } @Override protected Object execute(float... values) { float result = values[0]; for(int i=1 ; i<values.length ; i++) { result = Math.max(result, values[i]); } return result; } } }