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        HandlerScope scope(ComponentType component, Method method,
081                ChannelReplacements channelReplacements);
082
083        /**
084         * Returns the priority defined by the annotation
085         * 
086         * @param annotation the annotation
087         * @return the priority
088         */
089        int priority(Annotation annotation);
090
091        /**
092         * Utility method for checking if the method can be used as handler.
093         * 
094         * @param method the method
095         * @return the result
096         */
097        static boolean checkMethodSignature(Method method) {
098            return method.getParameterTypes().length == 0
099                || method.getParameterTypes().length == 1
100                    && Event.class.isAssignableFrom(
101                        method.getParameterTypes()[0])
102                || (method.getParameterTypes().length == 2
103                    && Event.class.isAssignableFrom(
104                        method.getParameterTypes()[0]))
105                    && Channel.class.isAssignableFrom(
106                        method.getParameterTypes()[1]);
107        }
108    }
109
110    /**
111     * Represents channel (criteria) replacements that are to
112     * be applied to `channels` elements of {@link Handler}
113     * annotations.
114     */
115    @SuppressWarnings("serial")
116    class ChannelReplacements // NOPMD (for missing serialVersionUID)
117            extends HashMap<Class<? extends Channel>, Object[]> {
118
119        /**
120         * Create a new replacements specification object.
121         *
122         * @return the channel replacements
123         */
124        public static ChannelReplacements create() {
125            return new ChannelReplacements();
126        }
127
128        /**
129         * Adds a replacements to the replacements.
130         *
131         * @param annotationCriterion the criterion used in the annotation
132         * @param replacements the replacements
133         * @return the channel replacements for easy chaining
134         */
135        public ChannelReplacements add(
136                Class<? extends Channel> annotationCriterion,
137                Channel... replacements) {
138            var criteria = Arrays.stream(replacements)
139                .map(Channel::defaultCriterion).toArray(Object[]::new);
140            put(annotationCriterion, criteria);
141            return this;
142        }
143    }
144}