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