Package com.google.gwt.uibinder.rebind

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

/*
* 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 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.JPackage;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.TagName;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.DomEvent.Type;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
import com.google.gwt.uibinder.client.LazyDomElement;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiRenderer;
import com.google.gwt.uibinder.client.impl.AbstractUiRenderer;
import com.google.gwt.uibinder.elementparsers.AttributeMessageParser;
import com.google.gwt.uibinder.elementparsers.BeanParser;
import com.google.gwt.uibinder.elementparsers.ElementParser;
import com.google.gwt.uibinder.elementparsers.IsEmptyParser;
import com.google.gwt.uibinder.elementparsers.UiChildParser;
import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
import com.google.gwt.uibinder.rebind.model.HtmlTemplateMethodWriter;
import com.google.gwt.uibinder.rebind.model.HtmlTemplatesWriter;
import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
import com.google.gwt.uibinder.rebind.model.OwnerClass;
import com.google.gwt.uibinder.rebind.model.OwnerField;
import com.google.gwt.user.client.ui.IsRenderable;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RenderableStamper;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.beans.Introspector;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
* Writer for UiBinder generated classes.
*/
public class UiBinderWriter implements Statements {

  static final String RENDER_PARAM_HOLDER_PREFIX = "_renderer_param_holder_";

  private static final String SAFE_VAR_PREFIX =
    "somethingUnlikelyToCollideWithParamNamesWefio";

  private static final String UI_RENDERER_DISPATCHER_PREFIX = "UiRendererDispatcherFor";

  private static final String PACKAGE_URI_SCHEME = "urn:import:";

  // TODO(rjrjr) Another place that we need a general anonymous field
  // mechanism
  private static final String CLIENT_BUNDLE_FIELD =
      "clientBundleFieldNameUnlikelyToCollideWithUserSpecifiedFieldOkay";

  public static String asCommaSeparatedList(String... args) {
    StringBuilder b = new StringBuilder();
    for (String arg : args) {
      if (b.length() > 0) {
        b.append(", ");
      }
      b.append(arg);
    }

    return b.toString();
  }

  /**
   * Escape text that will be part of a string literal to be interpreted at
   * runtime as an HTML attribute value.
   */
  public static String escapeAttributeText(String text) {
    text = escapeText(text, false);

    /*
     * Escape single-quotes to make them safe to be interpreted at runtime as an
     * HTML attribute value (for which we by convention use single quotes).
     */
    text = text.replaceAll("'", "'");
    return text;
  }

  /**
   * Escape text that will be part of a string literal to be interpreted at
   * runtime as HTML, optionally preserving whitespace.
   */
  public static String escapeText(String text, boolean preserveWhitespace) {
    // Replace reserved XML characters with entities. Note that we *don't*
    // replace single- or double-quotes here, because they're safe in text
    // nodes.
    text = text.replaceAll("&", "&");
    text = text.replaceAll("<", "&lt;");
    text = text.replaceAll(">", "&gt;");

    if (!preserveWhitespace) {
      text = text.replaceAll("\\s+", " ");
    }

    return escapeTextForJavaStringLiteral(text);
  }

  /**
   * Escape characters that would mess up interpretation of this string as a
   * string literal in generated code (that is, protect \, \n and " ).
   */
  public static String escapeTextForJavaStringLiteral(String text) {
    text = text.replace("\\", "\\\\");
    text = text.replace("\"", "\\\"");
    text = text.replace("\n", "\\n");

    return text;
  }

  /**
   * Returns a list of the given type and all its superclasses and implemented
   * interfaces in a breadth-first traversal.
   *
   * @param type the base type
   * @return a breadth-first collection of its type hierarchy
   */
  static Iterable<JClassType> getClassHierarchyBreadthFirst(JClassType type) {
    LinkedList<JClassType> list = new LinkedList<JClassType>();
    LinkedList<JClassType> q = new LinkedList<JClassType>();

    q.add(type);
    while (!q.isEmpty()) {
      // Pop the front of the queue and add it to the result list.
      JClassType curType = q.removeFirst();
      list.add(curType);

      // Add implemented interfaces to the back of the queue (breadth first,
      // remember?)
      for (JClassType intf : curType.getImplementedInterfaces()) {
        q.add(intf);
      }
      // Add then add superclasses
      JClassType superClass = curType.getSuperclass();
      if (superClass != null) {
        q.add(superClass);
      }
    }

    return list;
  }

  private static String capitalizePropName(String propName) {
    return propName.substring(0, 1).toUpperCase() + propName.substring(1);
  }

  /**
   * Searches for methods named onBrowserEvent in a {@code type}.
   */
  private static JMethod[] findEventMethods(JClassType type) {
    List<JMethod> methods = new ArrayList<JMethod>(Arrays.asList(type.getInheritableMethods()));

    for (Iterator<JMethod> iterator = methods.iterator(); iterator.hasNext();) {
      JMethod jMethod = iterator.next();
      if (!jMethod.getName().equals("onBrowserEvent")) {
        iterator.remove();
      }
    }

    return methods.toArray(new JMethod[methods.size()]);
  }

  /**
   * Scan the base class for the getter methods. Assumes getters begin with
   * "get". See {@link #validateRendererGetters(JClassType)} for a method that
   * guarantees this method will succeed.
   */
  private static List<JMethod> findGetterNames(JClassType owner) {
    List<JMethod> ret = new ArrayList<JMethod>();
    for (JMethod jMethod : owner.getInheritableMethods()) {
      String getterName = jMethod.getName();
      if (getterName.startsWith("get")) {
        ret.add(jMethod);
      }
    }
    return ret;
  }

  /**
   * Scans a class for a method named "render". Returns its parameters except
   * for the first one. See {@link #validateRenderParameters(JClassType)} for a
   * method that guarantees this method will succeed.
   */
  private static JParameter[] findRenderParameters(JClassType owner) {
    JMethod[] methods = owner.getInheritableMethods();
    JMethod renderMethod = null;

    for (JMethod jMethod : methods) {
      if (jMethod.getName().equals("render")) {
        renderMethod = jMethod;
      }
    }

    JParameter[] parameters = renderMethod.getParameters();
    return Arrays.copyOfRange(parameters, 1, parameters.length);
  }

  /**
   * Finds methods annotated with {@code @UiHandler} in a {@code type}.
   */
  private static JMethod[] findUiHandlerMethods(JClassType type) {
    ArrayList<JMethod> result = new ArrayList<JMethod>();
    JMethod[] allMethods = type.getInheritableMethods();

    for (JMethod jMethod : allMethods) {
      if (jMethod.getAnnotation(UiHandler.class) != null) {
        result.add(jMethod);
      }
    }

    return result.toArray(new JMethod[result.size()]);
  }

  private static String formatMethodError(JMethod eventMethod) {
    return "\"" + eventMethod.getReadableDeclaration(true, true, true, true, true) + "\""
        + " of " + eventMethod.getEnclosingType().getQualifiedSourceName();
  }

  /**
   * Determine the field name a getter is trying to retrieve. Assumes getters
   * begin with "get".
   */
  private static String getterToFieldName(String name) {
    String fieldName = name.substring(3);
    return Introspector.decapitalize(fieldName);
  }

  private static String renderMethodParameters(JParameter[] renderParameters) {
    StringBuilder builder = new StringBuilder();

    for (int i = 0; i < renderParameters.length; i++) {
      JParameter parameter = renderParameters[i];
      builder.append("final ");
      builder.append(parameter.getType().getQualifiedSourceName());
      builder.append(" ");
      builder.append(parameter.getName());
      if (i < renderParameters.length - 1) {
        builder.append(", ");
      }
    }

    return builder.toString();
  }

  private final MortalLogger logger;

  /**
   * Class names of parsers for various ui types, keyed by the classname of the
   * UI class they can build.
   */
  private final Map<String, String> elementParsers = new HashMap<String, String>();

  private final List<String> initStatements = new ArrayList<String>();
  private final List<String> statements = new ArrayList<String>();
  private final HandlerEvaluator handlerEvaluator;
  private final MessagesWriter messages;
  private final DesignTimeUtils designTime;
  private final Tokenator tokenator = new Tokenator();

  private final String templatePath;
  private final TypeOracle oracle;
  /**
   * The type we have been asked to generated, e.g. MyUiBinder
   */
  private final JClassType baseClass;

  /**
   * The name of the class we're creating, e.g. MyUiBinderImpl
   */
  private final String implClassName;

  private final JClassType uiOwnerType;

  private final JClassType uiRootType;

  private final JClassType isRenderableClassType;

  private final JClassType lazyDomElementClass;

  private final OwnerClass ownerClass;

  private final FieldManager fieldManager;

  private final HtmlTemplatesWriter htmlTemplates;

  private final ImplicitClientBundle bundleClass;

  private final boolean useLazyWidgetBuilders;

  private final boolean useSafeHtmlTemplates;

  private int domId = 0;

  private int fieldIndex;

  private String gwtPrefix;

  private int renderableStamper = 0;

  private String rendered;
  /**
   * Stack of element variable names that have been attached.
   */
  private final LinkedList<String> attachSectionElements = new LinkedList<String>();
  /**
   * Maps from field element name to the temporary attach record variable name.
   */
  private final Map<String, String> attachedVars = new HashMap<String, String>();

