Package com.google.gwt.uibinder.rebind

Source Code of com.google.gwt.uibinder.rebind.HandlerEvaluator

/*
* Copyright 2009 Google 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.
*/
package com.google.gwt.uibinder.rebind;

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.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.rebind.model.OwnerClass;
import com.google.gwt.uibinder.rebind.model.OwnerField;
import com.google.web.bindery.event.shared.HandlerRegistration;

/**
* This class implements an easy way to bind widget event handlers to methods
* annotated with {@link com.google.gwt.uibinder.client.UiHandler} so that the
* user doesn't need to worry about writing code to implement these bindings.
*
* <p>
* For instance, the class defined below:
*
* <pre>
*   public class MyClass {
*     @UiField Label label;
*
*     @UiBinder({"label", "link"})
*     public void doClick(ClickEvent e) {
*       // do something
*     }
*   }
* </pre>
*
* will generate a piece of code like:
*
* <pre>
*    ClickHandler handler0 = new ClickHandler() {
*      @Override
*      public void onClick(ClickEvent event) {
*        owner.doClick(event);
*      }
*   });
*   label.addClickHandler(handler0);
*   link.addClickHandler(handler0);
* </pre>
*
* Notice that the <b>link</b> object doesn't need to be annotated with
* {@link com.google.gwt.uibinder.client.UiField} as long as it exists
* (annotated with ui:field) in the template.
*/
class HandlerEvaluator {

  private static final String HANDLER_BASE_NAME =
      "handlerMethodWithNameVeryUnlikelyToCollideWithUserFieldNames";
  /*
   * TODO(rjrjr) The correct fix is to put the handlers in a locally defined
   * class, making the generated code look like this
   *
   * http://docs.google.com/Doc?docid=0AQfnKgX9tAdgZGZ2cTM5YjdfMmQ4OTk0eGhz&hl=en
   *
   * But that needs to wait for a refactor to get most of this stuff out of here
   * and into com.google.gwt.uibinder.rebind.model
   */
  private int varCounter = 0;

  private final MortalLogger logger;

  private final JClassType handlerRegistrationJClass;
  private final JClassType eventHandlerJClass;
  private final OwnerClass ownerClass;
  private final boolean useLazyWidgetBuilders;

  /**
   * The verbose testable constructor.
   *
   * @param ownerClass a descriptor of the UI owner class
   * @param logger the logger for warnings and errors
   * @param oracle the type oracle
   */
  HandlerEvaluator(OwnerClass ownerClass, MortalLogger logger,
      TypeOracle oracle, boolean useLazyWidgetBuilders) {
    this.ownerClass = ownerClass;
    this.logger = logger;
    this.useLazyWidgetBuilders = useLazyWidgetBuilders;

    handlerRegistrationJClass = oracle.findType(HandlerRegistration.class.getName());
    eventHandlerJClass = oracle.findType(EventHandler.class.getName());
  }

  /**
   * Runs the evaluator in the given class according to the valid fields
   * extracted from the template (via attribute ui:field).
   *
   * @param writer the writer used to output the results
   * @param fieldManager the field manager instance
   * @param uiOwner the name of the class evaluated here that owns the template
   */
  public void run(IndentedWriter writer, FieldManager fieldManager,
      String uiOwner) throws UnableToCompleteException {

    // Iterate through all methods defined in the class.
    for (JMethod method : ownerClass.getUiHandlers()) {
      // Evaluate the method.
      String boundMethod = method.getName();
      if (method.isPrivate()) {
        logger.die("Method '%s' cannot be private.", boundMethod);
      }

      // Retrieves both event and handler types.
      JParameter[] parameters = method.getParameters();
      if (parameters.length != 1) {
        logger.die("Method '%s' must have a single event parameter defined.",
            boundMethod);
      }
      JClassType eventType = parameters[0].getType().isClass();
      if (eventType == null) {
        logger.die("Parameter type is not a class.");
      }

      JClassType handlerType = getHandlerForEvent(eventType);
      if (handlerType == null) {
        logger.die("Parameter '%s' is not an event (subclass of GwtEvent).",
            eventType.getName());
      }

      // Cool to add the handler in the output.
      String handlerVarName = HANDLER_BASE_NAME + (++varCounter);
      writeHandler(writer, uiOwner, handlerVarName, handlerType, eventType,
          boundMethod);

      // Adds the handler created above.
      UiHandler annotation = method.getAnnotation(UiHandler.class);
      for (String objectName : annotation.value()) {
        // Is the field object valid?
        FieldWriter fieldWriter = fieldManager.lookup(objectName);
        if (fieldWriter == null) {
          logger.die(
              ("Method '%s' can not be bound. You probably missed ui:field='%s' "
                  + "in the template."), boundMethod, objectName);
        }
        JClassType objectType = fieldWriter.getInstantiableType();
        if (objectType.isGenericType() != null) {
          objectType = tryEnhancingTypeInfo(objectName, objectType);
        }

        // Retrieves the "add handler" method in the object.
        JMethod addHandlerMethodType = getAddHandlerMethodForObject(objectType, handlerType);
        if (addHandlerMethodType == null) {
          logger.die("Field '%s' does not have an 'add%s' method associated.",
              objectName, handlerType.getName());
        }

        // Cool to tie the handler into the object.
        writeAddHandler(writer, fieldManager, handlerVarName,
            addHandlerMethodType.getName(), objectName);
      }
    }
  }

