Package com.ekuefler.supereventbus.rebind

Source Code of com.ekuefler.supereventbus.rebind.EventRegistrationWriter

/*
* Copyright 2013 Erik Kuefler
*
* 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.
*/
package com.ekuefler.supereventbus.rebind;

import com.ekuefler.supereventbus.Subscribe;
import com.ekuefler.supereventbus.filtering.When;
import com.ekuefler.supereventbus.multievent.EventTypes;
import com.ekuefler.supereventbus.multievent.MultiEvent;
import com.ekuefler.supereventbus.priority.WithPriority;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.user.rebind.SourceWriter;

import java.lang.reflect.Constructor;
import java.util.LinkedList;
import java.util.List;

/**
* Writes implementations of {@link com.ekuefler.supereventbus.EventRegistration}. The generated
* class implements {@link com.ekuefler.supereventbus.EventRegistration#getMethods} by iterating
* over the target class's methods and generating an anonymous handler class for each method
* annotated with {@link Subscribe}.
*
* @author ekuefler@gmail.com (Erik Kuefler)
*/
class EventRegistrationWriter {

  private final TreeLogger logger;

  EventRegistrationWriter(TreeLogger logger) {
    this.logger = logger;
  }

  /**
   * Writes the source for getMethods() the given target class to the given writer.
   */
  void writeGetMethods(JClassType target, SourceWriter writer) throws UnableToCompleteException {
    String targetType = target.getQualifiedSourceName();
    writer.println("public List<EventHandlerMethod<%s, ?>> getMethods() {", targetType);
    writer.indent();

    // Write a list that we will add all handlers to before returning
    writer.println("List<%1$s> methods = new LinkedList<%1$s>();",
        String.format("EventHandlerMethod<%s, ?>", targetType));

    // Iterate over each method in the target, looking for methods annotated with @Subscribe
    for (JMethod method : target.getMethods()) {
      if (method.getAnnotation(Subscribe.class) == null) {
        continue;
      }
      checkValidity(target, method);

      // Generate a list of types that should be handled by this method. Normally, this is a single
      // type equal to the method's first argument. If the argument in a MultiEvent, this list of
      // types comes from the @EventTypes annotation on the parameter.
      final List<String> paramTypes = new LinkedList<String>();
      final boolean isMultiEvent;
      if (getFirstParameterType(method).equals(MultiEvent.class.getCanonicalName())) {
        isMultiEvent = true;
        for (Class<?> type : method.getParameters()[0].getAnnotation(EventTypes.class).value()) {
          paramTypes.add(type.getCanonicalName());
        }
      } else {
        isMultiEvent = false;
        paramTypes.add(getFirstParameterType(method));
      }

      // Add an implementation of EventHandlerMethod to the list for each type this method handles
      for (String paramType : paramTypes) {
        writer.println("methods.add(new EventHandlerMethod<%s, %s>() {", targetType, paramType);
        writer.indent();
        {
          // Implement invoke() by calling the method, first checking filters if provided
          writer.println("public void invoke(%s instance, %s arg) {", targetType, paramType);
          String invocation = String.format(
              isMultiEvent ? "instance.%s(new MultiEvent(arg));" : "instance.%s(arg);",
              method.getName());
          if (method.getAnnotation(When.class) != null) {
            writer.indentln("if (%s) { %s }", getFilter(method), invocation);
          } else {
            writer.indentln(invocation);
          }
          writer.println("}");

          // Implement acceptsArgument using instanceof
          writer.println("public boolean acceptsArgument(Object arg) {");
          writer.indentln("return arg instanceof %s;", paramType);
          writer.println("}");

          // Implement getDispatchOrder as the inverse of the method's priority
          writer.println("public int getDispatchOrder() {");
          writer.indentln("return %d;", method.getAnnotation(WithPriority.class) != null
              ? -method.getAnnotation(WithPriority.class).value()
              : 0);
          writer.println("}");
        }
        writer.outdent();
        writer.println("});");
      }
    }

    // Return the list of EventHandlerMethods
    writer.println("return methods;");
    writer.outdent();
    writer.println("}");
  }

