001package jmri.jmrit.logixng.util.parser;
002
003import java.lang.reflect.*;
004import java.util.*;
005
006import jmri.JmriException;
007import jmri.jmrit.logixng.SymbolTable;
008
009/**
010 * A parsed expression
011 *
012 * @author Daniel Bergqvist 2021
013 */
014public class ExpressionNodeMethod implements ExpressionNodeWithParameter {
015
016    private final String _method;
017    private final List<ExpressionNode> _parameterList;
018
019    public ExpressionNodeMethod(String method, Map<String, Variable> variables,
020            List<ExpressionNode> parameterList) throws FunctionNotExistsException {
021        _method = method;
022        _parameterList = parameterList;
023    }
024
025    private boolean isAssignableFrom(Class<?> type, Object param) {
026        if (param == null) return true;
027        if (type.isAssignableFrom(param.getClass())) return true;
028
029        if ((type == Boolean.TYPE) && (param instanceof Boolean)) return true;
030
031        if ((type == Byte.TYPE) && (param instanceof Byte)) return true;
032        if ((type == Short.TYPE) && (param instanceof Byte)) return true;
033        if ((type == Integer.TYPE) && (param instanceof Byte)) return true;
034        if ((type == Long.TYPE) && (param instanceof Byte)) return true;
035        if ((type == Float.TYPE) && (param instanceof Byte)) return true;
036        if ((type == Double.TYPE) && (param instanceof Byte)) return true;
037
038        if ((type == Byte.TYPE) && (param instanceof Short)) return true;
039        if ((type == Short.TYPE) && (param instanceof Short)) return true;
040        if ((type == Integer.TYPE) && (param instanceof Short)) return true;
041        if ((type == Long.TYPE) && (param instanceof Short)) return true;
042        if ((type == Float.TYPE) && (param instanceof Short)) return true;
043        if ((type == Double.TYPE) && (param instanceof Short)) return true;
044
045        if ((type == Byte.TYPE) && (param instanceof Integer)) return true;
046        if ((type == Short.TYPE) && (param instanceof Integer)) return true;
047        if ((type == Integer.TYPE) && (param instanceof Integer)) return true;
048        if ((type == Long.TYPE) && (param instanceof Integer)) return true;
049        if ((type == Float.TYPE) && (param instanceof Integer)) return true;
050        if ((type == Double.TYPE) && (param instanceof Integer)) return true;
051
052        if ((type == Byte.TYPE) && (param instanceof Long)) return true;
053        if ((type == Short.TYPE) && (param instanceof Long)) return true;
054        if ((type == Integer.TYPE) && (param instanceof Long)) return true;
055        if ((type == Long.TYPE) && (param instanceof Long)) return true;
056        if ((type == Float.TYPE) && (param instanceof Long)) return true;
057        if ((type == Double.TYPE) && (param instanceof Long)) return true;
058
059        if ((type == Float.TYPE) && (param instanceof Float)) return true;
060        if ((type == Double.TYPE) && (param instanceof Float)) return true;
061
062        if ((type == Float.TYPE) && (param instanceof Double)) return true;
063        return ((type == Double.TYPE) && (param instanceof Double));
064    }
065
066    private boolean canCall(Method m, Object[] params) {
067        Class<?>[] paramTypes = m.getParameterTypes();
068        if (paramTypes.length != params.length) return false;
069        for (int i=0; i < paramTypes.length; i++) {
070            if (!isAssignableFrom(paramTypes[i], params[i])) return false;
071        }
072        return true;
073    }
074
075    @SuppressWarnings("rawtypes")   // We don't know the generic types of Map.Entry in this method
076    private Object callMethod(Method method, Object obj, Object[] params)
077            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, ReflectionException {
078
079        Class<?>[] paramTypes = method.getParameterTypes();
080        Object[] newParams = new Object[params.length];
081        for (int i=0; i < params.length; i++) {
082            Object newParam;
083            if ((params[i] == null) || (paramTypes[i].isAssignableFrom(params[i].getClass()))) {
084                newParam = params[i];
085            }
086
087            else if ((paramTypes[i] == Boolean.TYPE) && (params[i] instanceof Boolean)) newParam = params[i];
088
089            else if ((paramTypes[i] == Byte.TYPE) && (params[i] instanceof Byte)) newParam = params[i];
090            else if ((paramTypes[i] == Short.TYPE) && (params[i] instanceof Byte)) newParam = (short)(byte)params[i];
091            else if ((paramTypes[i] == Integer.TYPE) && (params[i] instanceof Byte)) newParam = (int)(byte)params[i];
092            else if ((paramTypes[i] == Long.TYPE) && (params[i] instanceof Byte)) newParam = (long)(byte)params[i];
093            else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Byte)) newParam = (float)(byte)params[i];
094            else if ((paramTypes[i] == Double.TYPE) && (params[i] instanceof Byte)) newParam = (double)(byte)params[i];
095
096            else if ((paramTypes[i] == Byte.TYPE) && (params[i] instanceof Short)) newParam = (byte)(short)params[i];
097            else if ((paramTypes[i] == Short.TYPE) && (params[i] instanceof Short)) newParam = params[i];
098            else if ((paramTypes[i] == Integer.TYPE) && (params[i] instanceof Short)) newParam = (int)(short)params[i];
099            else if ((paramTypes[i] == Long.TYPE) && (params[i] instanceof Short)) newParam = (long)(short)params[i];
100            else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Short)) newParam = (float)(short)params[i];
101            else if ((paramTypes[i] == Double.TYPE) && (params[i] instanceof Short)) newParam = (double)(short)params[i];
102
103            else if ((paramTypes[i] == Byte.TYPE) && (params[i] instanceof Integer)) newParam = (byte)(int)params[i];
104            else if ((paramTypes[i] == Short.TYPE) && (params[i] instanceof Integer)) newParam = (short)(int)params[i];
105            else if ((paramTypes[i] == Integer.TYPE) && (params[i] instanceof Integer)) newParam = params[i];
106            else if ((paramTypes[i] == Long.TYPE) && (params[i] instanceof Integer)) newParam = (long)(int)params[i];
107            else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Integer)) newParam = (float)(int)params[i];
108            else if ((paramTypes[i] == Double.TYPE) && (params[i] instanceof Integer)) newParam = (double)(int)params[i];
109
110            else if ((paramTypes[i] == Byte.TYPE) && (params[i] instanceof Long)) newParam = (byte)(long)params[i];
111            else if ((paramTypes[i] == Short.TYPE) && (params[i] instanceof Long)) newParam = (short)(long)params[i];
112            else if ((paramTypes[i] == Integer.TYPE) && (params[i] instanceof Long)) newParam = (int)(long)params[i];
113            else if ((paramTypes[i] == Long.TYPE) && (params[i] instanceof Long)) newParam = params[i];
114            else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Long)) newParam = (float)(long)params[i];
115            else if ((paramTypes[i] == Double.TYPE) && (params[i] instanceof Long)) newParam = (double)(long)params[i];
116
117            else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Float)) newParam = params[i];
118            else if ((paramTypes[i] == Double.TYPE) && (params[i] instanceof Float)) newParam = (double)(float)params[i];
119
120            else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Double)) newParam = (float)(double)params[i];
121            else if ((paramTypes[i] == Double.TYPE) && (params[i] instanceof Double)) newParam = params[i];
122
123            else throw new RuntimeException(String.format("%s can not be assigned to %s", params[i].getClass().getName(), paramTypes[i].getName()));
124
125            newParams[i] = newParam;
126        }
127        try {
128            return method.invoke(obj, newParams);
129        } catch (IllegalAccessException ex) {
130            // https://stackoverflow.com/questions/50306093/java-9-calling-map-entrygetvalue-via-reflection#comment87628501_50306192
131            // https://stackoverflow.com/a/12038265
132            if (obj instanceof Map.Entry && newParams.length == 0) {
133                switch (method.getName()) {
134                    case "toString": return obj.toString();
135                    case "getKey": return ((Map.Entry)obj).getKey();
136                    case "getValue": return ((Map.Entry)obj).getValue();
137                    default:
138                        // Do nothing
139                }
140            }
141            if (!Modifier.isPublic(obj.getClass().getModifiers())) {
142                throw new ReflectionException("Can't call methods on object since it's private", ex);
143            }
144            throw ex;
145        }
146    }
147
148    @Override
149    public Object calculate(Object parameter, SymbolTable symbolTable) throws JmriException {
150        if (parameter == null) throw new NullPointerException("Parameter is null");
151
152        Method[] methods = parameter.getClass().getMethods();
153        List<Object> parameters = new ArrayList<>();
154        for (ExpressionNode exprNode : _parameterList) {
155            parameters.add(exprNode.calculate(symbolTable));
156        }
157        Object[] params = parameters.toArray();
158
159        Exception exception = null;
160        for (Method m : methods) {
161            if (!m.getName().equals(_method)) continue;
162            try {
163                if (canCall(m, params)) return callMethod(m, parameter, params);
164            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
165                exception = ex;
166            }
167        }
168
169        if (exception != null) throw new ReflectionException("Reflection exception", exception);
170
171        List<String> paramList = new ArrayList<>();
172        for (Object o : params) paramList.add(String.format("%s:%s", o, o != null ? o.getClass().getName() : "null"));
173        throw new CannotCallMethodException(String.format("Can not call method %s(%s) on object %s", _method, String.join(", ", paramList), parameter), _method);
174    }
175
176    /** {@inheritDoc} */
177    @Override
178    public String getDefinitionString() {
179        StringBuilder str = new StringBuilder();
180        str.append("Method:");
181        str.append(_method);
182        str.append("(");
183        for (int i=0; i < _parameterList.size(); i++) {
184            if (i > 0) {
185                str.append(",");
186            }
187            str.append(_parameterList.get(i).getDefinitionString());
188        }
189        str.append(")");
190        return str.toString();
191    }
192
193}