  private JClassType tryEnhancingTypeInfo(String objectName, JClassType objectType) {
    OwnerField uiField = ownerClass.getUiField(objectName);
    if (uiField != null) {
      JParameterizedType pType = uiField.getRawType().isParameterized();
      if (pType != null) {
        // Even field is parameterized, it might be a super class. In that case, if we use the field
        // type then we might miss some add handlers methods from the objectType itself; something
        // we don't want to happen!
        if (pType.getBaseType().equals(objectType)) {
          // Now we proved type from UiField is more specific, let's use that one
          return pType;
        }
      }
    }
    return objectType;
  }

  /**
   * Writes a handler entry using the given writer.
   *
   * @param writer the writer used to output the results
   * @param uiOwner the name of the class evaluated here that owns the template
   * @param handlerVarName the name of the handler variable
   * @param handlerType the handler we want to create
   * @param eventType the event associated with the handler
   * @param boundMethod the method bound in the handler
   */
  protected void writeHandler(IndentedWriter writer, String uiOwner,
      String handlerVarName, JClassType handlerType, JClassType eventType,
      String boundMethod) throws UnableToCompleteException {

    // Retrieves the single method (usually 'onSomething') related to all
    // handlers. Ex: onClick in ClickHandler, onBlur in BlurHandler ...
    JMethod[] methods = handlerType.getMethods();
    if (methods.length != 1) {
      logger.die("'%s' has more than one method defined.",
          handlerType.getName());
    }

    // Checks if the method has an Event as parameter. Ex: ClickEvent in
    // onClick, BlurEvent in onBlur ...
    JParameter[] parameters = methods[0].getParameters();
    if (parameters.length != 1 || parameters[0].getType() != eventType) {
      logger.die("Method '%s' needs '%s' as parameter", methods[0].getName(),
          eventType.getName());
    }

    writer.newline();
    // Create the anonymous class extending the raw type to avoid errors under the new JDT
    // if the type has a wildcard.
    writer.write("final %1$s %2$s = new %1$s() {",
        handlerType.getQualifiedSourceName(), handlerVarName);
    writer.indent();
    writer.write("public void %1$s(%2$s event) {", methods[0].getName(),
        // Use the event raw type to match the signature as we are using implementing the raw type
        // interface.
        eventType.getQualifiedSourceName());
    writer.indent();
    // Cast the event to the parameterized type to avoid warnings..
    writer.write("%1$s.%2$s((%3$s) event);", uiOwner, boundMethod,
        eventType.getParameterizedQualifiedSourceName());
    writer.outdent();
    writer.write("}");
    writer.outdent();
    writer.write("};");
  }

  /**
   * Adds the created handler to the given object (field).
   *
   * @param writer the writer used to output the results
   * @param handlerVarName the name of the handler variable
   * @param addHandlerMethodName the "add handler" method name associated with
   *          the object
   * @param objectName the name of the object we want to tie the handler
   */
  void writeAddHandler(IndentedWriter writer, FieldManager fieldManager,
      String handlerVarName, String addHandlerMethodName, String objectName) {
    if (useLazyWidgetBuilders) {
      fieldManager.require(objectName).addStatement("%1$s.%2$s(%3$s);",
          objectName, addHandlerMethodName, handlerVarName);
    } else {
      writer.write("%1$s.%2$s(%3$s);", objectName, addHandlerMethodName,
          handlerVarName);
    }
  }

