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

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

There is a newer version: 1.6
Show newest version
/*
 * Copyright 2008 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 java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.regex.Pattern;

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.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.uibinder.attributeparsers.FieldReferenceConverter;
import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
import com.google.gwt.uibinder.rebind.model.OwnerClass;
import com.google.gwt.uibinder.rebind.model.OwnerField;

/**
 * This version of {@link FieldManager} makes it possible for UiBinder
 * files to use widgets that need to be instantiated with gin.
 * See {@link GinUiBinderGenerator} for details. This
 * is a slightly modified version of {@link FieldManager}
 * that allows fields to be instantiated using gin dependency
 * injection. Modifications are clearly indicated by
 * {@code MODIFICATION} comments.
 *
 * @author Google
 * @author Philippe Beaudoin ([email protected])
 * @author Brandon Donnelson ([email protected])
 */
public class GinFieldManager extends FieldManager {

  static class FieldAndSource {
    final FieldWriter field;
    final XMLElement element;
    
    public FieldAndSource(FieldWriter field, XMLElement element) {
      this.field = field;
      this.element = element;
    }
  }

  private static final String GETTER_PREFIX = "get_";

  private static final String BUILDER_PREFIX = "build_";

  private static final String DUPLICATE_FIELD_ERROR = "Duplicate declaration of field %1$s.";

  private static final Comparator BUILD_DEFINITION_SORT =
      new Comparator() {
    public int compare(FieldWriter field1, FieldWriter field2) {
      // First get type precedence, if ties the field precedence is used.
      int precedence = field2.getFieldType().getBuildPrecedence()
          - field1.getFieldType().getBuildPrecedence();
      if (precedence == 0) {
        precedence = field2.getBuildPrecedence() - field1.getBuildPrecedence();
      }
      return precedence;
    }
  };

  private static final Pattern JAVA_IDENTIFIER =
      Pattern.compile("[\\p{L}_$][\\p{L}\\p{N}_$]*");

  public static String getFieldBuilder(String fieldName) {
    return String.format(BUILDER_PREFIX + "%s()", fieldName);
  }

  public static String getFieldGetter(String fieldName) {
    return String.format(GETTER_PREFIX + "%s()", fieldName);
  }

  public static String stripFieldGetter(String fieldName) {
    if (fieldName.startsWith(GETTER_PREFIX)) {
      return fieldName.substring(GETTER_PREFIX.length());
    }
    return fieldName;
  }

  private final TypeOracle typeOracle;

  private final MortalLogger logger;

  /**
   * Map of field name to FieldWriter. Note its a LinkedHashMap--we want to
   * write these out in the order they're declared.
   */
  private final LinkedHashMap fieldsMap =
      new LinkedHashMap();

  /**
   * A stack of the fields.
   */
  private final LinkedList parsedFieldStack = new LinkedList();

  private LinkedHashMap fieldReferences =
      new LinkedHashMap();

  /**
   * Counts the number of times a getter field is called, this important to
   * decide which strategy to take when outputing getters and builders.
   * {@see com.google.gwt.uibinder.rebind.FieldWriter#writeFieldDefinition}.
   */
  private final Map gettersCounter = new HashMap();

  /**
   * Whether to use the new strategy of generating UiBinder code.
   */
  private final boolean useLazyWidgetBuilders;

  // BEGIN MODIFICATION
  private Map ginjectorMethods = new HashMap();
  private JClassType ginjectorClass;
  
  public GinFieldManager(TypeOracle typeOracle, MortalLogger logger, JClassType ginjectorClass, 
      boolean useLazyWidgetBuilders) {
    super(typeOracle, logger, useLazyWidgetBuilders);
    
    this.typeOracle = typeOracle;
    this.logger = logger;
    this.useLazyWidgetBuilders = useLazyWidgetBuilders;
    
    this.ginjectorClass = ginjectorClass;
    for (JMethod method : ginjectorClass.getMethods()) {
      JClassType returnType = method.getReturnType().isClassOrInterface();
      if (method.getParameters().length == 0 && returnType != null) {
        ginjectorMethods.put(returnType, method.getName());
      }
    }
  }
  // END MODIFICATION

