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.HashMap;
030import org.jgrapes.core.Channel;
031import org.jgrapes.core.ComponentType;
032import org.jgrapes.core.Event;
033import org.jgrapes.core.HandlerScope;
034
035/**
036 * This annotation tags some other annotation as a handler annotation. 
037 * The tagged annotation can then be used to mark a method as a handler.
038 *  
039 * Every handler definition annotation must provide an {@link Evaluator} 
040 * to allow access to the properties of the handler annotation in a 
041 * uniform way. 
042 */
043@Documented
044@Retention(RetentionPolicy.RUNTIME)
045@Target(ElementType.ANNOTATION_TYPE)
046public @interface HandlerDefinition {
047
048    /**
049     * Returns the evaluator for the annotated handler annotation.
050     * 
051     * @return the evaluator
052     */
053    Class<? extends Evaluator> evaluator();
054
055    /**
056     * This interface allows access to the properties defined by arbitrary
057     * handler annotations in a uniform way. Handler annotations
058     * must specify the scope of a handler, i.e. for which events and
059     * channels the handler should be invoked, and the priority of
060     * the handler.  
061     */
062    interface Evaluator {
063
064        /**
065         * Returns the information about the events and channels handled
066         * by the handler that annotates the given method of the given
067         * comonent as a {@link HandlerScope} object. This method
068         * is invoked during object initialization. It may return
069         * {@code null} if a handler is not supposed to be added for
070         * this method during initialization (dynamic handler,
071         * see {@link Handler#dynamic()}). 
072         *
073         * @param component the component
074         * @param method the annotated method
075         * @param channelReplacements replacements for channel classes in 
076         * the annotation's `channels` element
077         * @return the scope or {@code null} if a handler for the method
078         * should not be created
079         */
080        @SuppressWarnings("PMD.LooseCoupling")
081        HandlerScope scope(ComponentType component, Method method,
082                ChannelReplacements channelReplacements);
083
084        /**
085         * Returns the priority defined by the annotation
086         * 
087         * @param annotation the annotation
088         * @return the priority
089         */
090        int priority(Annotation annotation);
091
092        /**
093         * Utility method for checking if the method can be used as handler.
094         * 
095         * @param method the method
096         * @return the result
097         */
098        @SuppressWarnings("PMD.UselessParentheses")
099        static boolean checkMethodSignature(Method method) {
100            return method.getParameterTypes().length == 0
101                || method.getParameterTypes().length == 1
102                    && Event.class.isAssignableFrom(
103                        method.getParameterTypes()[0])
104                || (method.getParameterTypes().length == 2
105                    && Event.class.isAssignableFrom(
106                        method.getParameterTypes()[0]))
107                    && Channel.class.isAssignableFrom(
108                        method.getParameterTypes()[1]);
109        }
110    }
111
112    /**
113     * Represents channel (criteria) replacements that are to
114     * be applied to `channels` elements of {@link Handler}
115     * annotations.
116     */
117    @SuppressWarnings("serial")
118    class ChannelReplacements // NOPMD (for missing serialVersionUID)
119            extends HashMap<Class<? extends Channel>, Object[]> {
120
121        /**
122         * Create a new replacements specification object.
123         *
124         * @return the channel replacements
125         */
126        @SuppressWarnings("PMD.LooseCoupling")
127        public static ChannelReplacements create() {
128            return new ChannelReplacements();
129        }
130
131        /**
132         * Adds a replacements to the replacements.
133         *
134         * @param annotationCriterion the criterion used in the annotation
135         * @param replacements the replacements
136         * @return the channel replacements for easy chaining
137         */
138        @SuppressWarnings("PMD.LooseCoupling")
139        public ChannelReplacements add(
140                Class<? extends Channel> annotationCriterion,
141                Channel... replacements) {
142            var criteria = Arrays.stream(replacements)
143                .map(Channel::defaultCriterion).toArray(Object[]::new);
144            put(annotationCriterion, criteria);
145            return this;
146        }
147    }
148}