  private int nextAttachVar = 0;
  /**
   * Stack of statements to be executed after we detach the current attach
   * section.
   */
  private final LinkedList<List<String>> detachStatementsStack = new LinkedList<List<String>>();
  private final AttributeParsers attributeParsers;

  private final UiBinderContext uiBinderCtx;

  private final String binderUri;
  private final boolean isRenderer;

  public UiBinderWriter(JClassType baseClass, String implClassName, String templatePath,
      TypeOracle oracle, MortalLogger logger, FieldManager fieldManager,
      MessagesWriter messagesWriter, DesignTimeUtils designTime, UiBinderContext uiBinderCtx,
      boolean useSafeHtmlTemplates, boolean useLazyWidgetBuilders, String binderUri)
      throws UnableToCompleteException {
    this.baseClass = baseClass;
    this.implClassName = implClassName;
    this.oracle = oracle;
    this.logger = logger;
    this.templatePath = templatePath;
    this.fieldManager = fieldManager;
    this.messages = messagesWriter;
    this.designTime = designTime;
    this.uiBinderCtx = uiBinderCtx;
    this.useSafeHtmlTemplates = useSafeHtmlTemplates;
    this.useLazyWidgetBuilders = useLazyWidgetBuilders;
    this.binderUri = binderUri;

    this.htmlTemplates = new HtmlTemplatesWriter(fieldManager, logger);

    // Check for possible misuse 'GWT.create(UiBinder.class)'
    JClassType uibinderItself = oracle.findType(UiBinder.class.getCanonicalName());
    if (uibinderItself.equals(baseClass)) {
      die("You must use a subtype of UiBinder in GWT.create(). E.g.,\n"
          + "  interface Binder extends UiBinder<Widget, MyClass> {}\n"
          + "  GWT.create(Binder.class);");
    }

    JClassType[] uiBinderTypes = baseClass.getImplementedInterfaces();
    if (uiBinderTypes.length == 0) {
      throw new RuntimeException("No implemented interfaces for " + baseClass.getName());
    }
    JClassType uiBinderType = uiBinderTypes[0];

    JClassType[] typeArgs = uiBinderType.isParameterized() == null
        ? new JClassType[0] : uiBinderType.isParameterized().getTypeArgs();

    String binderType = uiBinderType.getName();

    JClassType uiRendererClass = getOracle().findType(UiRenderer.class.getName());
    if (uiBinderType.isAssignableTo(uibinderItself)) {
      if (typeArgs.length < 2) {
        throw new RuntimeException("Root and owner type parameters are required for type %s"
            + binderType);
      }
      uiRootType = typeArgs[0];
      uiOwnerType = typeArgs[1];
      isRenderer = false;
    } else if (uiBinderType.isAssignableTo(uiRendererClass)) {
      if (typeArgs.length >= 1) {
        throw new RuntimeException("UiRenderer is not a parameterizable type in " + binderType);
      }
      if (!useSafeHtmlTemplates) {
        die("Configuration property UiBinder.useSafeHtmlTemplates\n"
            + "  must be set to true to generate a UiRenderer");
      }
      if (!useLazyWidgetBuilders) {
        die("Configuration property UiBinder.useLazyWidgetBuilders\n"
            + "  must be set to true to generate a UiRenderer");
      }

      // UiRenderers do not need owners but UiBinder generation needs some type here
      uiOwnerType = uiBinderType;
      uiRootType = null;
      isRenderer = true;
    } else {
      die(baseClass.getName() + " must implement UiBinder or UiRenderer");
      // This is unreachable in practice, but silences not initialized errors
      throw new UnableToCompleteException();
    }

    isRenderableClassType = oracle.findType(IsRenderable.class.getCanonicalName());
    lazyDomElementClass = oracle.findType(LazyDomElement.class.getCanonicalName());

    ownerClass = new OwnerClass(uiOwnerType, logger, uiBinderCtx);
    bundleClass =
        new ImplicitClientBundle(baseClass.getPackage().getName(), this.implClassName,
            CLIENT_BUNDLE_FIELD, logger);
    handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle, useLazyWidgetBuilders);

