1 package cz.cuni.amis.pogamut.sposh.executor;
2
3 import cz.cuni.amis.pogamut.sposh.elements.EnumValue;
4 import cz.cuni.amis.pogamut.sposh.engine.VariableContext;
5 import cz.cuni.amis.pogamut.sposh.exceptions.FubarException;
6 import cz.cuni.amis.pogamut.sposh.exceptions.MethodException;
7 import java.lang.annotation.Annotation;
8 import java.lang.reflect.Field;
9 import java.lang.reflect.InvocationTargetException;
10 import java.lang.reflect.Method;
11 import java.lang.reflect.Modifier;
12 import java.util.LinkedList;
13 import java.util.List;
14 import java.util.logging.Level;
15 import java.util.logging.Logger;
16
17
18
19
20
21 class ParamsMethod<RETURN> {
22
23 private final Class<?>[] allowedParamClasses = new Class<?>[]{
24 String.class,
25 Integer.class,
26 int.class,
27 Double.class,
28 double.class,};
29 private final String methodName;
30 private final Class<RETURN> returnCls;
31 private final Class<?> methodClass;
32 private final Method method;
33
34 ParamsMethod(Class methodClass, String methodName, Class<RETURN> returnCls) {
35 this.methodClass = methodClass;
36 this.methodName = methodName;
37 this.returnCls = returnCls;
38
39 this.method = findMethod();
40 }
41
42
43
44
45
46
47
48
49
50 private boolean areParamsAcceptable(Method method, boolean isEnumAcceptable, Class<?>... acceptedTypes) {
51 for (Class<?> paramType : method.getParameterTypes()) {
52 boolean paramAcceptable = false;
53 for (Class<?> acceptedType : acceptedTypes) {
54 if (paramType.equals(acceptedType)) {
55 paramAcceptable = true;
56 }
57 }
58 if (isEnumAcceptable && paramType.isEnum() && Modifier.isPublic(paramType.getModifiers())) {
59 paramAcceptable = true;
60 }
61 if (!paramAcceptable) {
62 return false;
63 }
64 }
65 return true;
66 }
67
68
69
70
71
72
73
74
75
76 private <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T> seekedAnnotation) {
77 for (Annotation annotation : annotations) {
78 if (annotation.annotationType().equals(seekedAnnotation)) {
79 return (T) annotation;
80 }
81 }
82 return null;
83 }
84
85
86
87
88
89
90
91 private boolean areParamsAnnotated(Method method) {
92 for (Annotation[] paramAnnotations : method.getParameterAnnotations()) {
93 if (getAnnotation(paramAnnotations, Param.class) == null) {
94 return false;
95 }
96 }
97 return true;
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 private Method[] filterMethods(Method[] methods, String seekedName, Class<?> returnType) {
123 List<Method> filteredMethods = new LinkedList<Method>();
124
125 for (Method testedMethod : methods) {
126 String testedMethodName = testedMethod.getName();
127 boolean methodIsPublic = (testedMethod.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC;
128 boolean methodIsAbstract = (testedMethod.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT;
129 boolean correctReturnType = returnType.isAssignableFrom(testedMethod.getReturnType());
130 boolean acceptedParams = areParamsAcceptable(testedMethod, true, allowedParamClasses);
131 boolean annotatedParams = areParamsAnnotated(testedMethod);
132
133 if (testedMethodName.equals(seekedName)
134 && methodIsPublic
135 && !methodIsAbstract
136 && !testedMethod.isVarArgs()
137 && correctReturnType
138 && acceptedParams
139 && annotatedParams) {
140 filteredMethods.add(testedMethod);
141 }
142 }
143 return filteredMethods.toArray(new Method[filteredMethods.size()]);
144 }
145
146
147
148
149
150
151
152
153
154
155
156
157 final Method findMethod() {
158 Method[] methods = filterMethods(methodClass.getMethods(), methodName, returnCls);
159 if (methods.length == 0) {
160 throw new NoSuchMethodError("Unable to find method " + methodName);
161 }
162 if (methods.length > 1) {
163 throw new UnsupportedOperationException("Multiple (" + methods.length + ") possible " + methodName + " methods, overloading is not supported.");
164 }
165 return methods[0];
166 }
167
168
169
170
171
172
173
174
175
176 public final RETURN invoke(Object thisObject, VariableContext params) throws InvocationTargetException {
177 Class<?>[] paramTypes = method.getParameterTypes();
178 Annotation[][] paramsAnnotations = method.getParameterAnnotations();
179
180 assert paramsAnnotations.length == paramTypes.length;
181
182 int paramCount = paramTypes.length;
183
184 List methodArguments = new LinkedList();
185 for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
186 Annotation[] paramAnnotations = paramsAnnotations[paramIndex];
187 Param param = getAnnotation(paramAnnotations, Param.class);
188
189 String variableName = param.value();
190 Object argumentValue = getArgumentValue(thisObject, params, paramTypes[paramIndex], variableName);
191 methodArguments.add(argumentValue);
192 }
193
194 try {
195 Object ret = method.invoke(thisObject, methodArguments.toArray());
196 return (RETURN) ret;
197 } catch (IllegalAccessException ex) {
198 throw new FubarException("findMethod filters for public methods", ex);
199 } catch (IllegalArgumentException ex) {
200 throw new FubarException("Error with parameter maching code", ex);
201 } catch (InvocationTargetException ex) {
202 throw ex;
203 }
204 }
205
206 private Object getArgumentValue(Object thisObject, VariableContext ctx, Class<?> paramType, String variableName) {
207 try {
208 Object argumentValue = ctx.getValue(variableName);
209
210
211 if (paramType.isEnum()) {
212 if (!argumentValue.getClass().equals(EnumValue.class)) {
213 throw new MethodException("Variable " + variableName + " should be an " + EnumValue.class.getSimpleName() + " string containing the FQN of an enum value.");
214 }
215 argumentValue = convertToEnumConstant((Class<Enum>)paramType, (EnumValue) argumentValue);
216 }
217
218 return argumentValue;
219 } catch (IllegalArgumentException ex) {
220 String thisObjectName = thisObject.getClass().getName();
221 throw new MethodException("No variable " + variableName + " for " + thisObjectName + '.' + methodName + '.', ex);
222 }
223 }
224
225
226
227
228
229
230
231
232
233 private Object convertToEnumConstant(Class<Enum> enumClass, EnumValue enumValue) {
234 String enumValueFQNPrefix = enumClass.getName() + '.';
235 String enumValueFQN = enumValue.getName();
236 if (!enumValueFQN.startsWith(enumValueFQNPrefix)) {
237 throw new IllegalArgumentException("Unable to convert \"" + enumValueFQN + "\" to the value of enum " + enumClass.getName() + ".");
238 }
239 String enumName = enumValueFQN.substring(enumValueFQNPrefix.length());
240 return Enum.valueOf(enumClass, enumName);
241 }
242 }