All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fujion.annotation.EventHandlerScanner Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * #%L
 * fujion
 * %%
 * Copyright (C) 2008 - 2017 Regenstrief Institute, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * #L%
 */
package org.fujion.annotation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.reflect.FieldUtils;
import org.fujion.common.MiscUtil;
import org.fujion.component.BaseComponent;
import org.fujion.event.Event;
import org.fujion.event.IEventListener;

/**
 * Wires {@literal @EventHandler}-annotated methods.
 */
public class EventHandlerScanner {

    /**
     * Event listener implementation that invokes an {@literal @EventHandler}-annotated method on a
     * target object.
     */
    private static class EventListener implements IEventListener {

        private final Object target;

        private final Method method;

        EventListener(Object target, Method method) {
            this.target = target;
            this.method = method;
        }

        /**
         * Pass an event to the handler method on the target.
         */
        @Override
        public void onEvent(Event event) {
            try {
                if (method.getParameterTypes().length == 0) {
                    method.invoke(target);
                } else {
                    method.invoke(target, event);
                }
            } catch (Throwable e) {
                throw MiscUtil.toUnchecked(e);
            }
        }

        @Override
        public boolean equals(Object object) {
            if (object == this) {
                return true;
            }
            
            if (!(object instanceof EventListener)) {
                return false;
            }
            
            EventListener el = (EventListener) object;
            return target == el.target && method.equals(el.method);
        }

        @Override
        public int hashCode() {
            return target.hashCode() ^ method.hashCode();
        }
    }

    /**
     * Recursively scans the controller's class and superclasses for {@literal @EventHandler}
     * -annotated methods.
     *
     * @param instance Controller to be wired.
     * @param root The root component used to resolve component names.
     */
    public static void wire(Object instance, BaseComponent root) {
        Class clazz = instance.getClass();

        while (clazz != Object.class) {
            wire(instance, root, clazz);
            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Scans the specified class for {@literal @EventHandler}-annotated methods.
     *
     * @param instance Controller to be wired.
     * @param root The root component used to resolve component names.
     * @param clazz The class to be scanned.
     */
    private static void wire(Object instance, BaseComponent root, Class clazz) {
        for (Method method : clazz.getDeclaredMethods()) {
            method.setAccessible(true);
            EventHandler[] annotations = method.getAnnotationsByType(EventHandler.class);

            for (EventHandler annot : annotations) {

                OnFailure onFailure = annot.onFailure();
                Class[] params = method.getParameterTypes();

                if (params.length > 1 || (params.length == 1 && !Event.class.isAssignableFrom(params[0]))) {
                    onFailure.doAction(
                        "Method " + method.getName() + " signature does not conform to that of an event handler.");
                    return;
                }

                Set targets = asSet(annot.target());
                Set types = asSet(annot.value());
                BaseComponent component = null;

                if (types.isEmpty()) {
                    onFailure.doAction("At least one event type must be specified");
                }

                if (targets.isEmpty()) {
                    targets.add("self");
                }

                for (String target : targets) {
                    if ("self".equals(target)) {
                        component = isComponent(clazz) ? (BaseComponent) instance : root;
                    } else if (target.startsWith("@")) {
                        Field field = findField(clazz, target.substring(1));

                        if (field != null) {
                            try {
                                field.setAccessible(true);
                                component = (BaseComponent) field.get(instance);
                            } catch (Exception e) {
                                // what to do here
                            }
                        }
                    } else {
                        component = root == null ? null : root.findByName(target);
                    }

                    if (component == null) {
                        onFailure.doAction("No suitable event handler target found for \"" + target + "\"");
                    } else {
                        for (String type : types) {
                            component.addEventListener(type, new EventListener(instance, method), annot.syncToClient());
                        }
                    }
                }
            }
        }
    }

    /**
     * Converts an array of string values to a set. This effectively removes duplicate and empty
     * entries.
     *
     * @param values Array of string values.
     * @return Corresponding set of values.
     */
    private static Set asSet(String[] values) {
        Set set = new HashSet<>();

        for (String value : values) {
            if (!value.isEmpty()) {
                set.add(value);
            }
        }

        return set;
    }

    /**
     * Returns true if the class is a component class.
     *
     * @param clazz Class to be examined.
     * @return True if the class is a component class.
     */
    private static boolean isComponent(Class clazz) {
        return BaseComponent.class.isAssignableFrom(clazz);
    }

    /**
     * Returns the field definition for the named field. Field must be a component type.
     *
     * @param clazz Class to be searched.
     * @param name Name of the field.
     * @return The corresponding field definition, or null if no matching field was found.
     */
    private static Field findField(Class clazz, String name) {
        try {
            Field result = FieldUtils.getField(clazz, name, true);
            return isComponent(result.getType()) ? result : null;
        } catch (Exception e) {
            return null;
        }
    }

    private EventHandlerScanner() {
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy