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

com.google.gwt.uibinder.rebind.HandlerEvaluator Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.
 *
 * 

* For instance, the class defined below: * *

 *   public class MyClass {
 *     @UiField Label label;
 *
 *     @UiBinder({"label", "link"})
 *     public void doClick(ClickEvent e) {
 *       // do something
 *     }
 *   }
 * 
* * will generate a piece of code like: * *
 *    ClickHandler handler0 = new ClickHandler() {
 *      @Override
 *      public void onClick(ClickEvent event) {
 *        owner.doClick(event);
 *      }
 *   });
 *   label.addClickHandler(handler0);
 *   link.addClickHandler(handler0);
 * 
* * Notice that the link 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 = (%1$s) new %3$s() {", handlerType.getParameterizedQualifiedSourceName(), handlerVarName, handlerType.getQualifiedSourceName()); 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. * *

* Output an error in case more than one method match the conditions described * above. *

* *
   *   Examples:
   *    - HandlerRegistration addClickHandler(ClickHandler handler)
   *    - HandlerRegistration addMouseOverHandler(MouseOverHandler handler)
   *    - HandlerRegistration addSubmitCompleteHandler(
   *          FormPanel.SubmitCompleteHandler handler)
   * 
* * @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 null 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; } JType methodParam = parameters[0].getType(); 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 * subjectHandler => Alt 1: TableHandler or Alt 2: TableHandler * * This is done as an alternative handler method to preserve the * original logic. */ JParameterizedType ptype = handlerType.isParameterized(); if (ptype != null) { // Alt 1: TableHandler => TableHandler if (methodParam.equals(ptype.getRawType())) { alternativeHandlerMethod = method; } // Alt 2: TableHandler => TableHandler if (objectType.isGenericType() != null && methodParam.getErasedType().equals(ptype.getRawType())) { // 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, null 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.", 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.", eventType.getName()); return null; } return argTypes[0]; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy