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