View Javadoc

1   /* This file is part of COPAL (COntext Provisioning for All).
2    *
3    * COPAL is a part of SM4All (Smart hoMes for All) project.
4    *
5    * COPAL is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU Lesser General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * COPAL is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public License
16   * along with COPAL. If not, see <http://www.gnu.org/licenses/>.
17   */
18  package at.ac.tuwien.infosys.sm4all.copal.api.processor;
19  
20  import java.lang.reflect.Array;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.text.MessageFormat;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Set;
29  import at.ac.tuwien.infosys.sm4all.copal.api.event.ContextEvent;
30  
31  /**
32   * Invoker of an {@link Actions} {@link Method}. For method to be considered an
33   * {@link Actions} {@link Method} all these conditions must be met:
34   * <ol>
35   * <li>{@link Method} must be annotated with {@link Action} or {@link Actions}.</li>
36   * <li>{@link Method} must be public.</li>
37   * <li>{@link Method} must have one or two parameters.</li>
38   * <li>The required parameter of {@link Method} must be of type
39   * {@link ContextEvent} or a subclass of it.</li>
40   * <li>The other (optional) parameter of {@link Method} must be of type
41   * {@link ProcessorAction}.</li>
42   * <li>{@link Method} must be void or return {@link ContextEvent} (or a subclass
43   * of it), array of {@link ContextEvent}s (or a subclass of it), or
44   * {@link Collection} of {@link ContextEvent}s (or a subclass of it).</li>
45   * </ol>
46   * 
47   * @author sanjin
48   */
49  public class ActionsMethod {
50  
51      private static final int MAX_PARAMETERS = 2;
52  
53      private final Set<ProcessorAction> actions;
54      private final Object processor;
55      private final Method method;
56      private final Class<?> eventClass;
57      private final Parameters parameters;
58      private final ResultType resultType;
59  
60      /**
61       * Creates instance of invoker of specified {@link Actions} {@link Method}
62       * on specified processor.
63       * 
64       * @param processor the wrapped annotated processor.
65       * @param method the {@link Actions} {@link Method}.
66       * @throws NullPointerException if specified wrapped annotated processor or
67       *         {@link Method} is <code>null</code>.
68       * @throws IllegalArgumentException if specified {@link Method} has no
69       *         {@link Action} or {@link Actions} annotation, is not public, has
70       *         zero or more than two parameters, has no {@link ContextEvent} (or
71       *         a subclass of it) parameter, other (optional) parameter is not
72       *         {@link ProcessorAction}, or {@link Method} is not void or it
73       *         doesn't return {@link ContextEvent}, array of
74       *         {@link ContextEvent}s, or {@link Collection} of
75       *         {@link ContextEvent}s.
76       */
77      public ActionsMethod(final Object processor, final Method method) {
78          super();
79  
80          if (null == processor) {
81              throw new NullPointerException("Processor cannot be null.");
82          }
83          if (null == method) {
84              throw new NullPointerException("Method cannot be null.");
85          }
86  
87          final String methodName = method.getName();
88          final Action action = method.getAnnotation(Action.class);
89          @SuppressWarnings("hiding")
90          final Actions actions = method.getAnnotation(Actions.class);
91          if ((null == action) && (null == actions)) {
92              throw new IllegalArgumentException(MessageFormat.format(
93                      "Method ''{0}'' has no Action nor Actions annotation.",
94                      methodName));
95          }
96          if ((null != action) && (null != actions)) {
97              throw new IllegalArgumentException(MessageFormat.format(
98                      "Method ''{0}'' has both Action and Actions annotations.",
99                      methodName));
100         }
101         if (!Modifier.isPublic(method.getModifiers())) {
102             throw new IllegalArgumentException(MessageFormat.format(
103                     "Actions method ''{0}'' is not public.", methodName));
104         }
105 
106         @SuppressWarnings("hiding")
107         final Class<?>[] parameters = method.getParameterTypes();
108         if (parameters.length > MAX_PARAMETERS) {
109             throw new IllegalArgumentException(MessageFormat.format(
110                     "Actions method ''{0}'' has more than two parameters.",
111                     methodName));
112         }
113         if (0 == parameters.length) {
114             throw new IllegalArgumentException(MessageFormat.format(
115                     "Actions method ''{0}'' has no parameters.", methodName));
116         }
117         if (1 == parameters.length) {
118             if (ContextEvent.class.isAssignableFrom(parameters[0])) {
119                 this.parameters = Parameters.Event;
120             } else {
121                 throw new IllegalArgumentException(
122                         MessageFormat.format(
123                                 "Parameter of Actions method ''{0}'' is not ContextEvent or its subclass.",
124                                 methodName));
125             }
126             this.eventClass = parameters[0];
127         } else {
128             if ((!ContextEvent.class.isAssignableFrom(parameters[0]))
129                     && (!ContextEvent.class.isAssignableFrom(parameters[1]))) {
130                 throw new IllegalArgumentException(
131                         MessageFormat.format(
132                                 "Neither parameter of Actions method ''{0}'' is ContextEvent or its subclass.",
133                                 methodName));
134             }
135             if ((!ProcessorAction.class.equals(parameters[0]))
136                     && (!ProcessorAction.class.equals(parameters[1]))) {
137                 throw new IllegalArgumentException(
138                         MessageFormat.format(
139                                 "Neither parameter of Actions method ''{0}'' is ProcessorAction.",
140                                 methodName));
141             }
142             if (ContextEvent.class.isAssignableFrom(parameters[0])) {
143                 this.parameters = Parameters.EventAndAction;
144                 this.eventClass = parameters[0];
145             } else {
146                 this.parameters = Parameters.ActionAndEvent;
147                 this.eventClass = parameters[1];
148             }
149         }
150         final Class<?> methodResult = method.getReturnType();
151         if (methodResult.equals(Void.class)) {
152             this.resultType = ResultType.Void;
153         } else if (ContextEvent.class.isAssignableFrom(methodResult)) {
154             this.resultType = ResultType.Event;
155         } else if (methodResult.isArray()
156                 && ContextEvent.class.isAssignableFrom(methodResult.getComponentType())) {
157             this.resultType = ResultType.Array;
158         } else if (Collection.class.isAssignableFrom(methodResult)) {
159             this.resultType = ResultType.Collection;
160         } else {
161             throw new IllegalArgumentException(
162                     MessageFormat.format(
163                             "Result of Actions method ''{0}'' is not void, ContextEvent, or ContextEvent array or collection.",
164                             methodName));
165         }
166 
167         this.processor = processor;
168         this.method = method;
169         if (null == actions) {
170             this.actions = toProcessorActionSet(action);
171         } else {
172             this.actions = toProcessorActionSet(actions.value());
173         }
174     }
175 
176     /**
177      * Returns the name of this {@link Actions} {@link Method}.
178      * 
179      * @return the name of this {@link Actions} {@link Method}.
180      * @see Method#getName()
181      */
182     public String getName() {
183         return this.method.getName();
184     }
185 
186     /**
187      * Returns {@link ProcessorAction}s handled by this {@link Actions}
188      * {@link Method}.
189      * 
190      * @return {@link ProcessorAction}s handled by this {@link Actions}
191      *         {@link Method}.
192      * @see Actions#value()
193      * @see Action
194      */
195     public ProcessorAction[] getActions() {
196         return this.actions.toArray(new ProcessorAction[this.actions.size()]);
197     }
198 
199     /**
200      * Returns types of parameters of this {@link Actions} {@link Method}.
201      * 
202      * @return types of parameters.
203      * @see Method#getParameterTypes()
204      */
205     public Class<?>[] getParameters() {
206         return this.method.getParameterTypes();
207     }
208 
209     /**
210      * Returns type of result of this {@link Actions} {@link Method}.
211      * 
212      * @return the type of result of this {@link Actions} {@link Method}.
213      * @see Method#getReturnType()
214      */
215     public Class<?> getResult() {
216         return this.method.getReturnType();
217     }
218 
219     /**
220      * Returns if specified {@link ProcessorAction} with specified
221      * {@link ContextEvent} can be invoked with this {@link Actions}
222      * {@link Method}.
223      * 
224      * @param action the {@link ProcessorAction}.
225      * @param event the {@link ContextEvent}.
226      * @return if specified {@link ProcessorAction} with specified
227      *         {@link ContextEvent} can be invoked.
228      * @throws NullPointerException if specified {@link ContextEvent} is
229      *         <code>null</code>.
230      */
231     public boolean canBeInvokedWith(final ProcessorAction action,
232             final ContextEvent event) {
233         return this.actions.contains(action)
234                 && this.eventClass.isAssignableFrom(event.getClass());
235     }
236 
237     /**
238      * Invokes the underlying {@link Actions} {@link Method} for specified
239      * {@link ProcessorAction} with specified {@link ContextEvent}. Result of
240      * invocation (or an empty array if there was no result) is returned if
241      * specified {@link ProcessorAction} has output.
242      * 
243      * @param action the {@link ProcessorAction}.
244      * @param event the {@link ContextEvent}.
245      * @return the resulting {@link ContextEvent}s of the underlying
246      *         {@link Actions} {@link Method}.
247      * @throws IllegalAccessException if this {@link Actions} {@link Method}
248      *         enforces Java language access control and the underlying method
249      *         is inaccessible.
250      * @throws InvocationTargetException if the underlying {@link Actions}
251      *         {@link Method} throws an {@link Exception}.
252      * @throws NullPointerException if specified {@link ProcessorAction} or
253      *         {@link ContextEvent} is <code>null</code>.
254      * @throws IllegalArgumentException if specified {@link ProcessorAction}
255      *         with specified {@link ContextEvent} can not be invoked with this
256      *         {@link Actions} {@link Method} i.e. the
257      *         {@link #canBeInvokedWith(ProcessorAction, ContextEvent)} method
258      *         returns <code>false</code>.
259      */
260     public ContextEvent[] invoke(final ProcessorAction action,
261             final ContextEvent event) throws IllegalAccessException,
262             InvocationTargetException {
263         if (null == action) {
264             throw new NullPointerException("Action cannot be null.");
265         }
266         if (null == event) {
267             throw new NullPointerException("Event cannot be null.");
268         }
269 
270         if (!canBeInvokedWith(action, event)) {
271             throw new IllegalArgumentException(
272                     "Actions method cannot be invoked with specified action and event.");
273         }
274 
275         final Object result;
276         switch (this.parameters) {
277         case Event:
278             result = this.method.invoke(this.processor, event);
279             break;
280         case EventAndAction:
281             result = this.method.invoke(this.processor, event, action);
282             break;
283         case ActionAndEvent:
284             result = this.method.invoke(this.processor, action, event);
285             break;
286         default:
287             result = null;
288         }
289 
290         return toProcessResult(action, result);
291     }
292 
293     private ContextEvent[] toProcessResult(final ProcessorAction action,
294             final Object processResult) {
295         final Set<ContextEvent> result = new HashSet<ContextEvent>();
296 
297         if (action.hasOutput() && (null != processResult)) {
298             switch (this.resultType) {
299             case Event:
300                 result.add((ContextEvent) processResult);
301                 break;
302             case Array:
303                 final int length = Array.getLength(processResult);
304                 for (int i = 0; i < length; i++) {
305                     result.add((ContextEvent) processResult);
306                 }
307                 break;
308             case Collection:
309                 for (final Object element : (Collection<?>) processResult) {
310                     if (element instanceof ContextEvent) {
311                         result.add((ContextEvent) element);
312                     }
313                 }
314                 break;
315             case Void:
316             default:
317                 break;
318             }
319         }
320 
321         return result.toArray(new ContextEvent[result.size()]);
322     }
323 
324     private static Set<ProcessorAction> toProcessorActionSet(
325             final Action... actions) {
326         final Set<ProcessorAction> result = new HashSet<ProcessorAction>();
327 
328         for (final Action action : actions) {
329             result.add(toProcessorAction(action));
330         }
331 
332         return Collections.unmodifiableSet(result);
333     }
334 
335     private static ProcessorAction toProcessorAction(final Action action) {
336         final ProcessorAction result;
337 
338         final String name = action.name();
339         final String input = action.input();
340         final String[] output = action.output();
341         if ((null != output) && (output.length > 0)) {
342             result = new ProcessorAction(name, input, output);
343         } else {
344             result = new ProcessorAction(name, input);
345         }
346 
347         return result;
348     }
349 
350     private static enum Parameters {
351         Event, EventAndAction, ActionAndEvent
352     }
353 
354     private static enum ResultType {
355         Void, Event, Array, Collection
356     }
357 }