  private void checkValidity(JClassType target, JMethod method) throws UnableToCompleteException {
    // General checks for all methods annotated with @Subscribe
    if (method.getParameterTypes().length != 1) {
      logger.log(Type.ERROR,
          String.format("Method %s.%s annotated with @Subscribe must take exactly one argument.",
              target.getName(), method.getName()));
      throw new UnableToCompleteException();
    } else if (method.isPrivate()) {
      logger.log(Type.ERROR,
          String.format("Method %s.%s annotated with @Subscribe must not be private.",
              target.getName(), method.getName()));
      throw new UnableToCompleteException();
    }

    if (method.getParameterTypes()[0].getQualifiedSourceName().equals(
        MultiEvent.class.getCanonicalName())) {
      // Checks specific to MultiEvents
      if (method.getParameters()[0].getAnnotation(EventTypes.class) == null) {
        logger.log(Type.ERROR,
            String.format("MultiEvent in method %s.%s must be annotated with @EventTypes.",
                target.getName(), method.getName()));
        throw new UnableToCompleteException();
      }

      // Ensure that no type is assignable to another type
      Class<?>[] classes = method.getParameters()[0].getAnnotation(EventTypes.class).value();
      for (Class<?> c1 : classes) {
        for (Class<?> c2 : classes) {
          if (c1 != c2 && c1.isAssignableFrom(c2)) {
            logger.log(Type.ERROR,
                String.format("The type %s is redundant with the type %s in method %s.%s.",
                    c2.getSimpleName(), c1.getSimpleName(), target.getName(), method.getName()));
            throw new UnableToCompleteException();
          }
        }
      }
    } else {
      // Checks for non-MultiEvents
      if (method.getParameters()[0].getAnnotation(EventTypes.class) != null) {
        logger.log(Type.ERROR,
            String.format(
                "@EventTypes must not be applied to a non-MultiEvent parameter in method %s.%s.",
                target.getName(), method.getName()));
        throw new UnableToCompleteException();
      }
    }
  }

  // Returns a boolean expression that should be used to check whether to invoke the given event
  // handler, based on the filters applied to it
  private String getFilter(JMethod method) throws UnableToCompleteException {
    StringBuilder predicate = new StringBuilder();
    When annotation = method.getAnnotation(When.class);
    boolean first = true;
    for (Class<?> filter : annotation.value()) {
      if (!classHasZeroArgConstructor(filter)) {
        logger.log(Type.ERROR, String.format(
            "Class %s extending EventFilter must define a public zero-argument constructor.",
            filter.getSimpleName()));
        throw new UnableToCompleteException();
      }
      if (!first) {
        predicate.append(" && ");
      }
      first = false;
      predicate.append(String.format(
          "new %s().accepts(instance, arg)", filter.getCanonicalName()));
    }
    return predicate.toString();
  }

  // Returns the type of the first parameter to the given method, boxed appropriately
  private String getFirstParameterType(JMethod method) {
    // If the parameter type is primitive, box it
    JType type = method.getParameterTypes()[0];
    if (type.isPrimitive() != null) {
      if (type.isPrimitive() == JPrimitiveType.BOOLEAN) {
        return Boolean.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.BYTE) {
        return Byte.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.CHAR) {
        return Character.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.DOUBLE) {
        return Double.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.FLOAT) {
        return Float.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.INT) {
        return Integer.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.LONG) {
        return Long.class.getName();
      } else if (type.isPrimitive() == JPrimitiveType.SHORT) {
        return Short.class.getName();
      }
    }

    // Otherwise return the fully-qualified type name
    return type.getQualifiedSourceName();
  }

  private boolean classHasZeroArgConstructor(Class<?> clazz) {
    try {
      for (Constructor<?> s : clazz.getConstructors()) {
        if (s.getParameterTypes().length == 0) {
          return true;
        }
      }
      return false;
    } catch (SecurityException e) {
      throw new RuntimeException(e);
    }
  }
}
TOP

Related Classes of com.ekuefler.supereventbus.rebind.EventRegistrationWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.