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

com.cookingfox.lapasse.compiler.processor.event.HandleEventProcessor Maven / Gradle / Ivy

The newest version!
package com.cookingfox.lapasse.compiler.processor.event;

import com.cookingfox.lapasse.annotation.HandleEvent;
import com.cookingfox.lapasse.api.event.Event;
import com.cookingfox.lapasse.api.state.State;
import com.cookingfox.lapasse.compiler.processor.ProcessorHelper;
import com.cookingfox.lapasse.compiler.utils.TypeUtils;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import java.util.LinkedList;
import java.util.List;

import static com.cookingfox.lapasse.compiler.processor.event.HandleEventAnnotationParams.ANNOTATION_NO_PARAMS;
import static com.cookingfox.lapasse.compiler.processor.event.HandleEventAnnotationParams.ANNOTATION_ONE_PARAM_EVENT;
import static com.cookingfox.lapasse.compiler.processor.event.HandleEventMethodParams.*;
import static com.cookingfox.lapasse.compiler.utils.TypeUtils.equalsType;
import static com.cookingfox.lapasse.compiler.utils.TypeUtils.isSubtype;

/**
 * Processes a {@link HandleEvent} annotated handler method.
 */
public class HandleEventProcessor {

    /**
     * The annotated element.
     */
    protected final Element element;

    /**
     * Value Object containing the processing results.
     */
    protected final HandleEventResult result = new HandleEventResult();

    /**
     * Utility methods for operating on types.
     */
    protected final Types types;

    //----------------------------------------------------------------------------------------------
    // CONSTRUCTOR
    //----------------------------------------------------------------------------------------------

    public HandleEventProcessor(Element element, Types types) {
        this.element = element;
        this.types = types;
    }

    //----------------------------------------------------------------------------------------------
    // PUBLIC METHODS
    //----------------------------------------------------------------------------------------------

    /**
     * Process the {@link HandleEvent} annotated handler method and create a result object with the
     * extracted values.
     *
     * @return The result object of process operation.
     * @throws Exception when the handler method is invalid.
     */
    public HandleEventResult process() throws Exception {
        ExecutableElement method = ProcessorHelper.validateAndGetAnnotatedMethod(element);
        HandleEvent eventAnnotation = method.getAnnotation(HandleEvent.class);
        List parameters = method.getParameters();
        TypeMirror returnType = method.getReturnType();

        // populate result
        result.methodName = element.getSimpleName();
        result.parameters = parameters;
        result.annotationParams = determineAnnotationParams(eventAnnotation);
        result.methodParams = determineMethodParams(parameters);
        result.stateType = determineStateType(returnType);
        result.eventType = determineEventType();

        detectTypesConflict();

        return result;
    }

    //----------------------------------------------------------------------------------------------
    // PROTECTED METHODS
    //----------------------------------------------------------------------------------------------

    /**
     * Creates an exception for when the handler method's parameters are invalid.
     *
     * @param parameters The handler method's parameters.
     * @return The exception with the formatted error message.
     */
    protected Exception createInvalidMethodParamsException(List parameters) {
        List types = new LinkedList<>();

        for (VariableElement parameter : parameters) {
            types.add(parameter.asType());
        }

        return new Exception(String.format("Method parameters are invalid (expected State and " +
                "Event implementations): %s", types));
    }

    /**
     * Checks whether there is a conflict between the types defined by the annotation, the method's
     * parameters and the method's return type.
     *
     * @throws Exception when there is a conflict between the defined types.
     */
    protected void detectTypesConflict() throws Exception {
        TypeMirror annotationEventType = result.getAnnotationEventType();
        TypeMirror eventType = result.getEventType();
        TypeMirror methodParamStateType = result.getMethodParamStateType();
        TypeMirror stateType = result.getStateType();

        // compare annotation and method event type
        if (annotationEventType != null && !types.isSameType(eventType, annotationEventType)) {
            throw new Exception(String.format("Annotation parameter for event (`%s`) has " +
                    "different type than method parameter (`%s`)", annotationEventType, eventType));
        }

        // compare method and return value state type
        if (methodParamStateType != null && !types.isSameType(methodParamStateType, stateType)) {
            throw new Exception(String.format("Method parameter for state (`%s`) has different " +
                    "type than return type (`%s`)", methodParamStateType, stateType));
        }
    }

