001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.core.annotation;
020
021import java.lang.annotation.Annotation;
022import java.lang.annotation.Documented;
023import java.lang.annotation.ElementType;
024import java.lang.annotation.Retention;
025import java.lang.annotation.RetentionPolicy;
026import java.lang.annotation.Target;
027import java.lang.reflect.Method;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.Map;
032import java.util.Set;
033import java.util.stream.Collectors;
034
035import org.jgrapes.core.Channel;
036import org.jgrapes.core.Channel.Default;
037import org.jgrapes.core.ClassChannel;
038import org.jgrapes.core.Component;
039import org.jgrapes.core.ComponentType;
040import org.jgrapes.core.Components;
041import org.jgrapes.core.Eligible;
042import org.jgrapes.core.Event;
043import org.jgrapes.core.HandlerScope;
044import org.jgrapes.core.Manager;
045import org.jgrapes.core.NamedChannel;
046import org.jgrapes.core.NamedEvent;
047import org.jgrapes.core.Self;
048import org.jgrapes.core.annotation.HandlerDefinition.ChannelReplacements;
049
050/**
051 * This is the basic, general purpose handler annotation provided as part of the
052 * core package.
053 * 
054 * The annotated method is invoked for events that have a type (or
055 * name) matching the given `events` (or `namedEvents`) element 
056 * of the annotation and that are fired on one of
057 * the `channels` (or `namedChannels`) specified in the annotation.
058 * 
059 * If neither event classes nor named events are specified in the 
060 * annotation, the class of the annotated method's first parameter (which 
061 * must be of type {@link Event} or a derived type) is used as (single)
062 * event class (see the examples in {@link #events()} and 
063 * {@link #namedEvents()}).
064 * 
065 * Channel matching is performed by matching the event's channels
066 * (see {@link Event#channels()}) with the channels specified in the
067 * handler. The matching algorithm invokes
068 * {@link Eligible#isEligibleFor(Object) isEligibleFor} for each of the 
069 * event's channels with the class (or name, see {@link #channels()} and 
070 * {@link Handler#namedChannels()}) of each of the channels specified
071 * in the handler.
072 * 
073 * If neither channel classes not named channels are specified in the 
074 * handler, or `{@link Default Channel.Default}.class` is specified as one 
075 * of the channel classes, the matching algorithm invokes
076 * {@link Eligible#isEligibleFor(Object) isEligibleFor} for each of
077 * the event's channels with the default criterion of the component's 
078 * channel (see {@link Manager#channel()} and 
079 * {@link Eligible#defaultCriterion()}) as argument.
080 *
081 * Finally, independent of any specified channels, the matching algorithm 
082 * invokes {@link Eligible#isEligibleFor(Object) isEligibleFor} 
083 * for each of the event's channels with the component's default criterion
084 * as argument. This results in a match if
085 * the component itself is used as one of the event's channels
086 * (see the description of {@link Eligible}).
087 * 
088 * If a match is found for a given event's properties and a handler's
089 * specified attributes, the handler method is invoked. 
090 * The method can have an additional optional parameter of type
091 * {@link Channel} (or a derived type). This parameter does not 
092 * influence the eligibility of the method regarding a given event,
093 * it determines how the method is invoked. If the method does not
094 * have a second parameter, it is invoked once if an event 
095 * matches. If the parameter exists, the method is invoked once for
096 * each of the event's channels, provided that the optional parameter's
097 * type is assignable from the event's channel.
098 * 
099 * Because annotation elements accept only literals as values, they
100 * cannot be used to register handlers with properties that are only
101 * known at runtime. It is therefore possible to specify a 
102 * {@link Handler} annotation with element `dynamic=true`. Such a 
103 * handler must be added explicitly by invoking 
104 * {@link Evaluator#add(ComponentType, String, Object)} or
105 * {@link Evaluator#add(ComponentType, String, Object, Object, int)},
106 * thus specifying some of the handler's properties dynamically (i.e.
107 * at runtime).
108 * 
109 * A special case is the usage of a channel that is only known at
110 * runtime. If there are several handlers for events on such a
111 * channel, a lot of methods will become dynamic. To avoid this,
112 * {@link Component}s support a {@link ChannelReplacements}
113 * parameter in their constructor. Using this, it is possible
114 * to specify a specially defined {@link Channel} class in the
115 * annotation that is replaced by a channel that is only known
116 * at runtime.
117 * 
118 * @see Component#channel()
119 */
120@Documented
121@Retention(RetentionPolicy.RUNTIME)
122@Target(ElementType.METHOD)
123@HandlerDefinition(evaluator = Handler.Evaluator.class)
124public @interface Handler {
125
126    /** The default value for the <code>events</code> parameter of
127     * the annotation. Indicates that the parameter is not used. */
128    final class NoEvent extends Event<Void> {
129    }
130
131    /** The default value for the <code>channels</code> parameter of
132     * the annotation. Indicates that the parameter is not used. */
133    final class NoChannel extends ClassChannel {
134    }
135
136    /**
137     * Specifies classes of events that the handler is to receive.
138     * 
139     * ```java
140     * class SampleComponent extends Component {
141     * 
142     *    {@literal @}Handler
143     *     public void onStart(Start event) {
144     *         // Invoked for Start events on the component's channel,
145     *         // event object made available
146     *     }
147     * 
148     *    {@literal @}Handler(events=Start.class)
149     *     public void onStart() {
150     *         // Invoked for Start events on the component's channel,
151     *         // not interested in the event object
152     *     }
153     * 
154     *    {@literal @}Handler(events={Start.class, Stop.class})
155     *     public void onStart(Event<?> event) {
156     *         // Invoked for Start and Stop events on the component's
157     *         // channel, event made available (may need casting to 
158     *         // access specific properties) 
159     *     }
160     * }
161     * ```
162     * 
163     * @return the event classes
164     */
165    @SuppressWarnings("rawtypes")
166    Class<? extends Event>[] events() default NoEvent.class;
167
168    /**
169     * Specifies names of {@link NamedEvent}s that the handler is to receive.
170     * 
171     * ```java
172     * class SampleComponent extends Component {
173     * 
174     *    {@literal @}Handler(namedEvents="Test")
175     *     public void onTest(Event<?> event) {
176     *         // Invoked for (named) "Test" events (new NamedEvent("Test")) 
177     *         // on the component's channel, event object made available
178     *     }
179     * }
180     * ```
181     * 
182     * @return the event names
183     */
184    String[] namedEvents() default "";
185
186    /**
187     * Specifies classes of channels that the handler listens on. If none
188     * are specified, the component's channel is used.
189     * 
190     * ```java
191     * class SampleComponent extends Component {
192     * 
193     *    {@literal @}Handler(channels=Feedback.class)
194     *     public void onStart(Start event) {
195     *         // Invoked for Start events on the "Feedback" channel
196     *         // (class Feedback implements Channel {...}),
197     *         // event object made available
198     *     }
199     * }
200     * ```
201     * 
202     * Specifying `channels=Channel.class` make the handler listen
203     * for all events, independent of the channel that they are fired on.
204     * 
205     * Specifying `channels=Self.class` make the handler listen
206     * for events that are fired on the conponent.
207     * 
208     * @return the channel classes
209     */
210    Class<? extends Channel>[] channels() default NoChannel.class;
211
212    /**
213     * Specifies names of {@link NamedChannel}s that the handler listens on.
214     * 
215     * ```java
216     * class SampleComponent extends Component {
217     * 
218     *    {@literal @}Handler(namedChannels="Feedback")
219     *     public void onStart(Start event) {
220     *         // Invoked for Start events on the (named) channel "Feedback"
221     *         // (new NamedChannel("Feedback")), event object made available
222     *     }
223     * }
224     * ```
225     * 
226     * @return the channel names
227     */
228    String[] namedChannels() default "";
229
230    /**
231     * Specifies a priority. The value is used to sort handlers.
232     * Handlers with higher priority are invoked first.
233     * 
234     * @return the priority
235     */
236    int priority() default 0;
237
238    /**
239     * Returns {@code true} if the annotated method defines a
240     * dynamic handler. A dynamic handler must be added to the set of
241     * handlers of a component explicitly at run time using
242     * {@link Evaluator#add(ComponentType, String, Object)}
243     * or {@link Evaluator#add(ComponentType, String, Object, Object, int)}.
244     * 
245     * ```java
246     * class SampleComponent extends Component {
247     * 
248     *     SampleComponent() {
249     *         Handler.Evaluator.add(this, "onStartDynamic", someChannel);
250     *     }
251     * 
252     *    {@literal @}Handler(dynamic=true)
253     *     public void onStartDynamic(Start event) {
254     *         // Only invoked if added as handler at runtime
255     *     }
256     * }
257     * ```
258     * 
259     * @return the result
260     */
261    boolean dynamic() default false;
262
263    /**
264     * This class provides the {@link Evaluator} for the 
265     * {@link Handler} annotation provided by the core package. It 
266     * implements the behavior as described for the annotation. 
267     */
268    class Evaluator implements HandlerDefinition.Evaluator {
269
270        @Override
271        public HandlerScope scope(
272                ComponentType component, Method method,
273                ChannelReplacements channelReplacements) {
274            Handler annotation = method.getAnnotation(Handler.class);
275            if (annotation == null || annotation.dynamic()) {
276                return null;
277            }
278            return new Scope(component, method, annotation,
279                channelReplacements, null, null);
280        }
281
282        /*
283         * (non-Javadoc)
284         * 
285         * @see
286         * org.jgrapes.core.annotation.HandlerDefinition.Evaluator#getPriority()
287         */
288        @Override
289        public int priority(Annotation annotation) {
290            return ((Handler) annotation).priority();
291        }
292
293        /**
294         * Adds the given method of the given component as a dynamic handler for
295         * a specific event and channel. The method with the given name must be
296         * annotated as dynamic handler and must have a single parameter of type
297         * {@link Event} (or a derived type as appropriate for the event type to
298         * be handled). It can have an optional parameter of type 
299         * {@link Channel}.
300         * 
301         * @param component
302         *            the component
303         * @param method
304         *            the name of the method that implements the handler
305         * @param eventValue
306         *            the event key that should be used for matching this
307         *            handler with an event. This is equivalent to an
308         *            <code>events</code>/<code>namedEvents</code> parameter
309         *            used with a single value in the handler annotation, but
310         *            here all kinds of Objects are allowed as key values.
311         * @param channelValue
312         *            the channel value that should be used for matching 
313         *            an event's channel with this handler. This is equivalent 
314         *            to a `channels`/`namedChannels` parameter with a single
315         *            value in the handler annotation, but
316         *            here all kinds of Objects are allowed as values. As a
317         *            convenience, if the actual object provided is a
318         *            {@link Channel}, its default criterion is used for 
319         *            matching.
320         * @param priority
321         *            the priority of the handler
322         */
323        public static void add(ComponentType component, String method,
324                Object eventValue, Object channelValue, int priority) {
325            addInternal(component, method, eventValue, channelValue, priority);
326        }
327
328        /**
329         * Add a handler like 
330         * {@link #add(ComponentType, String, Object, Object, int)}
331         * but take the values for event and priority from the annotation.
332         * 
333         * @param component the component
334         * @param method the name of the method that implements the handler
335         * @param channelValue the channel value that should be used 
336         * for matching an event's channel with this handler
337         */
338        public static void add(ComponentType component, String method,
339                Object channelValue) {
340            addInternal(component, method, null, channelValue, null);
341        }
342
343        @SuppressWarnings({ "PMD.CyclomaticComplexity",
344            "PMD.AvoidBranchingStatementAsLastInLoop" })
345        private static void addInternal(ComponentType component, String method,
346                Object eventValue, Object channelValue, Integer priority) {
347            try {
348                if (channelValue instanceof Channel) {
349                    channelValue = ((Eligible) channelValue).defaultCriterion();
350                }
351                for (Method m : component.getClass().getMethods()) {
352                    if (!m.getName().equals(method)) {
353                        continue;
354                    }
355                    for (Annotation annotation : m.getDeclaredAnnotations()) {
356                        Class<?> annoType = annotation.annotationType();
357                        if (!(annoType.equals(Handler.class))) {
358                            continue;
359                        }
360                        HandlerDefinition hda
361                            = annoType.getAnnotation(HandlerDefinition.class);
362                        if (hda == null
363                            || !Handler.class.isAssignableFrom(annoType)
364                            || !((Handler) annotation).dynamic()) {
365                            continue;
366                        }
367                        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
368                        Scope scope = new Scope(component, m,
369                            (Handler) annotation, Collections.emptyMap(),
370                            eventValue == null ? null
371                                : new Object[] { eventValue },
372                            new Object[] { channelValue });
373                        Components.manager(component).addHandler(m, scope,
374                            priority == null
375                                ? ((Handler) annotation).priority()
376                                : priority);
377                        return;
378                    }
379                }
380                throw new IllegalArgumentException(
381                    "No method named \"" + method + "\" with DynamicHandler"
382                        + " annotation and correct parameter list.");
383            } catch (SecurityException e) {
384                throw (RuntimeException) (new IllegalArgumentException()
385                    .initCause(e));
386            }
387        }
388
389        /**
390         * The handler scope implementation used by the evaluator.
391         */
392        private static class Scope implements HandlerScope {
393
394            private final Set<Object> eventCriteria = new HashSet<>();
395            private final Set<Object> channelCriteria = new HashSet<>();
396
397            /**
398             * Instantiates a new scope.
399             *
400             * @param component the component
401             * @param method the method
402             * @param annotation the annotation
403             * @param channelReplacements the channel replacements
404             * @param eventValues the event values
405             * @param channelValues the channel values
406             */
407            @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount",
408                "PMD.NPathComplexity", "PMD.UseVarargs",
409                "PMD.AvoidDeeplyNestedIfStmts",
410                "PMD.CollapsibleIfStatements" })
411            public Scope(ComponentType component, Method method,
412                    Handler annotation,
413                    Map<Class<? extends Channel>, Object> channelReplacements,
414                    Object[] eventValues, Object[] channelValues) {
415                if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) {
416                    throw new IllegalArgumentException("Method \""
417                        + method.toString() + "\" cannot be used as"
418                        + " handler (wrong signature).");
419                }
420                if (eventValues != null) { // NOPMD, != is easier to read
421                    eventCriteria.addAll(Arrays.asList(eventValues));
422                } else {
423                    // Get all event values from the handler annotation.
424                    if (annotation.events()[0] != Handler.NoEvent.class) {
425                        eventCriteria
426                            .addAll(Arrays.asList(annotation.events()));
427                    }
428                    // Get all named events from the annotation and add to event
429                    // keys.
430                    if (!annotation.namedEvents()[0].equals("")) {
431                        eventCriteria.addAll(
432                            Arrays.asList(annotation.namedEvents()));
433                    }
434                    // If no event types are given, try first parameter.
435                    if (eventCriteria.isEmpty()) {
436                        Class<?>[] paramTypes = method.getParameterTypes();
437                        if (paramTypes.length > 0) {
438                            if (Event.class.isAssignableFrom(paramTypes[0])) {
439                                eventCriteria.add(paramTypes[0]);
440                            }
441                        }
442                    }
443                }
444
445                if (channelValues != null) { // NOPMD, != is easier to read
446                    channelCriteria.addAll(Arrays.asList(channelValues));
447                } else {
448                    // Get channel values from the annotation.
449                    boolean addDefaultChannel = false;
450                    if (annotation.channels()[0] != Handler.NoChannel.class) {
451                        for (Class<?> c : annotation.channels()) {
452                            if (c == Self.class) {
453                                if (!(component instanceof Channel)) {
454                                    throw new IllegalArgumentException(
455                                        "Canot use channel This.class in "
456                                            + "annotation of " + method
457                                            + " because " + getClass().getName()
458                                            + " does not implement Channel.");
459                                }
460                                // Will be added anyway, see below, but
461                                // channelCriteria must not remain empty,
462                                // else the default channel is added.
463                                channelCriteria.add(
464                                    ((Channel) component).defaultCriterion());
465                            } else if (c == Channel.Default.class) {
466                                addDefaultChannel = true;
467                            } else {
468                                channelCriteria.add(channelReplacements == null
469                                    ? c
470                                    : channelReplacements.getOrDefault(c, c));
471                            }
472                        }
473                    }
474                    // Get named channels from annotation and add to channel
475                    // keys.
476                    if (!annotation.namedChannels()[0].equals("")) {
477                        channelCriteria.addAll(
478                            Arrays.asList(annotation.namedChannels()));
479                    }
480                    if (channelCriteria.isEmpty() || addDefaultChannel) {
481                        channelCriteria.add(Components.manager(component)
482                            .channel().defaultCriterion());
483                    }
484                }
485                // Finally, a component always handles events
486                // directed at it directly.
487                if (component instanceof Channel) {
488                    channelCriteria.add(
489                        ((Channel) component).defaultCriterion());
490                }
491
492            }
493
494            @Override
495            public boolean includes(Eligible event, Eligible[] channels) {
496                for (Object eventValue : eventCriteria) {
497                    if (event.isEligibleFor(eventValue)) {
498                        // Found match regarding event, now try channels
499                        for (Eligible channel : channels) {
500                            for (Object channelValue : channelCriteria) {
501                                if (channel.isEligibleFor(channelValue)) {
502                                    return true;
503                                }
504                            }
505                        }
506                        return false;
507                    }
508                }
509                return false;
510            }
511
512            /*
513             * (non-Javadoc)
514             * 
515             * @see java.lang.Object#toString()
516             */
517            @Override
518            public String toString() {
519                StringBuilder builder = new StringBuilder();
520                builder.append("Scope [");
521                if (eventCriteria != null) {
522                    builder.append("handledEvents=")
523                        .append(eventCriteria.stream().map(crit -> {
524                            if (crit instanceof Class) {
525                                return Components.className((Class<?>) crit);
526                            }
527                            return crit.toString();
528                        }).collect(Collectors.toSet()));
529                    builder.append(", ");
530                }
531                if (channelCriteria != null) {
532                    builder.append("handledChannels=");
533                    builder.append(channelCriteria);
534                }
535                builder.append(']');
536                return builder.toString();
537            }
538
539        }
540    }
541}