  /**
   * Converts the given field to its getter. Example:
   *  
  • myWidgetX = get_myWidgetX() *
  • f_html1 = get_f_html1() */ public String convertFieldToGetter(String fieldName) { // could this conversion can be moved to FieldWriter? if (!useLazyWidgetBuilders) { return fieldName; } incrementFieldCounter(fieldName); return getFieldGetter(fieldName); } /** * Prevent a field from being optimized as only being referenced once (and therefore constant for * all intents). This is necessary for UiRenderer fields passed as parameters to render() calls. * Those fields are modified every time a template is rendered with the parameter values. */ public void disableOptimization(String fieldName) { // TODO(rchandia): This hackish method should go away when the // UiRenderer generator gets separated from the one used for // UiBinder. Fields corresponding to parameters of render() will // not use the initialization generated by the FieldWriter. // Incrementing the counter twice ensures no optimization happens. // See AbstractFieldWriter#writeFieldDefinition() incrementFieldCounter(fieldName); incrementFieldCounter(fieldName); } public FieldReference findFieldReference(String expressionIn) { String expression = expressionIn; if (useLazyWidgetBuilders) { expression = stripFieldGetter(expression); } String converted = FieldReferenceConverter.expressionToPath(expression); return fieldReferences.get(converted); } /** * Initialize with field builders the generated Widgets inner class. * {@see com.google.gwt.uibinder.rebind.FieldWriter#writeFieldBuilder}. */ public void initializeWidgetsInnerClass(IndentedWriter w, OwnerClass ownerClass) throws UnableToCompleteException { FieldWriter[] fields = fieldsMap.values().toArray( new FieldWriter[fieldsMap.size()]); Arrays.sort(fields, BUILD_DEFINITION_SORT); for (FieldWriter field : fields) { int count = getGetterCounter(field.getName()); field.writeFieldBuilder(w, count, ownerClass.getUiField(field.getName())); } } /** * @param fieldName the name of the {@link FieldWriter} to find * @return the {@link FieldWriter} instance indexed by fieldName or * null in case fieldName is not found */ public FieldWriter lookup(String fieldName) { return fieldsMap.get(fieldName); } /** * Remove the field at the top of the {@link #parsedFieldStack}. */ public void pop() { parsedFieldStack.removeFirst(); } /** * @param source the element this field was parsed from * @param fieldWriter the field to push on the top of the * {@link #parsedFieldStack} */ public void push(XMLElement source, FieldWriter fieldWriter) { parsedFieldStack.addFirst(new FieldAndSource(fieldWriter, source)); } /** * Used to declare fields of an existing type. If your field will hold a type * that is being generated, see {@link #registerFieldOfGeneratedType}. *

    * When making a field we peek at the {@link #parsedFieldStack} to make sure * that the field that holds the widget currently being parsed will depended * upon the field being declared. This ensures, for example, that dom id * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel * will be declared before it is. * * @param fieldWriterType the field writer type associated * @param fieldType the type of the new field * @param fieldName the name of the new field * @return a new {@link FieldWriter} instance * @throws UnableToCompleteException on duplicate name */ public FieldWriter registerField(FieldWriterType fieldWriterType, JClassType fieldType, String fieldName) throws UnableToCompleteException { // BEGIN MODIFICATION String ginjectorMethod = ginjectorMethods.get(fieldType); FieldWriter field; if (ginjectorMethod != null) { // If the ginjector lets us create that fieldType then we use gin to instantiate it field = new FieldWriterOfInjectedType(this, fieldWriterType, fieldType, fieldName, ginjectorClass, ginjectorMethod, logger); } else { // Otherwise field = new FieldWriterOfExistingType(this, fieldWriterType, fieldType, fieldName, logger); } // END MODIFICATION return registerField(fieldName, field); } public FieldWriter registerField(JClassType fieldType, String fieldName) throws UnableToCompleteException { return registerField(FieldWriterType.DEFAULT, fieldType, fieldName); } public FieldWriter registerField(String type, String fieldName) throws UnableToCompleteException { return registerField(typeOracle.findType(type), fieldName); } /** * Used to declare fields that will hold generated instances generated * CssResource interfaces. If your field will hold a reference of an existing * type, see {@link #registerField}. For other generated types, use * {@link #registerFieldOfGeneratedType} * {@link #registerFieldForGeneratedCssResource}. *

    * When making a field we peek at the {@link #parsedFieldStack} to make sure * that the field that holds the widget currently being parsed will depended * upon the field being declared. This ensures, for example, that dom id * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel * will be declared before it is. * * @throws UnableToCompleteException on duplicate name * @return a new {@link FieldWriter} instance */ public FieldWriter registerFieldForGeneratedCssResource( ImplicitCssResource cssResource) throws UnableToCompleteException { FieldWriter field = new FieldWriterOfGeneratedCssResource(this, typeOracle.findType(String.class.getCanonicalName()), cssResource, logger); return registerField(cssResource.getName(), field); } /** * Register a new field for {@link com.google.gwt.uibinder.client.LazyDomElement} * types. LazyDomElement fields can only be associated with html elements. Example: * *

  • LazyDomElement<DivElement> -> <div>
  • *
  • LazyDomElement<Element> -> <div>
  • *
  • LazyDomElement<SpanElement> -> <span>
  • * * @param templateFieldType the html type to bind, eg, SpanElement, DivElement, etc * @param ownerField the field instance */ public FieldWriter registerFieldForLazyDomElement(JClassType templateFieldType, OwnerField ownerField) throws UnableToCompleteException { if (ownerField == null) { throw new RuntimeException("Cannot register a null owner field for LazyDomElement."); } FieldWriter field = new FieldWriterOfLazyDomElement(this, templateFieldType, ownerField, logger); return registerField(ownerField.getName(), field); } /** * Used to declare fields of a type (other than CssResource) that is to be * generated. If your field will hold a reference of an existing type, see * {@link #registerField}. For generated CssResources, see * {@link #registerFieldForGeneratedCssResource}. *

    * When making a field we peek at the {@link #parsedFieldStack} to make sure * that the field that holds the widget currently being parsed will depended * upon the field being declared. This ensures, for example, that dom id * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel * will be declared before it is. * * @param assignableType class or interface extened or implemented by this * type * @param typeName the full qualified name for the class associated with the * field * @param fieldName the name of the field * @throws UnableToCompleteException on duplicate name * @return a new {@link FieldWriter} instance */ public FieldWriter registerFieldOfGeneratedType(JClassType assignableType, String typePackage, String typeName, String fieldName) throws UnableToCompleteException { FieldWriter field = new FieldWriterOfGeneratedType(this, assignableType, typePackage, typeName, fieldName, logger); return registerField(fieldName, field); } /** * Called to register a {field.reference} encountered during * parsing, to be validated against the type oracle once parsing is complete. */ public void registerFieldReference(XMLElement source, String fieldReferenceString, JType... types) { source = source != null ? source : parsedFieldStack.peek().element; FieldReference fieldReference = fieldReferences.get(fieldReferenceString); if (fieldReference == null) { fieldReference = new FieldReference(fieldReferenceString, source, this, typeOracle); fieldReferences.put(fieldReferenceString, fieldReference); } fieldReference.addLeftHandType(source, types); } /** * Gets a FieldWriter given its name or throws a RuntimeException if not found. * @param fieldName the name of the {@link FieldWriter} to find * @return the {@link FieldWriter} instance indexed by fieldName */ public FieldWriter require(String fieldName) { FieldWriter fieldWriter = lookup(fieldName); if (fieldWriter == null) { throw new RuntimeException("The required field %s doesn't exist."); } return fieldWriter; } /** * To be called after parsing is complete. Surveys all * {field.reference}s and checks they refer to existing types, * and have appropriate return types. * * @throws UnableToCompleteException if any {field.references} * can't be resolved */ public void validate() throws UnableToCompleteException { boolean failed = false; for (Map.Entry entry : fieldReferences.entrySet()) { FieldReference ref = entry.getValue(); MonitoredLogger monitoredLogger = new MonitoredLogger(logger); ref.validate(monitoredLogger); failed |= monitoredLogger.hasErrors(); } if (failed) { throw new UnableToCompleteException(); } } /** * Outputs the getter and builder definitions for all fields. * {@see com.google.gwt.uibinder.rebind.AbstractFieldWriter#writeFieldDefinition}. */ public void writeFieldDefinitions(IndentedWriter writer, TypeOracle typeOracle, OwnerClass ownerClass, DesignTimeUtils designTime) throws UnableToCompleteException { Collection fields = fieldsMap.values(); for (FieldWriter field : fields) { int counter = getGetterCounter(field.getName()); field.writeFieldDefinition( writer, typeOracle, ownerClass.getUiField(field.getName()), designTime, counter, useLazyWidgetBuilders); } } /** * Writes all stored gwt fields. * * @param writer the writer to output */ public void writeGwtFieldsDeclaration(IndentedWriter writer) throws UnableToCompleteException { Collection fields = fieldsMap.values(); for (FieldWriter field : fields) { field.write(writer); } } private void ensureValidity(String fieldName) throws UnableToCompleteException { if (!JAVA_IDENTIFIER.matcher(fieldName).matches()) { logger.die("Illegal field name \"%s\"", fieldName); } } /** * Gets the number of times a getter for the given field is called. */ private int getGetterCounter(String fieldName) { Integer count = gettersCounter.get(fieldName); return (count == null) ? 0 : count; } /** * Increments the number of times a getter for the given field is called. */ private void incrementFieldCounter(String fieldName) { int count = getGetterCounter(fieldName) + 1; gettersCounter.put(fieldName, count); } private FieldWriter registerField(String fieldName, FieldWriter field) throws UnableToCompleteException { ensureValidity(fieldName); requireUnique(fieldName); fieldsMap.put(fieldName, field); if (parsedFieldStack.size() > 0) { FieldWriter parent = parsedFieldStack.peek().field; field.setBuildPrecedence(parent.getBuildPrecedence() + 1); parent.needs(field); } return field; } private void requireUnique(String fieldName) throws UnableToCompleteException { if (fieldsMap.containsKey(fieldName)) { logger.die(DUPLICATE_FIELD_ERROR, fieldName); } } }





    © 2015 - 2024 Weber Informatics LLC | Privacy Policy