    attributeParsers = new AttributeParsers(oracle, fieldManager, logger);
  }

  /**
   * Add a statement to be executed right after the current attached element is
   * detached. This is useful for doing things that might be expensive while the
   * element is attached to the DOM.
   *
   * @param format
   * @param args
   * @see #beginAttachedSection(String)
   */
  public void addDetachStatement(String format, Object... args) {
    detachStatementsStack.getFirst().add(String.format(format, args));
  }

  /**
   * Add a statement to be run after everything has been instantiated, in the
   * style of {@link String#format}.
   */
  public void addInitStatement(String format, Object... params) {
    initStatements.add(formatCode(format, params));
  }

  /**
   * Adds a statement to the block run after fields are declared, in the style
   * of {@link String#format}.
   */
  public void addStatement(String format, Object... args) {
    String code = formatCode(format, args);

    if (useLazyWidgetBuilders) {
      /**
       * I'm intentionally over-simplifying this and assuming that the input
       * comes always in the format: field.somestatement(); Thus, field can be
       * extracted easily and the element parsers don't need to be changed all
       * at once.
       */
      int idx = code.indexOf(".");
      String fieldName = code.substring(0, idx);
      fieldManager.require(fieldName).addStatement(format, args);
    } else {
      statements.add(code);
    }
  }

  /**
   * Begin a section where a new attachable element is being parsed--that is,
   * one that will be constructed as a big innerHTML string, and then briefly
   * attached to the dom to allow fields accessing its to be filled (at the
   * moment, HasHTMLParser, HTMLPanelParser, and DomElementParser.).
   * <p>
   * Succeeding calls made to {@link #ensureAttached} and
   * {@link #ensureCurrentFieldAttached} must refer to children of this element,
   * until {@link #endAttachedSection} is called.
   *
   * @param element Java expression for the generated code that will return the
   *          dom element to be attached.
   */
  public void beginAttachedSection(String element) {
    attachSectionElements.addFirst(element);
    detachStatementsStack.addFirst(new ArrayList<String>());
  }

  /**
   * Declare a field that will hold an Element instance. Returns a token that
   * the caller must set as the id attribute of that element in whatever
   * innerHTML expression will reproduce it at runtime.
   * <P>
   * In the generated code, this token will be replaced by an expression to
   * generate a unique dom id at runtime. Further code will be generated to be
   * run after widgets are instantiated, to use that dom id in a getElementById
   * call and assign the Element instance to its field.
   *
   * @param fieldName The name of the field being declared
   * @param ancestorField The name of fieldName parent
   */
  public String declareDomField(XMLElement source, String fieldName, String ancestorField)
      throws UnableToCompleteException {
    ensureAttached();
    String name = declareDomIdHolder(fieldName);

    if (useLazyWidgetBuilders) {
      // Create and initialize the dom field with LazyDomElement.
      FieldWriter field = fieldManager.require(fieldName);

      /**
       * But if the owner field is an instance of LazyDomElement then the code
       * can be optimized, no cast is needed and the getter doesn't need to be
       * called in its ancestral.
       */
      if (isOwnerFieldLazyDomElement(fieldName)) {
        field.setInitializer(formatCode("new %s(%s)", field.getQualifiedSourceName(),
            fieldManager.convertFieldToGetter(name)));
      } else {

        field.setInitializer(formatCode("new %s(%s).get().cast()",
            LazyDomElement.class.getCanonicalName(), fieldManager.convertFieldToGetter(name)));

        // The dom must be created by its ancestor.
        fieldManager.require(ancestorField).addAttachStatement(
            fieldManager.convertFieldToGetter(fieldName) + ";");
      }
    } else {
      setFieldInitializer(fieldName, "null");
      addInitStatement("%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
          fieldName, name);
      addInitStatement("%s.removeAttribute(\"id\");", fieldName);
    }

    return tokenForStringExpression(source, fieldManager.convertFieldToGetter(name));
  }

  /**
   * Declare a variable that will be filled at runtime with a unique id, safe
   * for use as a dom element's id attribute. For {@code UiRenderer} based code,
   * elements corresponding to a ui:field, need and id initialized to a value
   * that depends on the {@code fieldName}. For all other cases let
   * {@code fieldName} be {@code null}.
   *
   * @param fieldName name of the field corresponding to this variable.
   * @return that variable's name.
   */
  public String declareDomIdHolder(String fieldName) throws UnableToCompleteException {
    String domHolderName = "domId" + domId++;
    FieldWriter domField =
        fieldManager.registerField(FieldWriterType.DOM_ID_HOLDER,
            oracle.findType(String.class.getName()), domHolderName);
    if (isRenderer && fieldName != null) {
      domField.setInitializer("buildInnerId(\"" + fieldName + "\", uiId)");
    } else {
      domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
    }

    return domHolderName;
  }

  /**
   * If this element has a gwt:field attribute, create a field for it of the
   * appropriate type, and return the field name. If no gwt:field attribute is
   * found, do nothing and return null
   *
   * @return The new field name, or null if no field is created
   */
  public String declareFieldIfNeeded(XMLElement elem) throws UnableToCompleteException {
    String fieldName = getFieldName(elem);
    if (fieldName != null) {

      /**
       * We can switch types if useLazyWidgetBuilders is enabled and the
       * respective owner field is a LazyDomElement.
       */
      if (useLazyWidgetBuilders && isOwnerFieldLazyDomElement(fieldName)) {
        fieldManager.registerFieldForLazyDomElement(findFieldType(elem),
            ownerClass.getUiField(fieldName));
      } else {
        fieldManager.registerField(findFieldType(elem), fieldName);
      }
    }
    return fieldName;
  }

  /**
   * Declare a {@link RenderableStamper} instance that will be filled at runtime
   * with a unique token. This instance can then be used to stamp a single
   * {@link IsRenderable}.
   *
   * @return that variable's name.
   */
  public String declareRenderableStamper() throws UnableToCompleteException {
    String renderableStamperName = "renderableStamper" + renderableStamper++;
    FieldWriter domField =
        fieldManager.registerField(FieldWriterType.RENDERABLE_STAMPER,
            oracle.findType(RenderableStamper.class.getName()), renderableStamperName);
    domField.setInitializer(formatCode(
        "new %s(com.google.gwt.dom.client.Document.get().createUniqueId())",
        RenderableStamper.class.getName()));

    return renderableStamperName;
  }

  /**
   * Writes a new SafeHtml template to the generated BinderImpl.
   *
   * @return The invocation of the SafeHtml template function with the arguments
   *         filled in
   */
  public String declareTemplateCall(String html, String fieldName) throws IllegalArgumentException {
    if (!useSafeHtmlTemplates) {
      return '"' + html + '"';
    }
    FieldWriter w = fieldManager.lookup(fieldName);
    HtmlTemplateMethodWriter templateMethod = htmlTemplates.addSafeHtmlTemplate(html, tokenator);
    if (useLazyWidgetBuilders) {
      w.setHtml(templateMethod.getIndirectTemplateCall());
    } else {
      w.setHtml(templateMethod.getDirectTemplateCall());
    }
    return w.getHtml();
  }

  /**
   * Given a string containing tokens returned by
   * {@link #tokenForStringExpression}, {@link #tokenForSafeHtmlExpression} or
   * {@link #declareDomField}, return a string with those tokens replaced by the
   * appropriate expressions. (It is not normally necessary for an
   * {@link XMLElement.Interpreter} or {@link ElementParser} to make this call,
   * as the tokens are typically replaced by the TemplateWriter itself.)
   */
  public String detokenate(String betokened) {
    return tokenator.detokenate(betokened);
  }

  /**
   * Post an error message and halt processing. This method always throws an
   * {@link UnableToCompleteException}
   */
  public void die(String message) throws UnableToCompleteException {
    logger.die(message);
  }

  /**
   * Post an error message and halt processing. This method always throws an
   * {@link UnableToCompleteException}
   */
  public void die(String message, Object... params) throws UnableToCompleteException {
    logger.die(message, params);
  }

  /**
   * Post an error message about a specific XMLElement and halt processing. This
   * method always throws an {@link UnableToCompleteException}
   */
  public void die(XMLElement context, String message, Object... params)
      throws UnableToCompleteException {
    logger.die(context, message, params);
  }

  /**
   * End the current attachable section. This will detach the element if it was
   * ever attached and execute any detach statements.
   *
   * @see #beginAttachedSection(String)
   */
  public void endAttachedSection() {
    String elementVar = attachSectionElements.removeFirst();
    List<String> detachStatements = detachStatementsStack.removeFirst();
    if (attachedVars.containsKey(elementVar)) {
      String attachedVar = attachedVars.remove(elementVar);
      addInitStatement("%s.detach();", attachedVar);
      for (String statement : detachStatements) {
        addInitStatement(statement);
      }
    }
  }

  /**
   * Ensure that the specified element is attached to the DOM.
   *
   * @see #beginAttachedSection(String)
   */
  public void ensureAttached() {
    String attachSectionElement = attachSectionElements.getFirst();
    if (!attachedVars.containsKey(attachSectionElement)) {
      String attachedVar = "attachRecord" + nextAttachVar;
      addInitStatement("UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
          attachedVar, attachSectionElement);
      attachedVars.put(attachSectionElement, attachedVar);
      nextAttachVar++;
    }
  }

  /**
   * Ensure that the specified field is attached to the DOM. The field must hold
   * an object that responds to Element getElement(). Convenience wrapper for
   * {@link #ensureAttached}<code>(field + ".getElement()")</code>.
   *
   * @see #beginAttachedSection(String)
   */
  public void ensureCurrentFieldAttached() {
    ensureAttached();
  }

  /**
   * Finds the JClassType that corresponds to this XMLElement, which must be a
   * Widget or an Element.
   *
   * @throws UnableToCompleteException If no such widget class exists
   * @throws RuntimeException if asked to handle a non-widget, non-DOM element
   */
  public JClassType findFieldType(XMLElement elem) throws UnableToCompleteException {
    String tagName = elem.getLocalName();

    if (!isImportedElement(elem)) {
      return findDomElementTypeForTag(tagName);
    }

    String ns = elem.getNamespaceUri();
    String packageName = ns;
    String className = tagName;

    while (true) {
      JPackage pkg = parseNamespacePackage(packageName);
      if (pkg == null) {
        throw new RuntimeException("No such package: " + packageName);
      }

      JClassType rtn = pkg.findType(className);
      if (rtn != null) {
        return rtn;
      }

      // Try again: shift one element of the class name onto the package name.
      // If the class name has only one element left, fail.
      int index = className.indexOf(".");
      if (index == -1) {
        die(elem, "No class matching \"%s\" in %s", tagName, ns);
      }
      packageName = packageName + "." + className.substring(0, index);
      className = className.substring(index + 1);
    }
  }

  /**
   * Generates the code to set a property value (assumes that 'value' is a valid
   * Java expression).
   */
  public void genPropertySet(String fieldName, String propName, String value) {
    addStatement("%1$s.set%2$s(%3$s);", fieldName, capitalizePropName(propName), value);
  }

  /**
   * Generates the code to set a string property.
   */
  public void genStringPropertySet(String fieldName, String propName, String value) {
    genPropertySet(fieldName, propName, "\"" + value + "\"");
  }

  /**
   * The type we have been asked to generated, e.g. MyUiBinder
   */
  public JClassType getBaseClass() {
    return baseClass;
  }

  public ImplicitClientBundle getBundleClass() {
    return bundleClass;
  }

  /**
   * Returns the {@link DesignTimeUtils}, not <code>null</code>.
   */
  public DesignTimeUtils getDesignTime() {
    return designTime;
  }

  public FieldManager getFieldManager() {
    return fieldManager;
  }

  /**
   * Returns the logger, at least until we get get it handed off to parsers via
   * constructor args.
   */
  public MortalLogger getLogger() {
    return logger;
  }

  /**
   * Get the {@link MessagesWriter} for this UI, generating it if necessary.
   */
  public MessagesWriter getMessages() {
    return messages;
  }

  /**
   * Gets the type oracle.
   */
  public TypeOracle getOracle() {
    return oracle;
  }

  public OwnerClass getOwnerClass() {
    return ownerClass;
  }

  public String getUiFieldAttributeName() {
    return gwtPrefix + ":field";
  }

  public boolean isBinderElement(XMLElement elem) {
    String uri = elem.getNamespaceUri();
    return uri != null && binderUri.equals(uri);
  }

  public boolean isElementAssignableTo(XMLElement elem, Class<?> possibleSuperclass)
      throws UnableToCompleteException {
    JClassType classType = oracle.findType(possibleSuperclass.getCanonicalName());
    return isElementAssignableTo(elem, classType);
  }

  public boolean isElementAssignableTo(XMLElement elem, JClassType possibleSupertype)
      throws UnableToCompleteException {
    /*
     * Things like <W extends IsWidget & IsPlaid>
     */
    JTypeParameter typeParameter = possibleSupertype.isTypeParameter();
    if (typeParameter != null) {
      JClassType[] bounds = typeParameter.getBounds();
      for (JClassType bound : bounds) {
        if (!isElementAssignableTo(elem, bound)) {
          return false;
        }
      }
      return true;
    }

    /*
     * Binder fields are always declared raw, so we're cheating if the user is
     * playing with parameterized types. We're happy enough if the raw types
     * match, and rely on them to make sure the specific types really do work.
     */
    JParameterizedType parameterized = possibleSupertype.isParameterized();
    if (parameterized != null) {
      return isElementAssignableTo(elem, parameterized.getRawType());
    }

    JClassType fieldtype = findFieldType(elem);
    if (fieldtype == null) {
      return false;
    }
    return fieldtype.isAssignableTo(possibleSupertype);
  }

  public boolean isImportedElement(XMLElement elem) {
    String uri = elem.getNamespaceUri();
    return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);
  }

  /**
   * Checks whether the given owner field name is a LazyDomElement or not.
   */
  public boolean isOwnerFieldLazyDomElement(String fieldName) {
    OwnerField ownerField = ownerClass.getUiField(fieldName);
    if (ownerField == null) {
      return false;
    }

    return lazyDomElementClass.isAssignableFrom(ownerField.getType().getRawType());
  }

  public boolean isRenderableElement(XMLElement elem) throws UnableToCompleteException {
    return findFieldType(elem).isAssignableTo(isRenderableClassType);
  }

  public boolean isRenderer() {
    return isRenderer;
  }

  public boolean isWidgetElement(XMLElement elem) throws UnableToCompleteException {
    return isElementAssignableTo(elem, IsWidget.class);
  }

  /**
   * Parses the object associated with the specified element, and returns the
   * field writer that will hold it. The element is likely to make recursive
   * calls back to this method to have its children parsed.
   *
   * @param elem the xml element to be parsed
   * @return the field holder just created
   */
  public FieldWriter parseElementToField(XMLElement elem) throws UnableToCompleteException {
    if (elementParsers.isEmpty()) {
      registerParsers();
    }

    // Get the class associated with this element.
    JClassType type = findFieldType(elem);

    // Declare its field.
    FieldWriter field = declareField(elem, type.getQualifiedSourceName());

    /*
     * Push the field that will hold this widget on top of the parsedFieldStack
     * to ensure that fields registered by its parsers will be noted as
     * dependencies of the new widget. (See registerField.) Also push the
     * element being parsed, so that the fieldManager can hold that info for
     * later error reporting when field reference left hand sides are validated.
     */
    fieldManager.push(elem, field);

    // Give all the parsers a chance to generate their code.
    for (ElementParser parser : getParsersForClass(type)) {
      parser.parse(elem, field.getName(), type, this);
    }
    fieldManager.pop();

    return field;
  }

  /**
   * Gives the writer the initializer to use for this field instead of the
   * default GWT.create call.
   *
   * @throws IllegalStateException if an initializer has already been set
   */
  public void setFieldInitializer(String fieldName, String factoryMethod) {
    fieldManager.lookup(fieldName).setInitializer(factoryMethod);
  }

  /**
   * Instructs the writer to initialize the field with a specific constructor
   * invocation, instead of the default GWT.create call.
   *
   * @param fieldName the field to initialize
   * @param type the type of the field
   * @param args arguments to the constructor call
   */
  public void setFieldInitializerAsConstructor(String fieldName, String... args) {
    JClassType assignableType = fieldManager.lookup(fieldName).getAssignableType();
    setFieldInitializer(fieldName, formatCode("new %s(%s)", assignableType.getQualifiedSourceName(),
        asCommaSeparatedList(args)));
  }

  /**
   * Like {@link #tokenForStringExpression}, but used for runtime expressions
   * that we trust to be safe to interpret at runtime as HTML without escaping,
   * like translated messages with simple formatting. Wrapped in a call to
   * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant} to
   * keep the expression from being escaped by the SafeHtml template.
   *
   * @param expression must resolve to trusted HTML string
   */
  public String tokenForSafeConstant(XMLElement source, String expression) {
    if (!useSafeHtmlTemplates) {
      return tokenForStringExpression(source, expression);
    }

    expression = "SafeHtmlUtils.fromSafeConstant(" + expression + ")";
    htmlTemplates.noteSafeConstant(expression);
    return nextToken(source, expression);
  }

  /**
   * Like {@link #tokenForStringExpression}, but used for runtime
   * {@link com.google.gwt.safehtml.shared.SafeHtml SafeHtml} instances.
   *
   * @param expression must resolve to SafeHtml object
   */
  public String tokenForSafeHtmlExpression(XMLElement source, String expression) {
    if (!useSafeHtmlTemplates) {
      return tokenForStringExpression(source, expression + ".asString()");
    }

    htmlTemplates.noteSafeConstant(expression);
    return nextToken(source, expression);
  }

  /**
   * Like {@link #tokenForStringExpression}, but used for runtime
   * {@link com.google.gwt.safehtml.shared.SafeUri SafeUri} instances.
   *
   * @param expression must resolve to SafeUri object
   */
  public String tokenForSafeUriExpression(XMLElement source, String expression) {
    if (!useSafeHtmlTemplates) {
      return tokenForStringExpression(source, expression);
    }

    htmlTemplates.noteUri(expression);
    return nextToken(source, expression);
  }

  /**
   * Returns a string token that can be used in place the given expression
   * inside any string literals. Before the generated code is written, the
   * expression will be stitched back into the generated code in place of the
   * token, surrounded by plus signs. This is useful in strings to be handed to
   * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
   * other runtime expression in the string.
   *
   * @param expression must resolve to String
   */
  public String tokenForStringExpression(XMLElement source, String expression) {
    return nextToken(source, "\" + " + expression + " + \"");
  }

  public boolean useLazyWidgetBuilders() {
    return useLazyWidgetBuilders;
  }

  /**
   * @return true of SafeHtml integration is in effect
   */
  public boolean useSafeHtmlTemplates() {
    return useSafeHtmlTemplates;
  }

  /**
   * Post a warning message.
   */
  public void warn(String message) {
    logger.warn(message);
  }

  /**
   * Post a warning message.
   */
  public void warn(String message, Object... params) {
    logger.warn(message, params);
  }

  /**
   * Post a warning message.
   */
  public void warn(XMLElement context, String message, Object... params) {
    logger.warn(context, message, params);
  }

  /**
   * Entry point for the code generation logic. It generates the
   * implementation's superstructure, and parses the root widget (leading to all
   * of its children being parsed as well).
   *
   * @param doc TODO
   */
  void parseDocument(Document doc, PrintWriter printWriter) throws UnableToCompleteException {
    Element documentElement = doc.getDocumentElement();
    gwtPrefix = documentElement.lookupPrefix(binderUri);

    XMLElement elem =
        new XMLElementProviderImpl(attributeParsers, oracle, logger, designTime).get(documentElement);
    this.rendered = tokenator.detokenate(parseDocumentElement(elem));
    printWriter.print(rendered);
  }

  private void addElementParser(String gwtClass, String parser) {
    elementParsers.put(gwtClass, parser);
  }

  private void addWidgetParser(String className) {
    String gwtClass = "com.google.gwt.user.client.ui." + className;
    String parser = "com.google.gwt.uibinder.elementparsers." + className + "Parser";
    addElementParser(gwtClass, parser);
  }

  /**
   * Declares a field of the given type name, returning the name of the declared
   * field. If the element has a field or id attribute, use its value.
   * Otherwise, create and return a new, private field name for it.
   */
  private FieldWriter declareField(XMLElement source, String typeName)
      throws UnableToCompleteException {
    JClassType type = oracle.findType(typeName);
    if (type == null) {
      die(source, "Unknown type %s", typeName);
    }

    String fieldName = getFieldName(source);
    if (fieldName == null) {
      // TODO(rjrjr) could collide with user declared name, as is
      // also a worry in HandlerEvaluator. Need a general scheme for
      // anonymous fields. See the note in HandlerEvaluator and do
      // something like that, but in FieldManager.
      fieldName = "f_" + source.getLocalName() + ++fieldIndex;
    }
    fieldName = normalizeFieldName(fieldName);
    return fieldManager.registerField(type, fieldName);
  }

  private void dieGettingEventTypeName(JMethod jMethod, Exception e)
      throws UnableToCompleteException {
    die("Could not obtain DomEvent.Type object for first parameter of %s (%s)",
        formatMethodError(jMethod), e.getMessage());
  }

  /**
   * Ensures that all of the internal data structures are cleaned up correctly
   * at the end of parsing the document.
   *
   * @throws IllegalStateException
   */
  private void ensureAttachmentCleanedUp() {
    if (!attachSectionElements.isEmpty()) {
      throw new IllegalStateException("Attachments not cleaned up: " + attachSectionElements);
    }
    if (!detachStatementsStack.isEmpty()) {
      throw new IllegalStateException("Detach not cleaned up: " + detachStatementsStack);
    }
  }

  /**
   * Evaluate whether all @UiField attributes are also defined in the template.
   * Dies if not.
   */
  private void evaluateUiFields() throws UnableToCompleteException {
    if (designTime.isDesignTime()) {
      return;
    }
    for (OwnerField ownerField : getOwnerClass().getUiFields()) {
      String fieldName = ownerField.getName();
      FieldWriter fieldWriter = fieldManager.lookup(fieldName);

      if (fieldWriter == null) {
        die("Template %s has no %s attribute for %s.%s#%s", templatePath,
            getUiFieldAttributeName(), uiOwnerType.getPackage().getName(), uiOwnerType.getName(),
            fieldName);
      }
    }
  }

  /**
   * Given a DOM tag name, return the corresponding JSO subclass.
   */
  private JClassType findDomElementTypeForTag(String tag) {
    JClassType elementClass = oracle.findType("com.google.gwt.dom.client.Element");
    JClassType[] types = elementClass.getSubtypes();
    for (JClassType type : types) {
      TagName annotation = type.getAnnotation(TagName.class);
      if (annotation != null) {
        for (String annotationTag : annotation.value()) {
          if (annotationTag.equals(tag)) {
            return type;
          }
        }
      }
    }
    return elementClass;
  }

  /**
   * Calls {@code getType().getName()} on subclasses of {@code DomEvent}.
   */
  private String findEventTypeName(JMethod jMethod)
      throws UnableToCompleteException {
    // Get the event class name (i.e. ClickEvent)
    String eventTypeName = jMethod.getParameterTypes()[0].getQualifiedSourceName();

    Class<?> domType;

    // Get the class instance
    try {
      domType = Class.forName(eventTypeName);
    } catch (ClassNotFoundException e) {
      die("Could not find type %s in %s", eventTypeName, formatMethodError(jMethod));
      return null;
    }

    // Reflectively obtain the type (i.e. ClickEvent.getType())
    try {
      return ((Type<?>) domType.getMethod("getType", (Class[]) null).invoke(null,
          (Object[]) null)).getName();
    } catch (IllegalArgumentException e) {
      dieGettingEventTypeName(jMethod, e);
    } catch (SecurityException e) {
      dieGettingEventTypeName(jMethod, e);
    } catch (IllegalAccessException e) {
      dieGettingEventTypeName(jMethod, e);
    } catch (InvocationTargetException e) {
      dieGettingEventTypeName(jMethod, e);
    } catch (NoSuchMethodException e) {
      dieGettingEventTypeName(jMethod, e);
    }
    // Unreachable, but appeases the compiler
    return null;
  }

  /**
   * Use this method to format code. It forces the use of the en-US locale, so
   * that things like decimal format don't get mangled.
   */
  private String formatCode(String format, Object... params) {
    String r = String.format(Locale.US, format, params);
    return r;
  }

  /**
   * Inspects this element for a gwt:field attribute. If one is found, the
   * attribute is consumed and its value returned.
   *
   * @return The field name declared by an element, or null if none is declared
   */
  private String getFieldName(XMLElement elem) throws UnableToCompleteException {
    String fieldName = null;
    boolean hasOldSchoolId = false;
    if (elem.hasAttribute("id") && isWidgetElement(elem)) {
      hasOldSchoolId = true;
      // If an id is specified on the element, use that.
      fieldName = elem.consumeRawAttribute("id");
      warn(elem, "Deprecated use of id=\"%1$s\" for field name. "
          + "Please switch to gwt:field=\"%1$s\" instead. " + "This will soon be a compile error!",
          fieldName);
    }
    if (elem.hasAttribute(getUiFieldAttributeName())) {
      if (hasOldSchoolId) {
        die(elem, "Cannot declare both id and field on the same element");
      }
      fieldName = elem.consumeRawAttribute(getUiFieldAttributeName());
    }
    return fieldName;
  }

  private Class<? extends ElementParser> getParserForClass(JClassType uiClass) {
    // Find the associated parser.
    String uiClassName = uiClass.getQualifiedSourceName();
    String parserClassName = elementParsers.get(uiClassName);
    if (parserClassName == null) {
      return null;
    }

    // And instantiate it.
    try {
      return Class.forName(parserClassName).asSubclass(ElementParser.class);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException("Unable to instantiate parser", e);
    } catch (ClassCastException e) {
      throw new RuntimeException(parserClassName + " must extend ElementParser");
    }
  }

  /**
   * Find a set of element parsers for the given ui type.
   *
   * The list of parsers will be returned in order from most- to least-specific.
   */
  private Iterable<ElementParser> getParsersForClass(JClassType type) {
    List<ElementParser> parsers = new ArrayList<ElementParser>();

    /*
     * Let this non-widget parser go first (it finds <m:attribute/> elements).
     * Any other such should land here too.
     *
     * TODO(rjrjr) Need a scheme to associate these with a namespace uri or
     * something?
     */
    parsers.add(new AttributeMessageParser());
    parsers.add(new UiChildParser(uiBinderCtx));

    for (JClassType curType : getClassHierarchyBreadthFirst(type)) {
      try {
        Class<? extends ElementParser> cls = getParserForClass(curType);
        if (cls != null) {
          ElementParser parser = cls.newInstance();
          parsers.add(parser);
        }
      } catch (InstantiationException e) {
        throw new RuntimeException("Unable to instantiate " + curType.getName(), e);
      } catch (IllegalAccessException e) {
        throw new RuntimeException("Unable to instantiate " + curType.getName(), e);
      }
    }

    parsers.add(new BeanParser(uiBinderCtx));
    parsers.add(new IsEmptyParser());

    return parsers;
  }

  /**
   * Writes a field setter if the field is not provided and the field class is
   * compatible with its respective template field.
   */
  private void maybeWriteFieldSetter(IndentedWriter niceWriter, OwnerField ownerField,
      JClassType templateClass, String templateField) throws UnableToCompleteException {
    JClassType fieldType = ownerField.getType().getRawType();

    if (!ownerField.isProvided()) {
      /*
       * Normally check that the type the template created can be slammed into
       * the @UiField annotated field in the owning class
       */
      if (!templateClass.isAssignableTo(fieldType)) {
        die("In @UiField %s, template field and owner field types don't match: %s is not assignable to %s",
            ownerField.getName(), templateClass.getQualifiedSourceName(),
            fieldType.getQualifiedSourceName());
      }
      /*
       * And initialize the field
       */
      niceWriter.write("owner.%1$s = %2$s;", ownerField.getName(), templateField);
    } else {
      /*
       * But with @UiField(provided=true) the user builds it, so reverse the
       * direction of the assignability check and do no init.
       */
      if (!fieldType.isAssignableTo(templateClass)) {
        die("In UiField(provided = true) %s, template field and field types don't match: "
            + "@UiField(provided=true)%s is not assignable to %s", ownerField.getName(),
            fieldType.getQualifiedSourceName(), templateClass.getQualifiedSourceName());
      }
    }
  }

  private String nextToken(XMLElement source, String expression) {
    String nextToken = tokenator.nextToken(source, expression);
    return nextToken;
  }

  private String normalizeFieldName(String fieldName) {
    // If a field name has a '.' in it, replace it with '$' to make it a legal
    // identifier. This can happen with the field names associated with nested
    // classes.
    return fieldName.replace('.', '$');
  }

  /**
   * Parse the document element and return the source of the Java class that
   * will implement its UiBinder.
   */
  private String parseDocumentElement(XMLElement elem) throws UnableToCompleteException {
    fieldManager.registerFieldOfGeneratedType(oracle.findType(ClientBundle.class.getName()),
        bundleClass.getPackageName(), bundleClass.getClassName(), bundleClass.getFieldName());

    // Allow GWT.create() to init the field, the default behavior

    FieldWriter rootField = new UiBinderParser(this, messages, fieldManager, oracle, bundleClass,
            binderUri, uiBinderCtx).parse(elem);

    fieldManager.validate();

    StringWriter stringWriter = new StringWriter();
    IndentedWriter niceWriter = new IndentedWriter(new PrintWriter(stringWriter));

    if (isRenderer) {
      writeRenderer(niceWriter, rootField);
    } else if (useLazyWidgetBuilders) {
      for (ImplicitCssResource css : bundleClass.getCssMethods()) {
        String fieldName = css.getName();
        FieldWriter cssField = fieldManager.require(fieldName);
        cssField.addStatement("%s.ensureInjected();", fieldName);
      }
      writeBinderForRenderableStrategy(niceWriter, rootField);
    } else {
      writeBinder(niceWriter, rootField);
    }
    ensureAttachmentCleanedUp();
    return stringWriter.toString();
  }

  /**
   * Parses a package uri (e.g., package://com.google...).
   *
   * @throws UnableToCompleteException on bad package name
   */
  private JPackage parseNamespacePackage(String ns) throws UnableToCompleteException {
    if (ns.startsWith(PACKAGE_URI_SCHEME)) {
      String pkgName = ns.substring(PACKAGE_URI_SCHEME.length());

      JPackage pkg = oracle.findPackage(pkgName);
      if (pkg == null) {
        die("Package not found: " + pkgName);
      }

      return pkg;
    }

    return null;
  }

  private void registerParsers() {
    // TODO(rjrjr): Allow third-party parsers to register themselves
    // automagically

    addElementParser("com.google.gwt.dom.client.Element",
        "com.google.gwt.uibinder.elementparsers.DomElementParser");

    // Register widget parsers.
    addWidgetParser("UIObject");
    addWidgetParser("HasText");
    addWidgetParser("HasHTML");
    addWidgetParser("HasTreeItems");
    addWidgetParser("HasWidgets");
    addWidgetParser("HTMLPanel");
    addWidgetParser("AbsolutePanel");
    addWidgetParser("DockPanel");
    addWidgetParser("StackPanel");
    addWidgetParser("DisclosurePanel");
    addWidgetParser("TabPanel");
    addWidgetParser("MenuItem");
    addWidgetParser("MenuBar");
    addWidgetParser("CellPanel");
    addWidgetParser("CustomButton");
    addWidgetParser("DialogBox");
    addWidgetParser("LayoutPanel");
    addWidgetParser("DockLayoutPanel");
    addWidgetParser("StackLayoutPanel");
    addWidgetParser("TabLayoutPanel");
    addWidgetParser("Image");
    addWidgetParser("ListBox");
    addWidgetParser("Grid");
    addWidgetParser("HasAlignment");
    addWidgetParser("DateLabel");
    addWidgetParser("NumberLabel");
    if (useLazyWidgetBuilders) {
      addWidgetParser("LazyPanel");
      addWidgetParser("RenderablePanel");
    }
  }

  /**
   * Validates each {@code eventMethod} (e.g. {@code onBrowserEvent(HandlerType o, NativeEvent e,
   * Element parent, A a, B b, ...)}).
   * <ul>
   * <li> The second parameter type is {@code NativeEvent}
   * <li> The third parameter type is {@code Element}
   * <li> All the handler methods in the type of the first parameter
   *      (any methods annotated with {@code @UiHandler})
   *      have a signature compatible with the {@code eventMethod}
   * </ul>
   */
  private void validateEventMethod(JMethod eventMethod) throws UnableToCompleteException {
    JParameter[] parameters = eventMethod.getParameters();
    if (parameters.length < 3) {
      die("Too few parameters in %s",
          formatMethodError(eventMethod));
    }

    String nativeEventName = NativeEvent.class.getCanonicalName();
    JClassType nativeEventType = oracle.findType(nativeEventName);
    if (!nativeEventType.equals(parameters[1].getType())) {
      die("Second parameter must be of type %s in %s", nativeEventName,
          formatMethodError(eventMethod));
    }

    String elementName = com.google.gwt.dom.client.Element.class.getCanonicalName();
    JClassType elementType = oracle.findType(elementName);
    if (!elementType.equals(parameters[2].getType())) {
      die("Third parameter must be of type %s in %s", elementName,
          formatMethodError(eventMethod));
    }

    if (parameters[0].getType().isClassOrInterface() == null) {
      die("First parameter must be a class or interface in %s",
          formatMethodError(eventMethod));
    }

    JClassType eventReceiver = parameters[0].getType().isClassOrInterface();

    validateEventReceiver(parameters, eventReceiver, eventMethod);
  }

  /**
   * Validates the signature of all methods annotated with {@code @UiHandler}
   * in the {@code eventReceiver} type. All event handlers must have the same signature
   * where:
   * <ul>
   * <li> The annotation must list valid {@code ui:field}s
   * <li> The first parameter must be assignable to
   *      {@link com.google.gwt.event.dom.client.DomEvent DomEvent}
   * <li> If present, the second parameter must be of type
   *      {@link com.google.gwt.dom.client.Element Element}
   * <li> For all other parameters in position {@code n} must be of the same type as
   *      {@code parameters[n + 1]}
   * </ul>
   */
  private void validateEventReceiver(JParameter[] onBrowserEventParameters,
      JClassType eventReceiver, JMethod sourceMethod)
      throws UnableToCompleteException {

    // Pre-compute the expected parameter types (after the first one, that is)
    JType[] onBrowserEventParamTypes = new JType[onBrowserEventParameters.length - 2];

    // If present, second parameter must be an Element
    onBrowserEventParamTypes[0] = oracle.findType(com.google.gwt.dom.client.Element.class
        .getCanonicalName());
    // And the rest must be the same type
    for (int i = 3; i < onBrowserEventParameters.length; i++) {
      onBrowserEventParamTypes[i - 2] = onBrowserEventParameters[i].getType();
    }

    for (JMethod jMethod : eventReceiver.getInheritableMethods()) {
      Class<UiHandler> annotationClass = UiHandler.class;
      UiHandler annotation = jMethod.getAnnotation(annotationClass);
      // Ignore methods not annotated with @UiHandler
      if (annotation == null) {
        continue;
      }
      // Are the fields in @UiHandler known?
      String[] fields = annotation.value();
      if (fields == null) {
        die("@UiHandler returns null from its value in %s",
            formatMethodError(jMethod));
      }
      for (String fieldName : fields) {
        FieldWriter field = fieldManager.lookup(fieldName);
        if (field == null) {
          die("\"%s\" is not a known field name as listed in the @UiHandler annotation in %s",
              fieldName, formatMethodError(jMethod));
        }
      }

      // First parameter
      JParameter[] eventHandlerParameters = jMethod.getParameters();
      JClassType domEventType = oracle.findType(DomEvent.class.getCanonicalName());
      JClassType firstParamType = eventHandlerParameters[0].getType().isClassOrInterface();
      if (firstParamType == null || !firstParamType.isAssignableTo(domEventType)) {
        die("First parameter must be assignable to com.google.gwt.dom.client.DomEvent in %s",
            formatMethodError(jMethod));
      }

      // All others
      if (onBrowserEventParamTypes.length < eventHandlerParameters.length - 1) {
        die("Too many parameters in %s", formatMethodError(jMethod));
      }
      for (int i = 1; i < eventHandlerParameters.length; i++) {
        if (!eventHandlerParameters[i].getType().equals(onBrowserEventParamTypes[i - 1])) {
          die("Parameter %s in %s is not of the same type as parameter %s in %s",
              eventHandlerParameters[i].getName(), formatMethodError(jMethod),
              onBrowserEventParameters[i + 1].getName(),
              formatMethodError(sourceMethod));
        }
      }
    }
  }

  /**
   * Scan the base class for the getter methods. Assumes getters begin with
   * "get" and validates that each corresponds to a field declared with
   * {@code ui:field}, it has a single parameter, the parameter type is
   * assignable to {@code Element} and its return type is assignable to
   * {@code Element}.
   */
  private void validateRendererGetters(JClassType owner) throws UnableToCompleteException {
    for (JMethod jMethod : owner.getInheritableMethods()) {
      String getterName = jMethod.getName();
      if (getterName.startsWith("get")) {
        if (jMethod.getParameterTypes().length != 1) {
          die("Getter %s must have exactly one parameter in %s", getterName,
              owner.getQualifiedSourceName());
        }
        String elementClassName = com.google.gwt.dom.client.Element.class.getCanonicalName();
        JClassType elementType = oracle.findType(elementClassName);
        JClassType getterParamType =
            jMethod.getParameterTypes()[0].getErasedType().isClassOrInterface();

        if (!elementType.isAssignableFrom(getterParamType)) {
          die("Getter %s must have exactly one parameter of type assignable to %s in %s",
              getterName, elementClassName, owner.getQualifiedSourceName());
        }
        String fieldName = getterToFieldName(getterName);
        FieldWriter field = fieldManager.lookup(fieldName);
        if (field == null || !FieldWriterType.DEFAULT.equals(field.getFieldType())) {
          die("%s does not match a \"ui:field='%s'\" declaration in %s", getterName, fieldName,
              owner.getQualifiedSourceName());
        }
      } else if (!getterName.equals("render") && !getterName.equals("onBrowserEvent")
          && !getterName.equals("isParentOrRenderer")) {
        die("Unexpected method \"%s\" found in %s", getterName, owner.getQualifiedSourceName());
      }
    }
  }

  /**
   * Scans a class to validate that it contains a single method called render,
   * which has a {@code void} return type, and its first parameter is of type
   * {@code SafeHtmlBuilder}.
   */
  private void validateRenderParameters(JClassType owner) throws UnableToCompleteException {
    JMethod[] methods = owner.getInheritableMethods();
    JMethod renderMethod = null;

    for (JMethod jMethod : methods) {
      if (jMethod.getName().equals("render")) {
        if (renderMethod == null) {
          renderMethod = jMethod;
        } else {
          die("%s declares more than one method named render", owner.getQualifiedSourceName());
        }
      }
    }

    if (renderMethod == null
        || renderMethod.getParameterTypes().length < 1
        || !renderMethod.getParameterTypes()[0].getErasedType().getQualifiedSourceName().equals(
            SafeHtmlBuilder.class.getCanonicalName())) {
      die("%s does not declare a render(SafeHtmlBuilder ...) method",
          owner.getQualifiedSourceName());
    }
    if (!JPrimitiveType.VOID.equals(renderMethod.getReturnType())) {
      die("%s#render(SafeHtmlBuilder ...) does not return void", owner.getQualifiedSourceName());
    }
  }

  /**
   * Write statements that parsers created via calls to {@link #addStatement}.
   * Such statements will assume that {@link #writeGwtFields} has already been
   * called.
   */
  private void writeAddedStatements(IndentedWriter niceWriter) {
    for (String s : statements) {
      niceWriter.write(s);
    }
  }

  /**
   * Writes the UiBinder's source.
   */
  private void writeBinder(IndentedWriter w, FieldWriter rootField) throws UnableToCompleteException {
    writePackage(w);

    writeImports(w);
    w.newline();

    writeClassOpen(w);
    writeStatics(w);
    w.newline();

    // Create SafeHtml Template
    writeTemplatesInterface(w);
    w.newline();

    // createAndBindUi method
    w.write("public %s createAndBindUi(final %s owner) {",
        uiRootType.getParameterizedQualifiedSourceName(),
        uiOwnerType.getParameterizedQualifiedSourceName());
    w.indent();
    w.newline();

    writeGwtFields(w);
    w.newline();

    designTime.writeAttributes(this);
    writeAddedStatements(w);
    w.newline();

    writeInitStatements(w);
    w.newline();

    writeHandlers(w);
    w.newline();

    writeOwnerFieldSetters(w);

    writeCssInjectors(w);

    w.write("return %s;", rootField.getNextReference());
    w.outdent();
    w.write("}");

    // Close class
    w.outdent();
    w.write("}");
  }

  /**
   * Writes a different optimized UiBinder's source for the renderable strategy.
   */
  private void writeBinderForRenderableStrategy(IndentedWriter w, FieldWriter rootField)
      throws UnableToCompleteException {
    writePackage(w);

    writeImports(w);
    w.newline();

    writeClassOpen(w);
    writeStatics(w);
    w.newline();

    writeTemplatesInterface(w);

    w.newline();

    // createAndBindUi method
    w.write("public %s createAndBindUi(final %s owner) {",
        uiRootType.getParameterizedQualifiedSourceName(),
        uiOwnerType.getParameterizedQualifiedSourceName());
    w.indent();
    w.newline();

    designTime.writeAttributes(this);
    w.newline();

    w.write("return new Widgets(owner).%s;", rootField.getNextReference());
    w.outdent();
    w.write("}");

    // Writes the inner class Widgets.
    w.newline();
    w.write("/**");
    w.write(" * Encapsulates the access to all inner widgets");
    w.write(" */");
    w.write("class Widgets {");
    w.indent();

    String ownerClassType = uiOwnerType.getParameterizedQualifiedSourceName();
    w.write("private final %s owner;", ownerClassType);
    w.newline();

    writeHandlers(w);
    w.newline();

    w.write("public Widgets(final %s owner) {", ownerClassType);
    w.indent();
    w.write("this.owner = owner;");
    fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
    w.outdent();
    w.write("}");
    w.newline();

    htmlTemplates.writeTemplateCallers(w);

    evaluateUiFields();

    fieldManager.writeFieldDefinitions(w, getOracle(), getOwnerClass(), getDesignTime());

    w.outdent();
    w.write("}");

    // Close class
    w.outdent();
    w.write("}");
  }

  private void writeClassOpen(IndentedWriter w) {
    if (!isRenderer) {
      w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
          uiRootType.getParameterizedQualifiedSourceName(),
          uiOwnerType.getParameterizedQualifiedSourceName(),
          baseClass.getParameterizedQualifiedSourceName());
    } else {
      w.write("public class %s extends %s implements %s {", implClassName,
          AbstractUiRenderer.class.getName(),
          baseClass.getParameterizedQualifiedSourceName());
    }
    w.indent();
  }

  private void writeCssInjectors(IndentedWriter w) {
    for (ImplicitCssResource css : bundleClass.getCssMethods()) {
      w.write("%s.%s().ensureInjected();", bundleClass.getFieldName(), css.getName());
    }
    w.newline();
  }

  /**
   * Write declarations for variables or fields to hold elements declared with
   * gwt:field in the template. For those that have not had constructor
   * generation suppressed, emit GWT.create() calls instantiating them (or die
   * if they have no default constructor).
   *
   * @throws UnableToCompleteException on constructor problem
   */
  private void writeGwtFields(IndentedWriter niceWriter) throws UnableToCompleteException {
    // For each provided field in the owner class, initialize from the owner
    Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
    for (OwnerField ownerField : ownerFields) {
      if (ownerField.isProvided()) {
        String fieldName = ownerField.getName();
        FieldWriter fieldWriter = fieldManager.lookup(fieldName);

        // TODO why can this be null?
        if (fieldWriter != null) {
          String initializer;
          if (designTime.isDesignTime()) {
            String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
            initializer = designTime.getProvidedField(typeName, ownerField.getName());
          } else {
            initializer = formatCode("owner.%1$s", fieldName);
          }
          fieldManager.lookup(fieldName).setInitializer(initializer);
        }
      }
    }

    fieldManager.writeGwtFieldsDeclaration(niceWriter);
  }

  private void writeHandlers(IndentedWriter w) throws UnableToCompleteException {
    if (designTime.isDesignTime()) {
      return;
    }
    handlerEvaluator.run(w, fieldManager, "owner");
  }

  private void writeImports(IndentedWriter w) {
    w.write("import com.google.gwt.core.client.GWT;");
    w.write("import com.google.gwt.dom.client.Element;");
    if (!(htmlTemplates.isEmpty())) {
      w.write("import com.google.gwt.safehtml.client.SafeHtmlTemplates;");
      w.write("import com.google.gwt.safehtml.shared.SafeHtml;");
      w.write("import com.google.gwt.safehtml.shared.SafeHtmlUtils;");
      w.write("import com.google.gwt.safehtml.shared.SafeHtmlBuilder;");
      w.write("import com.google.gwt.safehtml.shared.SafeUri;");
      w.write("import com.google.gwt.safehtml.shared.UriUtils;");
      w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
    }

    if (!isRenderer) {
      w.write("import com.google.gwt.uibinder.client.UiBinder;");
      w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
      w.write("import %s.%s;", uiRootType.getPackage().getName(), uiRootType.getName());
    } else {
      w.write("import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;");
    }
  }

  /**
   * Write statements created by {@link #addInitStatement}. This code must be
   * placed after all instantiation code.
   */
  private void writeInitStatements(IndentedWriter niceWriter) {
    for (String s : initStatements) {
      niceWriter.write(s);
    }
  }

  /**
   * Write the statements to fill in the fields of the UI owner.
   */
  private void writeOwnerFieldSetters(IndentedWriter niceWriter) throws UnableToCompleteException {
    if (designTime.isDesignTime()) {
      return;
    }
    for (OwnerField ownerField : getOwnerClass().getUiFields()) {
      String fieldName = ownerField.getName();
      FieldWriter fieldWriter = fieldManager.lookup(fieldName);

      if (fieldWriter != null) {
        // ownerField is a widget.
        JClassType type = fieldWriter.getInstantiableType();
        if (type != null) {
          maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getInstantiableType(),
              fieldName);
        } else {
          // Must be a generated type
          if (!ownerField.isProvided()) {
            niceWriter.write("owner.%1$s = %1$s;", fieldName);
          }
        }

      } else {
        // ownerField was not found as bundle resource or widget, must die.
        die("Template %s has no %s attribute for %s.%s#%s", templatePath,
            getUiFieldAttributeName(), uiOwnerType.getPackage().getName(), uiOwnerType.getName(),
            fieldName);
      }
    }
  }

  private void writePackage(IndentedWriter w) {
    String packageName = baseClass.getPackage().getName();
    if (packageName.length() > 0) {
      w.write("package %1$s;", packageName);
      w.newline();
    }
  }

  /**
   * Writes the UiRenderer's source for the renderable strategy.
   */
  private void writeRenderer(IndentedWriter w, FieldWriter rootField) throws UnableToCompleteException {
    validateRendererGetters(baseClass);
    validateRenderParameters(baseClass);
    JMethod[] eventMethods = findEventMethods(baseClass);
    for (JMethod jMethod : eventMethods) {
      validateEventMethod(jMethod);
    }

    writePackage(w);

    writeImports(w);
    w.newline();

    writeClassOpen(w);
    writeStatics(w);
    w.newline();

    // Create SafeHtml Template
    writeTemplatesInterface(w);
    w.newline();
    htmlTemplates.writeTemplateCallers(w);

    w.newline();

    JParameter[] renderParameters = findRenderParameters(baseClass);

    writeRenderParameterDefinitions(w, renderParameters);

    String renderParameterDeclarations = renderMethodParameters(renderParameters);
    w.write("public void render(final %s sb%s%s) {", SafeHtmlBuilder.class.getName(),
        renderParameterDeclarations.length() != 0 ? ", " : "", renderParameterDeclarations);
    w.indent();
    w.newline();

    writeRenderParameterInitializers(w, renderParameters);

    w.write("uiId = com.google.gwt.dom.client.Document.get().createUniqueId();");
    w.newline();

    fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
    w.newline();

    String safeHtml = rootField.getSafeHtml();

    // TODO(rchandia) it should be possible to add the attribute when parsing
    // the UiBinder file
    w.write(
        "sb.append(stampUiRendererAttribute(%s, RENDERED_ATTRIBUTE, uiId));",
        safeHtml);
    w.outdent();

    w.write("}");
    w.newline();

    fieldManager.writeFieldDefinitions(w, getOracle(), getOwnerClass(), getDesignTime());

    writeRendererGetters(w, baseClass, rootField.getName());

    writeRendererEventMethods(w, eventMethods, rootField.getName());

    // Close class
    w.outdent();
    w.write("}");
  }

  private void writeRendererDispatcher(IndentedWriter w, String dispatcherName,
      JClassType targetType, String rootFieldName, JMethod[] uiHandlerMethods, JMethod sourceMethod)
      throws UnableToCompleteException {

    // static class UiRendererDispatcherForFoo extends UiRendererDispatcher<Foo> {
    w.write("static class %s extends UiRendererDispatcher<%s> {", dispatcherName,
        targetType.getQualifiedSourceName());
    w.indent();

    writeRendererDispatcherTableInit(w, rootFieldName, uiHandlerMethods,
        dispatcherName);

    writeRendererDispatcherExtraParameters(w, sourceMethod);

    writeRendererDispatcherFire(w, sourceMethod);

    w.write("@SuppressWarnings(\"rawtypes\")");
    w.write("@Override");
    // public void fireEvent(GwtEvent<?> somethingUnlikelyToCollideWithParamNames) {
    w.write("public void fireEvent(com.google.gwt.event.shared.GwtEvent<?> %sEvent) {",
        SAFE_VAR_PREFIX);
    w.indent();
    //   switch (getMethodIndex()) {
    w.write("switch (getMethodIndex()) {");
    w.indent();
    for (int j = 0; j < uiHandlerMethods.length; j++) {
      JMethod uiMethod = uiHandlerMethods[j];

      // case 0:
      w.write("case %s:", j);
      w.indent();

      //   getEventTarget().onClickRoot((ClickEvent) somethingUnlikelyToCollideWithParamNames,
      //       getRoot(), a, b);
      StringBuffer sb = new StringBuffer();
      JParameter[] sourceParameters = sourceMethod.getParameters();
      // Cat the extra parameters i.e. ", a, b"
      JType[] uiHandlerParameterTypes = uiMethod.getParameterTypes();
      if (uiHandlerParameterTypes.length >= 2) {
        sb.append(", getRoot()");
      }
      for (int k = 2; k < uiHandlerParameterTypes.length; k++) {
        JParameter sourceParam = sourceParameters[k + 1];
        sb.append(", ");
        sb.append(sourceParam.getName());
      }
      w.write("getEventTarget().%s((%s) %sEvent%s);", uiMethod.getName(),
          uiHandlerParameterTypes[0].getQualifiedSourceName(), SAFE_VAR_PREFIX,
          sb.toString());
      //   break;
      w.write("break;");
      w.newline();
      w.outdent();
    }
    //    default:
    w.write("default:");
    w.indent();
    //      break;
    w.write("break;");
    w.outdent();
    w.outdent();
    w.write("}");

    w.outdent();
    w.write("}");

    w.outdent();
    w.write("}");
  }

  private void writeRendererDispatcherExtraParameters(IndentedWriter w, JMethod sourceMethod) {
    for (int i = 3; i < sourceMethod.getParameters().length; i++) {
      JParameter param = sourceMethod.getParameters()[i];

      // private int a;
      // private String b;
      w.write("private %s %s;", param.getType().getParameterizedQualifiedSourceName(),
          param.getName());
    }
  }

  private void writeRendererDispatcherFire(IndentedWriter w, JMethod sourceMethod) {
    // public void fire(Foo o, NativeEvent e, Element parent, int a, String b) {
    w.write("public void fire(");
    w.indent();
    JParameter[] sourceParameters = sourceMethod.getParameters();
    for (int i = 0; i < sourceParameters.length; i++) {
      JParameter param = sourceParameters[i];
      w.write(i == 0 ? "%s %s" : ", %s %s", param.getType().getQualifiedSourceName(), param.getName());
    }
    w.write(") {");
    w.indent();

    // this.a = a;
    for (int i = 3; i < sourceParameters.length; i++) {
      JParameter sourceParam = sourceParameters[i];
      w.write("this.%s = %s;", sourceParam.getName(), sourceParam.getName());
    }

    // fireEvent(o, e, parent);
    w.write("fireEvent(%s, %s, %s);", sourceParameters[0].getName(), sourceParameters[1].getName(),
        sourceParameters[2].getName());

    w.outdent();
    w.write("}");
    w.newline();
  }

  private void writeRendererDispatcherTableInit(IndentedWriter w,
      String rootFieldName, JMethod[] uiHandlerMethods, String dispatcherName)
      throws UnableToCompleteException {
    ArrayList<String> keys = new ArrayList<String>();
    ArrayList<Integer> values = new ArrayList<Integer>();

    // Collect the event types and field names to form the dispatch table
    for (int i = 0; i < uiHandlerMethods.length; i++) {
      JMethod jMethod = uiHandlerMethods[i];
      String eventType = findEventTypeName(jMethod);
      String[] fieldNames = jMethod.getAnnotation(UiHandler.class).value();
      for (String fieldName : fieldNames) {
        if (rootFieldName.equals(fieldName)) {
          fieldName = AbstractUiRenderer.ROOT_FAKE_NAME;
        }
        keys.add(eventType + AbstractUiRenderer.UI_ID_SEPARATOR + fieldName);
        values.add(i);
      }
    }

    // private static String[] somethingUnlikelyToCollideWithParamNames_keys;
    w.write("private static String[] %s_keys;", SAFE_VAR_PREFIX);
    // private static Integer[] somethingUnlikelyToCollideWithParamNames_values;
    w.write("private static Integer[] %s_values;", SAFE_VAR_PREFIX);

    w.write("static {");
    w.indent();
    // private static String[] somethingUnlikelyToCollideWithParamNames_keys = new String[] {
    w.write("%s_keys = new String[] {", SAFE_VAR_PREFIX);
    w.indent();
    for (String key : keys) {
      // "click:aField",
      w.write("\"%s\",", key);
    }
    w.outdent();
    w.write("};");
    w.newline();

    // somethingUnlikelyToCollideWithParamNames_values = {0,1};
    w.write("%s_values = new Integer[] {", SAFE_VAR_PREFIX);
    w.indent();
    StringBuffer commaSeparatedValues = new StringBuffer();
    for (Integer value : values) {
      commaSeparatedValues.append(value);
      commaSeparatedValues.append(",");
    }
    // "0,0,0,1,1,",
    w.write("%s", commaSeparatedValues.toString());
    w.outdent();
    w.write("};");
    w.newline();

    w.outdent();
    w.write("}");
    w.newline();

    // public Foo() {
    w.write("public %s() {", dispatcherName);
    w.indent();
    // initDispatchTable(keys, values);
    w.write("initDispatchTable(%s_keys, %s_values);", SAFE_VAR_PREFIX, SAFE_VAR_PREFIX);

    // This ensures the DomEvent#TYPE fields are properly initialized and registered
    // ClickEvent.getType();
    HashSet<String> eventTypes = new HashSet<String>();
    for (JMethod uiMethod : uiHandlerMethods) {
      eventTypes.add(uiMethod.getParameterTypes()[0].getQualifiedSourceName());
    }
    for (String eventType : eventTypes) {
      w.write("%s.getType();", eventType);
    }


    w.outdent();
    w.write("}");
    w.newline();
  }

  private void writeRendererEventMethods(IndentedWriter w, JMethod[] eventMethods,
      String rootField) throws UnableToCompleteException {
    for (JMethod jMethod : eventMethods) {
      JClassType eventTargetType = jMethod.getParameterTypes()[0].isClassOrInterface();
      String eventTargetSimpleName = eventTargetType.getSimpleSourceName();
      String dispatcherClassName = UI_RENDERER_DISPATCHER_PREFIX + eventTargetSimpleName;
      JMethod[] uiHandlerMethods = findUiHandlerMethods(eventTargetType);

      // public void onBrowserEvent(Foo f, NativeEvent event, Element parent, A a, B b ...) {
      w.write("@Override");
      w.write("public %s {", jMethod.getReadableDeclaration(true, true, true, true, true));

      if (uiHandlerMethods.length != 0) {
        w.indent();
        //  if (singletonUiRendererDispatcherForFoo == null) {
        w.write("if (singleton%s == null) {", dispatcherClassName);
        w.indent();
        // singletonUiRendererDispatcherForFoo = new UiRendererDispatcherForFoo();
        w.write("singleton%s = new %s();", dispatcherClassName, dispatcherClassName);

        w.outdent();
        w.write("}");

        // singletonUiRendererDispatcherForFoo.fire(o, event, parent, a, b);
        StringBuffer sb = new StringBuffer();
        JParameter[] parameters = jMethod.getParameters();
        for (int i = 0; i < parameters.length; i++) {
          JParameter callParam = parameters[i];
          if (i != 0) {
            sb.append(", ");
          }
          sb.append(callParam.getName());
        }
        w.write("singleton%s.fire(%s);", dispatcherClassName, sb.toString());
        w.outdent();
      }

      w.write("}");
      w.newline();

      if (uiHandlerMethods.length != 0) {
        // private static UiRendererDispatcherForFoo singletonUiRendererDispatcherForFoo;
        w.write("private static %s singleton%s;", dispatcherClassName, dispatcherClassName);

        writeRendererDispatcher(w, dispatcherClassName, eventTargetType, rootField, uiHandlerMethods,
            jMethod);
      }
    }
  }

  private void writeRendererGetters(IndentedWriter w, JClassType owner, String rootFieldName) {
    List<JMethod> getters = findGetterNames(owner);

    // For every requested getter
    for (JMethod getter : getters) {
      // public ElementSubclass getFoo(Element parent) {
      w.write("%s {", getter.getReadableDeclaration(false, false, false, false, true));
      w.indent();
      String elementParameter = getter.getParameters()[0].getName();
      String getterFieldName = getterToFieldName(getter.getName());
      // The non-root elements are found by id
      if (!getterFieldName.equals(rootFieldName)) {
        // return (ElementSubclass) findUiField(parent);
        w.write("return (%s) findInnerField(%s, \"%s\", RENDERED_ATTRIBUTE);",
            getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter,
            getterFieldName);
      } else {
        // return (ElementSubclass) findPreviouslyRendered(parent);
        w.write("return (%s) findRootElement(%s, RENDERED_ATTRIBUTE);",
            getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter);
      }
      w.outdent();
      w.write("}");
    }
  }

  private void writeRenderParameterDefinitions(IndentedWriter w, JParameter[] renderParameters) {
    for (int i = 0; i < renderParameters.length; i++) {
      JParameter parameter = renderParameters[i];
      w.write("private %s %s%s;", parameter.getType().getQualifiedSourceName(),
          RENDER_PARAM_HOLDER_PREFIX, parameter.getName());
      w.newline();
    }
  }

  private void writeRenderParameterInitializers(IndentedWriter w, JParameter[] renderParameters) {
    for (int i = 0; i < renderParameters.length; i++) {
      JParameter parameter = renderParameters[i];
      w.write("%s%s = %s;", RENDER_PARAM_HOLDER_PREFIX, parameter.getName(), parameter.getName());
      w.newline();
    }
  }

  private void writeStaticMessagesInstance(IndentedWriter niceWriter) {
    if (messages.hasMessages()) {
      niceWriter.write(messages.getDeclaration());
    }
  }

  private void writeStatics(IndentedWriter w) {
    writeStaticMessagesInstance(w);
    designTime.addDeclarations(w);
  }

  /**
   * Write statements created by {@link HtmlTemplatesWriter#addSafeHtmlTemplate}
   * . This code must be placed after all instantiation code.
   */
  private void writeTemplatesInterface(IndentedWriter w) {
    if (!(htmlTemplates.isEmpty())) {
      assert useSafeHtmlTemplates : "SafeHtml is off, but templates were made.";
      htmlTemplates.writeInterface(w);
      w.newline();
    }
  }
}
TOP

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

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.