    /**
     * Determines the type of parameters for the {@link HandleEvent} annotation. The annotation
     * can hold a reference to the concrete event class that this method should handle.
     *
     * @param annotation The annotation object.
     * @return An enum which indicates the annotation parameters.
     * @throws Exception when the annotation parameters could not be determined.
     */
    protected HandleEventAnnotationParams determineAnnotationParams(HandleEvent annotation) throws Exception {
        TypeMirror annotationEventType = null;

        try {
            annotation.event(); // this should throw
        } catch (MirroredTypeException e) {
            annotationEventType = e.getTypeMirror();
        }

        // this should never happen
        if (annotationEventType == null) {
            throw new Exception("Could not extract event type from annotation");
        }

        // should not be base type
        if (TypeUtils.equalsType(annotationEventType, Event.class)) {
            return ANNOTATION_NO_PARAMS;
        }

        result.annotationEventType = annotationEventType;

        return ANNOTATION_ONE_PARAM_EVENT;
    }

    /**
     * Determines the concrete event type of the handler method. The event type can be set by both
     * the method parameters (event object) or the annotation (event class).
     *
     * @return The concrete event type of the handler method.
     * @throws Exception when the concrete event type could not be determined.
     */
    protected TypeMirror determineEventType() throws Exception {
        TypeMirror eventType = null;

        // determine with method params
        switch (result.getMethodParams()) {
            case METHOD_ONE_PARAM_EVENT:
            case METHOD_TWO_PARAMS_EVENT_STATE:
                // first param
                eventType = result.getParameters().get(0).asType();
                break;

            case METHOD_TWO_PARAMS_STATE_EVENT:
                // second param
                eventType = result.getParameters().get(1).asType();
                break;
        }

        // determine with annotation params
        if (eventType == null && result.getAnnotationParams() == ANNOTATION_ONE_PARAM_EVENT) {
            eventType = result.getAnnotationEventType();
        }

        // no event type: throw
        if (eventType == null) {
            throw new Exception(String.format("Could not determine the target event type. Add an " +
                    "event method parameter or add the type to the annotation: " +
                    "`@%s(event = MyEvent.class)`", HandleEvent.class.getSimpleName()));
        }

        // validate event
        return extendsEvent(eventType);
    }

    /**
     * Validates and identifies the handler method parameters.
     *
     * @param parameters The method parameters.
     * @return An indication of the method parameters.
     * @throws Exception when the method parameters are invalid.
     */
    protected HandleEventMethodParams determineMethodParams(List parameters) throws Exception {
        int numParams = parameters.size();

        if (numParams < 1) {
            return METHOD_NO_PARAMS;
        } else if (numParams > 2) {
            throw createInvalidMethodParamsException(parameters);
        }

        VariableElement firstParam = parameters.get(0);
        boolean firstIsEvent = isSubtype(firstParam, Event.class);
        boolean firstIsState = isSubtype(firstParam, State.class);

        if (!firstIsEvent && !firstIsState) {
            throw createInvalidMethodParamsException(parameters);
        }

        if (numParams == 1) {
            if (firstIsEvent) {
                return METHOD_ONE_PARAM_EVENT;
            }

            result.methodParamStateType = extendsState(firstParam.asType());

            return METHOD_ONE_PARAM_STATE;
        }

        VariableElement secondParam = parameters.get(1);

        if (firstIsEvent && isSubtype(secondParam, State.class)) {
            result.methodParamStateType = extendsState(secondParam.asType());

            return METHOD_TWO_PARAMS_EVENT_STATE;
        } else if (firstIsState && isSubtype(secondParam, Event.class)) {
            result.methodParamStateType = extendsState(firstParam.asType());

            return METHOD_TWO_PARAMS_STATE_EVENT;
        }

        throw createInvalidMethodParamsException(parameters);
    }

    /**
     * Determines the concrete state type based on the handler method's return type.
     *
     * @param returnType The return type of the handler method
     * @return The concrete state type of the handler method.
     * @throws Exception when the return type is an invalid state type.
     */
    protected TypeMirror determineStateType(TypeMirror returnType) throws Exception {
        if (!isSubtype(returnType, State.class) || equalsType(returnType, State.class)) {
            throw new Exception("Return type of @HandleEvent annotated method must extend "
                    + State.class.getName());
        }

        return returnType;
    }

    /**
     * Asserts the provided type extends event.
     *
     * @param type The type to validate.
     * @return The provided type, if valid.
     * @throws Exception when the provided is equal to the event base type.
     */
    protected TypeMirror extendsEvent(TypeMirror type) throws Exception {
        if (equalsType(type, Event.class)) {
            throw new Exception(String.format("Event parameter cannot be the base type `%s`",
                    Event.class.getName()));
        }

        return type;
    }

    /**
     * Asserts the provided type extends state.
     *
     * @param type The type to validate.
     * @return The provided type, if valid.
     * @throws Exception when the provided is equal to the state base type.
     */
    protected TypeMirror extendsState(TypeMirror type) throws Exception {
        if (equalsType(type, State.class)) {
            throw new Exception(String.format("State parameter cannot be the base type `%s`",
                    State.class.getName()));
        }

        return type;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy