moe.encode.airblock.commands.core.components.ComponentBag.java Source code

Java tutorial

Introduction

Here is the source code for moe.encode.airblock.commands.core.components.ComponentBag.java

Source

/*
 * AirBlock - Framework for Multi-Platform Minecraft-Plugins.
 * Copyright (C) 2014, 2015 stux!
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package moe.encode.airblock.commands.core.components;

import lombok.NonNull;
import moe.encode.airblock.commands.core.backend.Handle;
import moe.encode.airblock.commands.core.backend.HandleWrapper;
import moe.encode.airblock.commands.core.settings.Environment;
import org.apache.commons.lang3.ArrayUtils;

import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>Holder for components.</p>
 * <p>
 *     Makes sure that the given wrapper has the given component and will use
 *     the default implementations provided by the handle object when no
 *     other component defined their own implementation of the interface.
 * </p>
 */
public class ComponentBag {

    /**
     * The registered method.
     */
    private Map<Class<?>, Object> methods = new HashMap<Class<?>, Object>();

    /**
     * Registers the new component.
     * @param o The object to register.
     */
    public void registerComponent(@NonNull Object o) {
        this.registerComponent(o.getClass(), o);
    }

    /**
     * Register the implemented interfaces of the superclass.
     * @param cls The type of the object.
     * @param obj The object to register.
     */
    private void registerComponent(Class<?> cls, Object obj) {
        Components components = cls.getAnnotation(Components.class);
        if (components == null)
            return;

        for (Class<?> interfaceCls : components.value()) {
            if (!this.methods.containsKey(interfaceCls))
                this.methods.put(interfaceCls, obj);
        }

        this.registerComponent(cls.getSuperclass(), obj);
    }

    /**
     * Checks if the given interface is implemented by the component holder
     * @param interfaceCls The interface that should be implemented.
     * @return {@code true} if the given interface has been implemented.
     */
    public boolean isImplemented(@NonNull Class<?> interfaceCls, @NonNull HandleWrapper<?> wrapper) {
        return interfaceCls.isInstance(wrapper.getHandle()) || this.methods.containsKey(interfaceCls);
    }

    /**
     * <p>Calls the component method.</p>
     * <p>
     *     Please note that external components are prioritized as they may implement better
     *     suited algorithms for the actual plugin.
     * </p>
     * @param cls     The interface class that is implemented.
     * @param method  The method.
     * @param handle  The handle.
     * @param objects The parameters.
     * @param <T> The types.
     * @return The result of the call.
     */
    @Nullable
    public <T> T call(@NonNull Class<?> cls, @NonNull Method method, @NonNull HandleWrapper<?> handle,
            Object... objects) throws Throwable {
        Handle<?> hObj = handle.getHandle();
        if (this.methods.containsKey(cls))
            return this.callExternal(cls, method, handle.getEnvironment(), hObj, objects);
        else if (cls.isInstance(hObj))
            return this.callInternal(method, handle.getEnvironment(), hObj, objects);
        else
            throw new UnsupportedOperationException("Interface has not been implemented.");
    }

    /**
     * Calls the internal implementation of the method.
     * @param method       The methods of the call.
     * @param environment  The environment that is used by the command.
     * @param handle       The handle that was called.
     * @param parameters   The parameters that should be passed to the function.
     * @param <T> The type of the implementation.
     * @return The result of the function.
     * @throws Throwable If the method throws an exception.
     */
    <T> T callInternal(Method method, Environment environment, Handle<?> handle, Object... parameters)
            throws Throwable {
        Method aMethod;
        try {
            aMethod = handle.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw new UnsupportedOperationException("The implementation does not implement the called method.");
        }
        return this.invoke(aMethod, environment, handle, parameters);
    }

    /**
     * Calls the actual component method.
     * @param cls          The type that should be searched.
     * @param method       The methods of the call.
     * @param environment  The environment that is used by the command.
     * @param handle       The handle that was called.
     * @param parameters   The parameters that should be passed to the function.
     * @param <T> The type of the implementation.
     * @return The result of the function.
     * @throws Throwable If the method throws an exception.
     */
    <T> T callExternal(Class<?> cls, Method method, Environment environment, Handle<?> handle, Object... parameters)
            throws Throwable {
        Object container = this.methods.get(cls);
        if (container == null)
            throw new UnsupportedOperationException("Unsupported interface: " + cls.getSimpleName());

        Method aMethod = this.getMethod(container.getClass(), method, handle);
        if (aMethod == null) {
            throw new UnsupportedOperationException(
                    "The implementation of the interface does not implement the called method.");
        }

        return this.invoke(aMethod, environment, container, ArrayUtils.add(parameters, 0, handle));
    }

    /**
     * Returns the correct method for the implementation.
     *
     * @param cls     The class which method should be searched.
     * @param method  The method that should be found.
     * @param handle  The handle that is affected by the holder.
     * @return The method or {@code null} if the method was not found.
     */
    @SuppressWarnings("unchecked")
    Method getMethod(Class<?> cls, Method method, Handle<?> handle) {
        Class<?> current = cls;
        while (current.getAnnotation(Components.class) != null) {
            Class<?> handleCls = handle.getClass();
            while (Handle.class.isAssignableFrom(handleCls)) {
                try {
                    return current.getDeclaredMethod(method.getName(),
                            ArrayUtils.add(method.getParameterTypes(), 0, handleCls));
                } catch (NoSuchMethodException ignored) {
                    handleCls = handleCls.getSuperclass();
                }
            }
            current = current.getSuperclass();
        }
        return null;
    }

    /**
     * Invokes the function in the way described by the function that has been annotated.
     * @param method       The method that should be called.
     * @param instance     The instance that should be implemented.
     * @param parameters   The parameters
     * @return The result of the method or the callable.
     * @throws Throwable If the method throws an exception.
     */
    <T> T invoke(Method method, Environment environment, Object instance, Object... parameters) throws Throwable {
        Component component = method.getAnnotation(Component.class);
        Component.ExecutionThread syncstate = Component.ExecutionThread.SAME_THREAD;
        if (component != null) {
            syncstate = (component != null) ? component.thread() : Component.ExecutionThread.SAME_THREAD;
        }
        return syncstate.invoke(environment, method, instance, parameters);
    }
}