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. This results in a match if
084 * 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 hanler 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     * This class provides the {@link Evaluator} for the 
270     * {@link Handler} annotation provided by the core package. It 
271     * implements the behavior as described for the annotation. 
272     */
273    class Evaluator implements HandlerDefinition.Evaluator {
274
275        @Override
276        public HandlerScope scope(
277                ComponentType component, Method method,
278                ChannelReplacements channelReplacements) {
279            Handler annotation = method.getAnnotation(Handler.class);
280            if (annotation == null || annotation.dynamic()) {
281                return null;
282            }
283            return new Scope(component, method, annotation,
284                channelReplacements, null, null);
285        }
286
287        /*
288         * (non-Javadoc)
289         * 
290         * @see
291         * org.jgrapes.core.annotation.HandlerDefinition.Evaluator#getPriority()
292         */
293        @Override
294        public int priority(Annotation annotation) {
295            return ((Handler) annotation).priority();
296        }
297
298        /**
299         * Adds the given method of the given component as a dynamic handler for
300         * a specific event and channel. The method with the given name must be
301         * annotated as dynamic handler and must have a single parameter of type
302         * {@link Event} (or a derived type as appropriate for the event type to
303         * be handled). It can have an optional parameter of type 
304         * {@link Channel}.
305         * 
306         * @param component
307         *            the component
308         * @param method
309         *            the name of the method that implements the handler
310         * @param eventValue
311         *            the event key that should be used for matching this
312         *            handler with an event. This is equivalent to an
313         *            <code>events</code>/<code>namedEvents</code> parameter
314         *            used with a single value in the handler annotation, but
315         *            here all kinds of Objects are allowed as key values.
316         * @param channelValue
317         *            the channel value that should be used for matching 
318         *            an event's channel with this handler. This is equivalent 
319         *            to a `channels`/`namedChannels` parameter with a single
320         *            value in the handler annotation, but
321         *            here all kinds of Objects are allowed as values. As a
322         *            convenience, if the actual object provided is a
323         *            {@link Channel}, its default criterion is used for 
324         *            matching.
325         * @param priority
326         *            the priority of the handler
327         */
328        public static void add(ComponentType component, String method,
329                Object eventValue, Object channelValue, int priority) {
330            addInternal(component, method, eventValue, channelValue, priority);
331        }
332
333        /**
334         * Add a handler like 
335         * {@link #add(ComponentType, String, Object, Object, int)}
336         * but take the values for event and priority from the annotation.
337         * 
338         * @param component the component
339         * @param method the name of the method that implements the handler
340         * @param channelValue the channel value that should be used 
341         * for matching an event's channel with this handler
342         */
343        public static void add(ComponentType component, String method,
344                Object channelValue) {
345            addInternal(component, method, null, channelValue, null);
346        }
347
348        @SuppressWarnings({ "PMD.CyclomaticComplexity",
349            "PMD.AvoidBranchingStatementAsLastInLoop",
350            "PMD.CognitiveComplexity" })
351        private static void addInternal(ComponentType component, String method,
352                Object eventValue, Object channelValue, Integer priority) {
353            try {
354                if (channelValue instanceof Channel) {
355                    channelValue = ((Eligible) channelValue).defaultCriterion();
356                }
357                for (Method m : component.getClass().getMethods()) {
358                    if (!m.getName().equals(method)) {
359                        continue;
360                    }
361                    for (Annotation annotation : m.getDeclaredAnnotations()) {
362                        Class<?> annoType = annotation.annotationType();
363                        if (!(annoType.equals(Handler.class))) {
364                            continue;
365                        }
366                        HandlerDefinition hda
367                            = annoType.getAnnotation(HandlerDefinition.class);
368                        if (hda == null
369                            || !Handler.class.isAssignableFrom(annoType)
370                            || !((Handler) annotation).dynamic()) {
371                            continue;
372                        }
373                        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
374                        Scope scope = new Scope(component, m,
375                            (Handler) annotation, Collections.emptyMap(),
376                            eventValue == null ? null
377                                : new Object[] { eventValue },
378                            new Object[] { channelValue });
379                        Components.manager(component).addHandler(m, scope,
380                            priority == null
381                                ? ((Handler) annotation).priority()
382                                : priority);
383                        return;
384                    }
385                }
386                throw new IllegalArgumentException(
387                    "No method named \"" + method + "\" with DynamicHandler"
388                        + " annotation and correct parameter list.");
389            } catch (SecurityException e) {
390                throw (RuntimeException) (new IllegalArgumentException()
391                    .initCause(e));
392            }
393        }
394
395        /**
396         * The handler scope implementation used by the evaluator.
397         */
398        private static class Scope implements HandlerScope {
399
400            private final Set<Object> eventCriteria = new HashSet<>();
401            private final Set<Object> channelCriteria = new HashSet<>();
402
403            /**
404             * Instantiates a new scope.
405             *
406             * @param component the component
407             * @param method the method
408             * @param annotation the annotation
409             * @param channelReplacements the channel replacements
410             * @param eventValues the event values
411             * @param channelValues the channel values
412             */
413            @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount",
414                "PMD.NPathComplexity", "PMD.UseVarargs",
415                "PMD.AvoidDeeplyNestedIfStmts", "PMD.CollapsibleIfStatements",
416                "PMD.CognitiveComplexity" })
417            public Scope(ComponentType component, Method method,
418                    Handler annotation,
419                    Map<Class<? extends Channel>, Object> channelReplacements,
420                    Object[] eventValues, Object[] channelValues) {
421                if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) {
422                    throw new IllegalArgumentException("Method \""
423                        + method.toString() + "\" cannot be used as"
424                        + " handler (wrong signature).");
425                }
426                if (eventValues != null) { // NOPMD, != is easier to read
427                    eventCriteria.addAll(Arrays.asList(eventValues));
428                } else {
429                    // Get all event values from the handler annotation.
430                    if (annotation.events()[0] != Handler.NoEvent.class) {
431                        eventCriteria
432                            .addAll(Arrays.asList(annotation.events()));
433                    }
434                    // Get all named events from the annotation and add to event
435                    // keys.
436                    if (!annotation.namedEvents()[0].isEmpty()) {
437                        eventCriteria.addAll(
438                            Arrays.asList(annotation.namedEvents()));
439                    }
440                    // If no event types are given, try first parameter.
441                    if (eventCriteria.isEmpty()) {
442                        Class<?>[] paramTypes = method.getParameterTypes();
443                        if (paramTypes.length > 0) {
444                            if (Event.class.isAssignableFrom(paramTypes[0])) {
445                                eventCriteria.add(paramTypes[0]);
446                            }
447                        }
448                    }
449                }
450
451                if (channelValues != null) { // NOPMD, != is easier to read
452                    channelCriteria.addAll(Arrays.asList(channelValues));
453                } else {
454                    // Get channel values from the annotation.
455                    boolean addDefaultChannel = false;
456                    if (annotation.channels()[0] != Handler.NoChannel.class) {
457                        for (Class<?> c : annotation.channels()) {
458                            if (c == Self.class) {
459                                if (!(component instanceof Channel)) {
460                                    throw new IllegalArgumentException(
461                                        "Canot use channel This.class in "
462                                            + "annotation of " + method
463                                            + " because " + getClass().getName()
464                                            + " does not implement Channel.");
465                                }
466                                // Will be added anyway, see below, but
467                                // channelCriteria must not remain empty,
468                                // else the default channel is added.
469                                channelCriteria.add(
470                                    ((Channel) component).defaultCriterion());
471                            } else if (c == Channel.Default.class) {
472                                addDefaultChannel = true;
473                            } else {
474                                channelCriteria.add(channelReplacements == null
475                                    ? c
476                                    : channelReplacements.getOrDefault(c, c));
477                            }
478                        }
479                    }
480                    // Get named channels from annotation and add to channel
481                    // keys.
482                    if (!annotation.namedChannels()[0].isEmpty()) {
483                        channelCriteria.addAll(
484                            Arrays.asList(annotation.namedChannels()));
485                    }
486                    if (channelCriteria.isEmpty() || addDefaultChannel) {
487                        channelCriteria.add(Components.manager(component)
488                            .channel().defaultCriterion());
489                    }
490                }
491                // Finally, a component always handles events
492                // directed at it directly.
493                if (component instanceof Channel) {
494                    channelCriteria.add(
495                        ((Channel) component).defaultCriterion());
496                }
497
498            }
499
500            @Override
501            @SuppressWarnings("PMD.CognitiveComplexity")
502            public boolean includes(Eligible event, Eligible[] channels) {
503                for (Object eventValue : eventCriteria) {
504                    if (event.isEligibleFor(eventValue)) {
505                        // Found match regarding event, now try channels
506                        for (Eligible channel : channels) {
507                            for (Object channelValue : channelCriteria) {
508                                if (channel.isEligibleFor(channelValue)) {
509                                    return true;
510                                }
511                            }
512                        }
513                        return false;
514                    }
515                }
516                return false;
517            }
518
519            /*
520             * (non-Javadoc)
521             * 
522             * @see java.lang.Object#toString()
523             */
524            @Override
525            public String toString() {
526                StringBuilder builder = new StringBuilder();
527                builder.append("Scope [");
528                if (eventCriteria != null) {
529                    builder.append("handledEvents=")
530                        .append(eventCriteria.stream().map(crit -> {
531                            if (crit instanceof Class) {
532                                return Components.className((Class<?>) crit);
533                            }
534                            return crit.toString();
535                        }).collect(Collectors.toSet()));
536                    builder.append(", ");
537                }
538                if (channelCriteria != null) {
539                    builder.append("handledChannels=");
540                    builder.append(channelCriteria);
541                }
542                builder.append(']');
543                return builder.toString();
544            }
545
546        }
547    }
548}