View Javadoc

1   package cz.cuni.amis.pogamut.sposh.executor;
2   
3   import cz.cuni.amis.pogamut.sposh.engine.VariableContext;
4   import cz.cuni.amis.pogamut.sposh.exceptions.FubarException;
5   import cz.cuni.amis.pogamut.sposh.exceptions.MethodException;
6   import java.lang.annotation.Annotation;
7   import java.lang.reflect.InvocationTargetException;
8   import java.lang.reflect.Method;
9   import java.lang.reflect.Modifier;
10  import java.util.LinkedList;
11  import java.util.List;
12  import java.util.logging.Level;
13  import java.util.logging.Logger;
14  
15  /**
16   *
17   * @author Honza
18   */
19  class ParamsMethod<RETURN> {
20  
21      private final String methodName;
22      private final Class<RETURN> returnCls;
23      private final Class<?> methodClass;
24      private final Method method;
25      
26      ParamsMethod(Class methodClass, String methodName, Class<RETURN> returnCls) {
27          this.methodClass = methodClass;
28          this.methodName = methodName;
29          this.returnCls = returnCls;
30  
31          this.method = findMethod();
32      }
33  
34      /**
35       * Is every parameter of the @method among @acceptedTypes?
36       *
37       * @param method Method whose parameters are checked
38       * @param acceptedTypes Acceptable types of parameters
39       * @return
40       */
41      private boolean areParamsAcceptable(Method method, Class<?>... acceptedTypes) {
42          for (Class<?> paramType : method.getParameterTypes()) {
43              boolean found = false;
44              for (Class<?> acceptedType : acceptedTypes) {
45                  if (paramType.equals(acceptedType)) {
46                      found = true;
47                  }
48              }
49              if (!found) {
50                  return false;
51              }
52          }
53          return true;
54      }
55  
56      /**
57       * Find @seekedAnnotation among array of passed annotations and return it.
58       *
59       * @param <T> Type of annotation.
60       * @param annotations Array of annotations that are being searched in.
61       * @param seekedAnnotation Type of annotation this method is looking for
62       * @return Return found annotation or null if not present.
63       */
64      private <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T> seekedAnnotation) {
65          for (Annotation annotation : annotations) {
66              if (annotation.annotationType().equals(seekedAnnotation)) {
67                  return (T) annotation;
68              }
69          }
70          return null;
71      }
72  
73      /**
74       * Are all parameters of @method annotated with {@link Param}?
75       *
76       * @param method Method whose parameters are checked.
77       * @return
78       */
79      private boolean areParamsAnnotated(Method method) {
80          for (Annotation[] paramAnnotations : method.getParameterAnnotations()) {
81              if (getAnnotation(paramAnnotations, Param.class) == null) {
82                  return false;
83              }
84          }
85          return true;
86      }
87  
88      /**
89       * Filter all methods to ensure following conditions:
90       *
91       * <ul><li>Method name is @name</li>
92       *
93       * <li>Method is <tt>public<tt></li>
94       *
95       * <li>Method is not <tt>abstract</tt></li>.
96       *
97       * <li>Method does not have variable number of arguments</li>
98       *
99       * <li>Return type of method is @returnType</li>
100      *
101      * <li>All parameter fields are of type {@link String}, {@link Integer} or {@link Double}</li>
102      *
103      * <li>All parameters are annotated with {@link Param}</li> </ul>
104      *
105      * @param methods Array of methods to be filtered.
106      * @param seekedName Name of the methods we are looking for.
107      * @param returnType Expected return type of filtered methods.
108      * @return Array of methods with @name.
109      */
110     private Method[] filterMethods(Method[] methods, String seekedName, Class<?> returnType) {
111         List<Method> filteredMethods = new LinkedList<Method>();
112 
113         for (Method testedMethod : methods) {
114             String testedMethodName = testedMethod.getName();
115             boolean methodIsPublic = (testedMethod.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC;
116             boolean methodIsAbstract = (testedMethod.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT;
117             boolean correctReturnType = returnType.isAssignableFrom(testedMethod.getReturnType());
118             boolean acceptedParams = areParamsAcceptable(testedMethod, String.class, Integer.class, int.class, Double.class, double.class);
119             boolean annotatedParams = areParamsAnnotated(testedMethod);
120 
121             if (testedMethodName.equals(seekedName)
122                     && methodIsPublic
123                     && !methodIsAbstract
124                     && !testedMethod.isVarArgs()
125                     && correctReturnType
126                     && acceptedParams
127                     && annotatedParams) {
128                 filteredMethods.add(testedMethod);
129             }
130         }
131         return filteredMethods.toArray(new Method[filteredMethods.size()]);
132     }
133 
134     /**
135      * Go through all methods of the {@link #methodClass} and find the one which
136      * has suits our tastes (see {@link #filterMethods(java.lang.reflect.Method[], java.lang.String, java.lang.Class)
137      * }) for details.
138      * 
139      * @throws NoSuchMethodError If no such method exists
140      * @throws UnsupportedOperationException If there is more than one such method.
141      *
142      * @return Found method.
143      */
144     final Method findMethod() {
145         Method[] methods = filterMethods(methodClass.getMethods(), methodName, returnCls);
146         if (methods.length == 0) {
147             throw new NoSuchMethodError("Unable to find method " + methodName);
148         }
149         if (methods.length > 1) {
150             throw new UnsupportedOperationException("Multiple (" + methods.length + ") possible " + methodName + " methods, overloading is not supported.");
151         }
152         return methods[0];
153     }
154 
155     /**
156      * Take the passed @thisObject and call parametrized method on it.
157      * 
158      * @param params Variable context that will fill the parameters of the
159      * method with its variables ({@link Param#value() ) is used as name of variable in the context.
160      * @throws InvocationTargetException When invoked method throws an exception.
161      */
162     public final RETURN invoke(Object thisObject, VariableContext params) throws InvocationTargetException {
163         Class<?>[] paramTypes = method.getParameterTypes();
164         Annotation[][] paramsAnnotations = method.getParameterAnnotations();
165 
166         assert paramsAnnotations.length == paramTypes.length;
167 
168         int paramCount = paramTypes.length;
169 
170         List methodArguments = new LinkedList();
171         for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
172             Annotation[] paramAnnotations = paramsAnnotations[paramIndex];
173             Param param = getAnnotation(paramAnnotations, Param.class);
174 
175             String variableName = param.value();
176             try {
177                 Object variableValue = params.getValue(variableName);
178                 methodArguments.add(variableValue);
179             } catch (IllegalArgumentException ex) {
180                 String thisObjectName = thisObject.getClass().getName();
181                 throw new MethodException("No variable " + variableName + " for " + thisObjectName + "." + methodName, ex);
182             }
183         }
184 
185         try {
186             Object ret = method.invoke(thisObject, methodArguments.toArray());
187             return (RETURN) ret;
188         } catch (IllegalAccessException ex) {
189             throw new FubarException("findMethod filters for public methods", ex);
190         } catch (IllegalArgumentException ex) {
191             throw new FubarException("Error with parameter maching code", ex);
192         } catch (InvocationTargetException ex) {
193             throw ex;
194         }
195     }
196 }