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}