  /**
   * Checks if a specific handler is valid for a given object and return the
   * method that ties them. The object must override a method that returns
   * {@link com.google.gwt.event.shared.HandlerRegistration} and receives a
   * single input parameter of the same type of handlerType.
   *
   * <p>
   * Output an error in case more than one method match the conditions described
   * above.
   * </p>
   *
   * <pre>
   *   <b>Examples:</b>
   *    - HandlerRegistration addClickHandler(ClickHandler handler)
   *    - HandlerRegistration addMouseOverHandler(MouseOverHandler handler)
   *    - HandlerRegistration addSubmitCompleteHandler(
   *          FormPanel.SubmitCompleteHandler handler)
   * </pre>
   *
   * @param objectType the object type we want to check
   * @param handlerType the handler type we want to check in the object
   *
   * @return the method that adds handlerType into objectType, or <b>null</b> if
   *         no method was found
   */
  private JMethod getAddHandlerMethodForObject(JClassType objectType,
      JClassType handlerType) throws UnableToCompleteException {
    JMethod handlerMethod = null;
    JMethod alternativeHandlerMethod = null;
    JMethod alternativeHandlerMethod2 = null;
    for (JMethod method : objectType.getInheritableMethods()) {

      // Condition 1: returns HandlerRegistration?
      JClassType returnClassType = method.getReturnType().isClassOrInterface();
      if (returnClassType != null && handlerRegistrationJClass.isAssignableFrom(returnClassType)) {

        // Condition 2: single parameter of the same type of handlerType?
        JParameter[] parameters = method.getParameters();
        if (parameters.length != 1) {
          continue;
        }

        JClassType methodParam = parameters[0].getType().isClassOrInterface();
        if (methodParam == null) {
          continue;
        }

        if (handlerType.equals(methodParam)) {

          // Condition 3: does more than one method match the condition?
          if (handlerMethod != null) {
            logger.die(
                ("This handler cannot be generated. Methods '%s' and '%s' are "
                    + "ambiguous. Which one to pick?"), method, handlerMethod);
          }

          handlerMethod = method;
          continue;
        }

        /**
         * Normalize the parameter and check for an alternative handler method.
         * Might be the case where the given objectType is generic. In this
         * situation we need to normalize the method parameter to test for
         * equality. For instance:
         *
         *   handlerType => TableHandler<String>
         *   subjectHandler => Alt 1: TableHandler or Alt 2: TableHandler<T>
         *
         * This is done as an alternative handler method to preserve the
         * original logic.
         */
        if (handlerType.isAssignableFrom(methodParam)) {
          // Alt 1: TableHandler<String> => TableHandler or TableHandler<?> => TableHandler<String>
          alternativeHandlerMethod = method;
        } else if (handlerType.isParameterized() != null && objectType.isGenericType() != null) {
          // Alt 2: TableHandler<String> => TableHandler<T>
          if (methodParam.getErasedType().equals(handlerType.isParameterized().getErasedType())) {
            // Unfortunately this is overly lenient but it was always like this
            alternativeHandlerMethod2 = method;
          }
        }
      }
    }

    return (handlerMethod != null) ? handlerMethod
        : (alternativeHandlerMethod != null) ? alternativeHandlerMethod : alternativeHandlerMethod2;
  }

  /**
   * Retrieves the handler associated with the event.
   *
   * @param eventType the given event
   * @return the associated handler, <code>null</code> if not found
   */
  private JClassType getHandlerForEvent(JClassType eventType) {

    // All handlers event must have an overrided method getAssociatedType().
    // We take advantage of this information to get the associated handler.
    // Ex:
    // com.google.gwt.event.dom.client.ClickEvent
    // ---> com.google.gwt.event.dom.client.ClickHandler
    //
    // com.google.gwt.event.dom.client.BlurEvent
    // ---> com.google.gwt.event.dom.client.BlurHandler

    if (eventType == null) {
      return null;
    }

    JMethod method = eventType.findMethod("getAssociatedType", new JType[0]);
    if (method == null) {
      logger.warn(
          "Method 'getAssociatedType()' could not be found in the event '%s'.",
          eventType.getName());
      return null;
    }

    JType returnType = method.getReturnType();
    if (returnType == null) {
      logger.warn(
          "The method 'getAssociatedType()' in the event '%s' returns void.",
          eventType.getName());
      return null;
    }

    JParameterizedType isParameterized = returnType.isParameterized();
    if (isParameterized == null) {
      logger.warn(
          "The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
          eventType.getName());
      return null;
    }

    JClassType[] argTypes = isParameterized.getTypeArgs();
    if ((argTypes.length != 1)
        && !argTypes[0].isAssignableTo(eventHandlerJClass)) {
      logger.warn(
          "The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
          eventType.getName());
      return null;
    }

    return argTypes[0];
  }
}
TOP

Related Classes of com.google.gwt.uibinder.rebind.HandlerEvaluator

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.