View Javadoc

1   /*
2    * Copyright 2008 Google Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package com.google.gwt.uibinder.rebind;
17  
18  import java.io.PrintWriter;
19  import java.io.StringWriter;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  
28  import org.vectomatic.dom.svg.OMSVGElement;
29  import org.vectomatic.dom.svg.utils.SVGConstants;
30  import org.w3c.dom.Document;
31  import org.w3c.dom.Element;
32  
33  import com.google.gwt.core.ext.UnableToCompleteException;
34  import com.google.gwt.core.ext.typeinfo.JClassType;
35  import com.google.gwt.core.ext.typeinfo.JMethod;
36  import com.google.gwt.core.ext.typeinfo.JParameter;
37  import com.google.gwt.core.ext.typeinfo.JParameterizedType;
38  import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
39  import com.google.gwt.core.ext.typeinfo.JType;
40  import com.google.gwt.core.ext.typeinfo.JTypeParameter;
41  import com.google.gwt.core.ext.typeinfo.TypeOracle;
42  import com.google.gwt.dev.resource.ResourceOracle;
43  import com.google.gwt.dom.client.NativeEvent;
44  import com.google.gwt.dom.client.TagName;
45  import com.google.gwt.event.dom.client.DomEvent;
46  import com.google.gwt.event.dom.client.DomEvent.Type;
47  import com.google.gwt.resources.client.ClientBundle;
48  import com.google.gwt.resources.rg.GssResourceGenerator.GssOptions;
49  import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
50  import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
51  import com.google.gwt.uibinder.client.ElementParserToUse;
52  import com.google.gwt.uibinder.client.LazyDomElement;
53  import com.google.gwt.uibinder.client.UiBinder;
54  import com.google.gwt.uibinder.client.UiHandler;
55  import com.google.gwt.uibinder.client.UiRenderer;
56  import com.google.gwt.uibinder.client.impl.AbstractUiRenderer;
57  import com.google.gwt.uibinder.elementparsers.AttributeMessageParser;
58  import com.google.gwt.uibinder.elementparsers.BeanParser;
59  import com.google.gwt.uibinder.elementparsers.ElementParser;
60  import com.google.gwt.uibinder.elementparsers.IsEmptyParser;
61  import com.google.gwt.uibinder.elementparsers.UiChildParser;
62  import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
63  import com.google.gwt.uibinder.rebind.model.HtmlTemplateMethodWriter;
64  import com.google.gwt.uibinder.rebind.model.HtmlTemplatesWriter;
65  import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
66  import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
67  import com.google.gwt.uibinder.rebind.model.OwnerClass;
68  import com.google.gwt.uibinder.rebind.model.OwnerField;
69  import com.google.gwt.user.client.ui.IsRenderable;
70  import com.google.gwt.user.client.ui.IsWidget;
71  import com.google.gwt.user.client.ui.RenderableStamper;
72  
73  import org.w3c.dom.Document;
74  import org.w3c.dom.Element;
75  
76  import java.beans.Introspector;
77  import java.io.PrintWriter;
78  import java.io.StringWriter;
79  import java.lang.reflect.InvocationTargetException;
80  import java.util.ArrayList;
81  import java.util.Arrays;
82  import java.util.Collection;
83  import java.util.HashMap;
84  import java.util.HashSet;
85  import java.util.Iterator;
86  import java.util.LinkedList;
87  import java.util.List;
88  import java.util.Locale;
89  import java.util.Map;
90  
91  /**
92   * Writer for UiBinder generated classes.
93   */
94  public class UiBinderWriter implements Statements {
95  
96    private static final String SAFE_VAR_PREFIX =
97      "somethingUnlikelyToCollideWithParamNamesWefio";
98  
99    private static final String UI_RENDERER_DISPATCHER_PREFIX = "UiRendererDispatcherFor";
100 
101   private static final String PACKAGE_URI_SCHEME = "urn:import:";
102 
103   // TODO(rjrjr) Another place that we need a general anonymous field
104   // mechanism
105   private static final String CLIENT_BUNDLE_FIELD =
106       "clientBundleFieldNameUnlikelyToCollideWithUserSpecifiedFieldOkay";
107 
108   public static String asCommaSeparatedList(String... args) {
109     StringBuilder b = new StringBuilder();
110     for (String arg : args) {
111       if (b.length() > 0) {
112         b.append(", ");
113       }
114       b.append(arg);
115     }
116 
117     return b.toString();
118   }
119 
120   /**
121    * Escape text that will be part of a string literal to be interpreted at
122    * runtime as an HTML attribute value.
123    */
124   public static String escapeAttributeText(String text) {
125     text = escapeText(text, false);
126 
127     /*
128      * Escape single-quotes to make them safe to be interpreted at runtime as an
129      * HTML attribute value (for which we by convention use single quotes).
130      */
131     text = text.replaceAll("'", "'");
132     return text;
133   }
134 
135   /**
136    * Escape text that will be part of a string literal to be interpreted at
137    * runtime as HTML, optionally preserving whitespace.
138    */
139   public static String escapeText(String text, boolean preserveWhitespace) {
140     // Replace reserved XML characters with entities. Note that we *don't*
141     // replace single- or double-quotes here, because they're safe in text
142     // nodes.
143     text = text.replaceAll("&", "&");
144     text = text.replaceAll("<", "&lt;");
145     text = text.replaceAll(">", "&gt;");
146 
147     if (!preserveWhitespace) {
148       text = text.replaceAll("\\s+", " ");
149     }
150 
151     return escapeTextForJavaStringLiteral(text);
152   }
153 
154   /**
155    * Escape characters that would mess up interpretation of this string as a
156    * string literal in generated code (that is, protect \, \n and " ).
157    */
158   public static String escapeTextForJavaStringLiteral(String text) {
159     text = text.replace("\\", "\\\\");
160     text = text.replace("\"", "\\\"");
161     text = text.replace("\n", "\\n");
162 
163     return text;
164   }
165 
166   /**
167    * Returns a list of the given type and all its superclasses and implemented
168    * interfaces in a breadth-first traversal.
169    *
170    * @param type the base type
171    * @return a breadth-first collection of its type hierarchy
172    */
173   static Iterable<JClassType> getClassHierarchyBreadthFirst(JClassType type) {
174     LinkedList<JClassType> list = new LinkedList<JClassType>();
175     LinkedList<JClassType> q = new LinkedList<JClassType>();
176 
177     q.add(type);
178     while (!q.isEmpty()) {
179       // Pop the front of the queue and add it to the result list.
180       JClassType curType = q.removeFirst();
181       list.add(curType);
182 
183       // Add implemented interfaces to the back of the queue (breadth first,
184       // remember?)
185       for (JClassType intf : curType.getImplementedInterfaces()) {
186         q.add(intf);
187       }
188       // Add then add superclasses
189       JClassType superClass = curType.getSuperclass();
190       if (superClass != null) {
191         q.add(superClass);
192       }
193     }
194 
195     return list;
196   }
197 
198   private static String capitalizePropName(String propName) {
199     return propName.substring(0, 1).toUpperCase(Locale.ROOT) + propName.substring(1);
200   }
201 
202   /**
203    * Searches for methods named onBrowserEvent in a {@code type}.
204    */
205   private static JMethod[] findEventMethods(JClassType type) {
206     List<JMethod> methods = new ArrayList<JMethod>(Arrays.asList(type.getInheritableMethods()));
207 
208     for (Iterator<JMethod> iterator = methods.iterator(); iterator.hasNext();) {
209       JMethod jMethod = iterator.next();
210       if (!jMethod.getName().equals("onBrowserEvent")) {
211         iterator.remove();
212       }
213     }
214 
215     return methods.toArray(new JMethod[methods.size()]);
216   }
217 
218   /**
219    * Scan the base class for the getter methods. Assumes getters begin with
220    * "get". See {@link #validateRendererGetters(JClassType)} for a method that
221    * guarantees this method will succeed.
222    */
223   private static List<JMethod> findGetterNames(JClassType owner) {
224     List<JMethod> ret = new ArrayList<JMethod>();
225     for (JMethod jMethod : owner.getInheritableMethods()) {
226       String getterName = jMethod.getName();
227       if (getterName.startsWith("get")) {
228         ret.add(jMethod);
229       }
230     }
231     return ret;
232   }
233 
234   /**
235    * Scans a class for a method named "render". Returns its parameters except
236    * for the first one. See {@link #validateRenderParameters(JClassType)} for a
237    * method that guarantees this method will succeed.
238    */
239   private static JParameter[] findRenderParameters(JClassType owner) {
240     JMethod[] methods = owner.getInheritableMethods();
241     JMethod renderMethod = null;
242 
243     for (JMethod jMethod : methods) {
244       if (jMethod.getName().equals("render")) {
245         renderMethod = jMethod;
246       }
247     }
248 
249     JParameter[] parameters = renderMethod.getParameters();
250     return Arrays.copyOfRange(parameters, 1, parameters.length);
251   }
252 
253   /**
254    * Finds methods annotated with {@code @UiHandler} in a {@code type}.
255    */
256   private static JMethod[] findUiHandlerMethods(JClassType type) {
257     ArrayList<JMethod> result = new ArrayList<JMethod>();
258     JMethod[] allMethods = type.getInheritableMethods();
259 
260     for (JMethod jMethod : allMethods) {
261       if (jMethod.getAnnotation(UiHandler.class) != null) {
262         result.add(jMethod);
263       }
264     }
265 
266     return result.toArray(new JMethod[result.size()]);
267   }
268 
269   private static String formatMethodError(JMethod eventMethod) {
270     return "\"" + eventMethod.getReadableDeclaration(true, true, true, true, true) + "\""
271         + " of " + eventMethod.getEnclosingType().getQualifiedSourceName();
272   }
273 
274   /**
275    * Determine the field name a getter is trying to retrieve. Assumes getters
276    * begin with "get".
277    */
278   private static String getterToFieldName(String name) {
279     String fieldName = name.substring(3);
280     return Introspector.decapitalize(fieldName);
281   }
282 
283   private static String renderMethodParameters(JParameter[] renderParameters) {
284     StringBuilder builder = new StringBuilder();
285 
286     for (int i = 0; i < renderParameters.length; i++) {
287       JParameter parameter = renderParameters[i];
288       builder.append("final ");
289       builder.append(parameter.getType().getQualifiedSourceName());
290       builder.append(" ");
291       builder.append(parameter.getName());
292       if (i < renderParameters.length - 1) {
293         builder.append(", ");
294       }
295     }
296 
297     return builder.toString();
298   }
299 
300   private final MortalLogger logger;
301 
302   /**
303    * Class names of parsers for various ui types, keyed by the classname of the
304    * UI class they can build.
305    */
306   private final Map<String, String> elementParsers = new HashMap<String, String>();
307 
308   private final List<String> initStatements = new ArrayList<String>();
309   private final List<String> statements = new ArrayList<String>();
310   private final HandlerEvaluator handlerEvaluator;
311   private final MessagesWriter messages;
312   private final DesignTimeUtils designTime;
313   private final Tokenator tokenator = new Tokenator();
314 
315   private final String templatePath;
316   private final TypeOracle oracle;
317   /**
318    * The type we have been asked to generated, e.g. MyUiBinder
319    */
320   private final JClassType baseClass;
321 
322   /**
323    * The name of the class we're creating, e.g. MyUiBinderImpl
324    */
325   private final String implClassName;
326 
327   private final JClassType uiOwnerType;
328 
329   private final JClassType uiRootType;
330 
331   private final JClassType isRenderableClassType;
332 
333   private final JClassType lazyDomElementClass;
334 
335   private final OwnerClass ownerClass;
336 
337   private final FieldManager fieldManager;
338 
339   private final HtmlTemplatesWriter htmlTemplates;
340 
341   private final ImplicitClientBundle bundleClass;
342 
343   private final boolean useLazyWidgetBuilders;
344 
345   private final boolean useSafeHtmlTemplates;
346 
347   private int domId = 0;
348 
349   private int fieldIndex;
350 
351   private String gwtPrefix;
352 
353   private int renderableStamper = 0;
354 
355   private String rendered;
356   /**
357    * Stack of element variable names that have been attached.
358    */
359   private final LinkedList<String> attachSectionElements = new LinkedList<String>();
360   /**
361    * Maps from field element name to the temporary attach record variable name.
362    */
363   private final Map<String, String> attachedVars = new HashMap<String, String>();
364 
365   private int nextAttachVar = 0;
366   /**
367    * Stack of statements to be executed after we detach the current attach
368    * section.
369    */
370   private final LinkedList<List<String>> detachStatementsStack = new LinkedList<List<String>>();
371   private final AttributeParsers attributeParsers;
372 
373   private final UiBinderContext uiBinderCtx;
374 
375   private final String binderUri;
376   private final boolean isRenderer;
377 
378   private final ResourceOracle resourceOracle;
379 
380   private final GssOptions gssOptions;
381 
382   public UiBinderWriter(JClassType baseClass, String implClassName, String templatePath,
383       TypeOracle oracle, MortalLogger logger, FieldManager fieldManager,
384       MessagesWriter messagesWriter, DesignTimeUtils designTime, UiBinderContext uiBinderCtx,
385       boolean useSafeHtmlTemplates, boolean useLazyWidgetBuilders, String binderUri,
386       ResourceOracle resourceOracle, GssOptions gssOptions) throws UnableToCompleteException {
387     this.baseClass = baseClass;
388     this.implClassName = implClassName;
389     this.oracle = oracle;
390     this.logger = logger;
391     this.templatePath = templatePath;
392     this.fieldManager = fieldManager;
393     this.messages = messagesWriter;
394     this.designTime = designTime;
395     this.uiBinderCtx = uiBinderCtx;
396     this.useSafeHtmlTemplates = useSafeHtmlTemplates;
397     this.useLazyWidgetBuilders = useLazyWidgetBuilders;
398     this.binderUri = binderUri;
399     this.resourceOracle = resourceOracle;
400     this.gssOptions = gssOptions;
401 
402     this.htmlTemplates = new HtmlTemplatesWriter(fieldManager, logger);
403 
404     // Check for possible misuse 'GWT.create(UiBinder.class)'
405     JClassType uibinderItself = oracle.findType(UiBinder.class.getCanonicalName());
406     if (uibinderItself.equals(baseClass)) {
407       die("You must use a subtype of UiBinder in GWT.create(). E.g.,\n"
408           + "  interface Binder extends UiBinder<Widget, MyClass> {}\n"
409           + "  GWT.create(Binder.class);");
410     }
411 
412     JClassType[] uiBinderTypes = baseClass.getImplementedInterfaces();
413     if (uiBinderTypes.length == 0) {
414       throw new RuntimeException("No implemented interfaces for " + baseClass.getName());
415     }
416     JClassType uiBinderType = uiBinderTypes[0];
417 
418     JClassType[] typeArgs = uiBinderType.isParameterized() == null
419         ? new JClassType[0] : uiBinderType.isParameterized().getTypeArgs();
420 
421     String binderType = uiBinderType.getName();
422 
423     JClassType uiRendererClass = getOracle().findType(UiRenderer.class.getName());
424     if (uiBinderType.isAssignableTo(uibinderItself)) {
425       if (typeArgs.length < 2) {
426         throw new RuntimeException("Root and owner type parameters are required for type %s"
427             + binderType);
428       }
429       uiRootType = typeArgs[0];
430       uiOwnerType = typeArgs[1];
431       isRenderer = false;
432     } else if (uiBinderType.isAssignableTo(uiRendererClass)) {
433       if (typeArgs.length >= 1) {
434         throw new RuntimeException("UiRenderer is not a parameterizable type in " + binderType);
435       }
436       if (!useSafeHtmlTemplates) {
437         die("Configuration property UiBinder.useSafeHtmlTemplates\n"
438             + "  must be set to true to generate a UiRenderer");
439       }
440       if (!useLazyWidgetBuilders) {
441         die("Configuration property UiBinder.useLazyWidgetBuilders\n"
442             + "  must be set to true to generate a UiRenderer");
443       }
444 
445       // UiRenderers do not need owners but UiBinder generation needs some type here
446       uiOwnerType = uiBinderType;
447       uiRootType = null;
448       isRenderer = true;
449     } else {
450       die(baseClass.getName() + " must implement UiBinder or UiRenderer");
451       // This is unreachable in practice, but silences not initialized errors
452       throw new UnableToCompleteException();
453     }
454 
455     isRenderableClassType = oracle.findType(IsRenderable.class.getCanonicalName());
456     lazyDomElementClass = oracle.findType(LazyDomElement.class.getCanonicalName());
457 
458     ownerClass = new OwnerClass(uiOwnerType, logger, uiBinderCtx);
459     bundleClass =
460         new ImplicitClientBundle(baseClass.getPackage().getName(), this.implClassName,
461             CLIENT_BUNDLE_FIELD, logger);
462     handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle, useLazyWidgetBuilders);
463 
464     attributeParsers = new AttributeParsers(oracle, fieldManager, logger);
465   }
466 
467   /**
468    * Add a statement to be executed right after the current attached element is
469    * detached. This is useful for doing things that might be expensive while the
470    * element is attached to the DOM.
471    *
472    * @param format
473    * @param args
474    * @see #beginAttachedSection(String)
475    */
476   public void addDetachStatement(String format, Object... args) {
477     detachStatementsStack.getFirst().add(String.format(format, args));
478   }
479 
480   /**
481    * Add a statement to be run after everything has been instantiated, in the
482    * style of {@link String#format}.
483    */
484   public void addInitStatement(String format, Object... params) {
485     initStatements.add(formatCode(format, params));
486   }
487 
488   /**
489    * Adds a statement to the block run after fields are declared, in the style
490    * of {@link String#format}.
491    */
492   public void addStatement(String format, Object... args) {
493     String code = formatCode(format, args);
494 
495     if (useLazyWidgetBuilders) {
496       /**
497        * I'm intentionally over-simplifying this and assuming that the input
498        * comes always in the format: field.somestatement(); Thus, field can be
499        * extracted easily and the element parsers don't need to be changed all
500        * at once.
501        */
502       int idx = code.indexOf(".");
503       String fieldName = code.substring(0, idx);
504       fieldManager.require(fieldName).addStatement(format, args);
505     } else {
506       statements.add(code);
507     }
508   }
509 
510   /**
511    * Begin a section where a new attachable element is being parsed--that is,
512    * one that will be constructed as a big innerHTML string, and then briefly
513    * attached to the dom to allow fields accessing its to be filled (at the
514    * moment, HasHTMLParser, HTMLPanelParser, and DomElementParser.).
515    * <p>
516    * Succeeding calls made to {@link #ensureAttached} and
517    * {@link #ensureCurrentFieldAttached} must refer to children of this element,
518    * until {@link #endAttachedSection} is called.
519    *
520    * @param element Java expression for the generated code that will return the
521    *          dom element to be attached.
522    */
523   public void beginAttachedSection(String element) {
524     attachSectionElements.addFirst(element);
525     detachStatementsStack.addFirst(new ArrayList<String>());
526   }
527 
528   /**
529    * Declare a field that will hold an Element instance. Returns a token that
530    * the caller must set as the id attribute of that element in whatever
531    * innerHTML expression will reproduce it at runtime.
532    * <P>
533    * In the generated code, this token will be replaced by an expression to
534    * generate a unique dom id at runtime. Further code will be generated to be
535    * run after widgets are instantiated, to use that dom id in a getElementById
536    * call and assign the Element instance to its field.
537    *
538    * @param fieldName The name of the field being declared
539    * @param ancestorField The name of fieldName parent
540    */
541   public String declareDomField(XMLElement source, String fieldName, String ancestorField)
542       throws UnableToCompleteException {
543     ensureAttached();
544     String name = declareDomIdHolder(fieldName);
545 
546     if (useLazyWidgetBuilders) {
547       // Create and initialize the dom field with LazyDomElement.
548       FieldWriter field = fieldManager.require(fieldName);
549 
550       /**
551        * But if the owner field is an instance of LazyDomElement then the code
552        * can be optimized, no cast is needed and the getter doesn't need to be
553        * called in its ancestral.
554        */
555       if (isOwnerFieldLazyDomElement(fieldName)) {
556         field.setInitializer(formatCode("new %s(%s)", field.getQualifiedSourceName(),
557             fieldManager.convertFieldToGetter(name)));
558       } else {
559 
560         field.setInitializer(formatCode("new %s(%s).get().cast()",
561             LazyDomElement.class.getCanonicalName(), fieldManager.convertFieldToGetter(name)));
562 
563         // The dom must be created by its ancestor.
564         fieldManager.require(ancestorField).addAttachStatement(
565             fieldManager.convertFieldToGetter(fieldName) + ";");
566       }
567     } else {
568       setFieldInitializer(fieldName, "null");
569       addInitStatement("%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
570           fieldName, name);
571       addInitStatement("%s.removeAttribute(\"id\");", fieldName);
572     }
573 
574     return tokenForStringExpression(source, fieldManager.convertFieldToGetter(name));
575   }
576 
577   /**
578    * Declare a variable that will be filled at runtime with a unique id, safe
579    * for use as a dom element's id attribute. For {@code UiRenderer} based code,
580    * elements corresponding to a ui:field, need and id initialized to a value
581    * that depends on the {@code fieldName}. For all other cases let
582    * {@code fieldName} be {@code null}.
583    *
584    * @param fieldName name of the field corresponding to this variable.
585    * @return that variable's name.
586    */
587   public String declareDomIdHolder(String fieldName) throws UnableToCompleteException {
588     String domHolderName = "domId" + domId++;
589     FieldWriter domField =
590         fieldManager.registerField(FieldWriterType.DOM_ID_HOLDER,
591             oracle.findType(String.class.getName()), domHolderName);
592     if (isRenderer && fieldName != null) {
593       domField.setInitializer("buildInnerId(\"" + fieldName + "\", uiId)");
594     } else {
595       domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
596     }
597 
598     return domHolderName;
599   }
600 
601   /**
602    * If this element has a gwt:field attribute, create a field for it of the
603    * appropriate type, and return the field name. If no gwt:field attribute is
604    * found, do nothing and return null
605    *
606    * @return The new field name, or null if no field is created
607    */
608   public String declareFieldIfNeeded(XMLElement elem) throws UnableToCompleteException {
609     String fieldName = getFieldName(elem);
610     if (fieldName != null) {
611 
612       /**
613        * We can switch types if useLazyWidgetBuilders is enabled and the
614        * respective owner field is a LazyDomElement.
615        */
616       if (useLazyWidgetBuilders && isOwnerFieldLazyDomElement(fieldName)) {
617         fieldManager.registerFieldForLazyDomElement(findFieldType(elem),
618             ownerClass.getUiField(fieldName));
619       } else {
620         fieldManager.registerField(findFieldType(elem), fieldName);
621       }
622     }
623     return fieldName;
624   }
625 
626   /**
627    * Declare a {@link RenderableStamper} instance that will be filled at runtime
628    * with a unique token. This instance can then be used to stamp a single
629    * {@link IsRenderable}.
630    *
631    * @return that variable's name.
632    */
633   public String declareRenderableStamper() throws UnableToCompleteException {
634     String renderableStamperName = "renderableStamper" + renderableStamper++;
635     FieldWriter domField =
636         fieldManager.registerField(FieldWriterType.RENDERABLE_STAMPER,
637             oracle.findType(RenderableStamper.class.getName()), renderableStamperName);
638     domField.setInitializer(formatCode(
639         "new %s(com.google.gwt.dom.client.Document.get().createUniqueId())",
640         RenderableStamper.class.getName()));
641 
642     return renderableStamperName;
643   }
644 
645   /**
646    * Writes a new SafeHtml template to the generated BinderImpl.
647    *
648    * @return The invocation of the SafeHtml template function with the arguments
649    *         filled in
650    */
651   public String declareTemplateCall(String html, String fieldName) throws IllegalArgumentException {
652     if (!useSafeHtmlTemplates) {
653       return '"' + html + '"';
654     }
655     FieldWriter w = fieldManager.lookup(fieldName);
656     HtmlTemplateMethodWriter templateMethod = htmlTemplates.addSafeHtmlTemplate(html, tokenator);
657     if (useLazyWidgetBuilders) {
658       w.setHtml(templateMethod.getIndirectTemplateCall());
659     } else {
660       w.setHtml(templateMethod.getDirectTemplateCall());
661     }
662     return w.getHtml();
663   }
664 
665   /**
666    * Given a string containing tokens returned by
667    * {@link #tokenForStringExpression}, {@link #tokenForSafeHtmlExpression} or
668    * {@link #declareDomField}, return a string with those tokens replaced by the
669    * appropriate expressions. (It is not normally necessary for an
670    * {@link XMLElement.Interpreter} or {@link ElementParser} to make this call,
671    * as the tokens are typically replaced by the TemplateWriter itself.)
672    */
673   public String detokenate(String betokened) {
674     return tokenator.detokenate(betokened);
675   }
676 
677   /**
678    * Post an error message and halt processing. This method always throws an
679    * {@link UnableToCompleteException}
680    */
681   public void die(String message) throws UnableToCompleteException {
682     logger.die(message);
683   }
684 
685   /**
686    * Post an error message and halt processing. This method always throws an
687    * {@link UnableToCompleteException}
688    */
689   public void die(String message, Object... params) throws UnableToCompleteException {
690     logger.die(message, params);
691   }
692 
693   /**
694    * Post an error message about a specific XMLElement and halt processing. This
695    * method always throws an {@link UnableToCompleteException}
696    */
697   public void die(XMLElement context, String message, Object... params)
698       throws UnableToCompleteException {
699     logger.die(context, message, params);
700   }
701 
702   /**
703    * End the current attachable section. This will detach the element if it was
704    * ever attached and execute any detach statements.
705    *
706    * @see #beginAttachedSection(String)
707    */
708   public void endAttachedSection() {
709     String elementVar = attachSectionElements.removeFirst();
710     List<String> detachStatements = detachStatementsStack.removeFirst();
711     if (attachedVars.containsKey(elementVar)) {
712       String attachedVar = attachedVars.remove(elementVar);
713       addInitStatement("%s.detach();", attachedVar);
714       for (String statement : detachStatements) {
715         addInitStatement(statement);
716       }
717     }
718   }
719 
720   /**
721    * Ensure that the specified element is attached to the DOM.
722    *
723    * @see #beginAttachedSection(String)
724    */
725   public void ensureAttached() {
726     String attachSectionElement = attachSectionElements.getFirst();
727     if (!attachedVars.containsKey(attachSectionElement)) {
728       String attachedVar = "attachRecord" + nextAttachVar;
729       addInitStatement("UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
730           attachedVar, attachSectionElement);
731       attachedVars.put(attachSectionElement, attachedVar);
732       nextAttachVar++;
733     }
734   }
735 
736   /**
737    * Ensure that the specified field is attached to the DOM. The field must hold
738    * an object that responds to Element getElement(). Convenience wrapper for
739    * {@link #ensureAttached}<code>(field + ".getElement()")</code>.
740    *
741    * @see #beginAttachedSection(String)
742    */
743   public void ensureCurrentFieldAttached() {
744     ensureAttached();
745   }
746 
747   /**
748    * Finds the JClassType that corresponds to this XMLElement, which must be a
749    * Widget or an Element.
750    *
751    * @throws UnableToCompleteException If no such widget class exists
752    * @throws RuntimeException if asked to handle a non-widget, non-DOM element
753    */
754   public JClassType findFieldType(XMLElement elem) throws UnableToCompleteException {
755     String tagName = elem.getLocalName();
756 
757     // laaglu
758     String uri = elem.getNamespaceUri();
759     if (SVGConstants.SVG_NAMESPACE_URI.equals(uri)) {
760     	return findSvgDomElementTypeForTag(tagName);
761     }
762     // laaglu
763 
764     if (!isImportedElement(elem)) {
765       return findDomElementTypeForTag(tagName);
766     }
767 
768     String ns = elem.getNamespaceUri();
769     String packageName = ns.substring(PACKAGE_URI_SCHEME.length());
770     String className = tagName;
771 
772     while (true) {
773       JClassType rtn = getOracle().findType(packageName + "." + className);
774       if (rtn != null) {
775         return rtn;
776       }
777 
778       // Try again: shift one element of the class name onto the package name.
779       // If the class name has only one element left, fail.
780       int index = className.indexOf(".");
781       if (index == -1) {
782         die(elem, "No class matching \"%s\" in %s", tagName, ns);
783       }
784       packageName = packageName + "." + className.substring(0, index);
785       className = className.substring(index + 1);
786     }
787   }
788 
789   // laaglu
790   /**
791    * Given a SVG tag name, return the corresponding
792    * {@link org.vectomatic.dom.svg.OMSVGElement} subclass.
793    */
794   private JClassType findSvgDomElementTypeForTag(String tag) {
795     JClassType elementClass = oracle.findType(OMSVGElement.class.getName());
796     JClassType[] types = elementClass.getSubtypes();
797     for (JClassType type : types) {
798       TagName annotation = type.getAnnotation(TagName.class);
799       if (annotation != null) {
800         for (String annotationTag : annotation.value()) {
801           if (annotationTag.equals(tag)) {
802             return type;
803           }
804         }
805       }
806     }
807 
808     return elementClass;
809   }
810   // laaglu
811 
812   /**
813    * Generates the code to set a property value (assumes that 'value' is a valid
814    * Java expression).
815    */
816   public void genPropertySet(String fieldName, String propName, String value) {
817     addStatement("%1$s.set%2$s(%3$s);", fieldName, capitalizePropName(propName), value);
818   }
819 
820   /**
821    * Generates the code to set a string property.
822    */
823   public void genStringPropertySet(String fieldName, String propName, String value) {
824     genPropertySet(fieldName, propName, "\"" + value + "\"");
825   }
826 
827   /**
828    * The type we have been asked to generated, e.g. MyUiBinder
829    */
830   public JClassType getBaseClass() {
831     return baseClass;
832   }
833 
834   public ImplicitClientBundle getBundleClass() {
835     return bundleClass;
836   }
837 
838   /**
839    * Returns the {@link DesignTimeUtils}, not <code>null</code>.
840    */
841   public DesignTimeUtils getDesignTime() {
842     return designTime;
843   }
844 
845   public FieldManager getFieldManager() {
846     return fieldManager;
847   }
848 
849   /**
850    * Returns the logger, at least until we get get it handed off to parsers via
851    * constructor args.
852    */
853   public MortalLogger getLogger() {
854     return logger;
855   }
856 
857   /**
858    * Get the {@link MessagesWriter} for this UI, generating it if necessary.
859    */
860   public MessagesWriter getMessages() {
861     return messages;
862   }
863 
864   /**
865    * Gets the type oracle.
866    */
867   public TypeOracle getOracle() {
868     return oracle;
869   }
870 
871   public OwnerClass getOwnerClass() {
872     return ownerClass;
873   }
874 
875   public String getUiFieldAttributeName() {
876     return gwtPrefix + ":field";
877   }
878 
879   public boolean isBinderElement(XMLElement elem) {
880     String uri = elem.getNamespaceUri();
881     return uri != null && binderUri.equals(uri);
882   }
883 
884   public boolean isElementAssignableTo(XMLElement elem, Class<?> possibleSuperclass)
885       throws UnableToCompleteException {
886     JClassType classType = oracle.findType(possibleSuperclass.getCanonicalName());
887     return isElementAssignableTo(elem, classType);
888   }
889 
890   public boolean isElementAssignableTo(XMLElement elem, JClassType possibleSupertype)
891       throws UnableToCompleteException {
892     /*
893      * Things like <W extends IsWidget & IsPlaid>
894      */
895     JTypeParameter typeParameter = possibleSupertype.isTypeParameter();
896     if (typeParameter != null) {
897       JClassType[] bounds = typeParameter.getBounds();
898       for (JClassType bound : bounds) {
899         if (!isElementAssignableTo(elem, bound)) {
900           return false;
901         }
902       }
903       return true;
904     }
905 
906     /*
907      * Binder fields are always declared raw, so we're cheating if the user is
908      * playing with parameterized types. We're happy enough if the raw types
909      * match, and rely on them to make sure the specific types really do work.
910      */
911     JParameterizedType parameterized = possibleSupertype.isParameterized();
912     if (parameterized != null) {
913       return isElementAssignableTo(elem, parameterized.getRawType());
914     }
915 
916     JClassType fieldtype = findFieldType(elem);
917     if (fieldtype == null) {
918       return false;
919     }
920     return fieldtype.isAssignableTo(possibleSupertype);
921   }
922 
923   public boolean isImportedElement(XMLElement elem) {
924     String uri = elem.getNamespaceUri();
925     return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);
926   }
927 
928   /**
929    * Checks whether the given owner field name is a LazyDomElement or not.
930    */
931   public boolean isOwnerFieldLazyDomElement(String fieldName) {
932     OwnerField ownerField = ownerClass.getUiField(fieldName);
933     if (ownerField == null) {
934       return false;
935     }
936 
937     return lazyDomElementClass.isAssignableFrom(ownerField.getType().getRawType());
938   }
939 
940   public boolean isRenderableElement(XMLElement elem) throws UnableToCompleteException {
941     return findFieldType(elem).isAssignableTo(isRenderableClassType);
942   }
943 
944   public boolean isRenderer() {
945     return isRenderer;
946   }
947 
948   public boolean isWidgetElement(XMLElement elem) throws UnableToCompleteException {
949     return isElementAssignableTo(elem, IsWidget.class);
950   }
951 
952   /**
953    * Parses the object associated with the specified element, and returns the
954    * field writer that will hold it. The element is likely to make recursive
955    * calls back to this method to have its children parsed.
956    *
957    * @param elem the xml element to be parsed
958    * @return the field holder just created
959    */
960   public FieldWriter parseElementToField(XMLElement elem) throws UnableToCompleteException {
961     if (elementParsers.isEmpty()) {
962       registerParsers();
963     }
964 
965     // Get the class associated with this element.
966     JClassType type = findFieldType(elem);
967 
968     // Declare its field.
969     FieldWriter field = declareField(elem, type.getQualifiedSourceName());
970 
971     /*
972      * Push the field that will hold this widget on top of the parsedFieldStack
973      * to ensure that fields registered by its parsers will be noted as
974      * dependencies of the new widget. (See registerField.) Also push the
975      * element being parsed, so that the fieldManager can hold that info for
976      * later error reporting when field reference left hand sides are validated.
977      */
978     fieldManager.push(elem, field);
979 
980     // Give all the parsers a chance to generate their code.
981     for (ElementParser parser : getParsersForClass(type)) {
982       parser.parse(elem, field.getName(), type, this);
983     }
984     fieldManager.pop();
985 
986     return field;
987   }
988 
989   /**
990    * Gives the writer the initializer to use for this field instead of the
991    * default GWT.create call.
992    *
993    * @throws IllegalStateException if an initializer has already been set
994    */
995   public void setFieldInitializer(String fieldName, String factoryMethod) {
996     fieldManager.lookup(fieldName).setInitializer(factoryMethod);
997   }
998 
999   /**
1000    * Instructs the writer to initialize the field with a specific constructor
1001    * invocation, instead of the default GWT.create call.
1002    *
1003    * @param fieldName the field to initialize
1004    * @param args arguments to the constructor call
1005    */
1006   public void setFieldInitializerAsConstructor(String fieldName, String... args) {
1007     JClassType assignableType = fieldManager.lookup(fieldName).getAssignableType();
1008     setFieldInitializer(fieldName, formatCode("new %s(%s)", assignableType.getQualifiedSourceName(),
1009         asCommaSeparatedList(args)));
1010   }
1011 
1012   /**
1013    * Like {@link #tokenForStringExpression}, but used for runtime expressions
1014    * that we trust to be safe to interpret at runtime as HTML without escaping,
1015    * like translated messages with simple formatting. Wrapped in a call to
1016    * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant} to
1017    * keep the expression from being escaped by the SafeHtml template.
1018    *
1019    * @param expression must resolve to trusted HTML string
1020    */
1021   public String tokenForSafeConstant(XMLElement source, String expression) {
1022     if (!useSafeHtmlTemplates) {
1023       return tokenForStringExpression(source, expression);
1024     }
1025 
1026     expression = "SafeHtmlUtils.fromSafeConstant(" + expression + ")";
1027     htmlTemplates.noteSafeConstant(expression);
1028     return nextToken(source, expression);
1029   }
1030 
1031   /**
1032    * Like {@link #tokenForStringExpression}, but used for runtime
1033    * {@link com.google.gwt.safehtml.shared.SafeHtml SafeHtml} instances.
1034    *
1035    * @param expression must resolve to SafeHtml object
1036    */
1037   public String tokenForSafeHtmlExpression(XMLElement source, String expression) {
1038     if (!useSafeHtmlTemplates) {
1039       return tokenForStringExpression(source, expression + ".asString()");
1040     }
1041 
1042     htmlTemplates.noteSafeConstant(expression);
1043     return nextToken(source, expression);
1044   }
1045 
1046   /**
1047    * Like {@link #tokenForStringExpression}, but used for runtime
1048    * {@link com.google.gwt.safehtml.shared.SafeUri SafeUri} instances.
1049    *
1050    * @param expression must resolve to SafeUri object
1051    */
1052   public String tokenForSafeUriExpression(XMLElement source, String expression) {
1053     if (!useSafeHtmlTemplates) {
1054       return tokenForStringExpression(source, expression);
1055     }
1056 
1057     htmlTemplates.noteUri(expression);
1058     return nextToken(source, expression);
1059   }
1060 
1061   /**
1062    * Returns a string token that can be used in place the given expression
1063    * inside any string literals. Before the generated code is written, the
1064    * expression will be stitched back into the generated code in place of the
1065    * token, surrounded by plus signs. This is useful in strings to be handed to
1066    * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
1067    * other runtime expression in the string.
1068    *
1069    * @param expression must resolve to String
1070    */
1071   public String tokenForStringExpression(XMLElement source, String expression) {
1072     return nextToken(source, "\" + " + expression + " + \"");
1073   }
1074 
1075   public boolean useLazyWidgetBuilders() {
1076     return useLazyWidgetBuilders;
1077   }
1078 
1079   /**
1080    * @return true of SafeHtml integration is in effect
1081    */
1082   public boolean useSafeHtmlTemplates() {
1083     return useSafeHtmlTemplates;
1084   }
1085 
1086   /**
1087    * Post a warning message.
1088    */
1089   public void warn(String message) {
1090     logger.warn(message);
1091   }
1092 
1093   /**
1094    * Post a warning message.
1095    */
1096   public void warn(String message, Object... params) {
1097     logger.warn(message, params);
1098   }
1099 
1100   /**
1101    * Post a warning message.
1102    */
1103   public void warn(XMLElement context, String message, Object... params) {
1104     logger.warn(context, message, params);
1105   }
1106 
1107   /**
1108    * Entry point for the code generation logic. It generates the
1109    * implementation's superstructure, and parses the root widget (leading to all
1110    * of its children being parsed as well).
1111    *
1112    * @param doc TODO
1113    */
1114   void parseDocument(Document doc, PrintWriter printWriter) throws UnableToCompleteException {
1115     Element documentElement = doc.getDocumentElement();
1116     gwtPrefix = documentElement.lookupPrefix(binderUri);
1117 
1118     XMLElement elem =
1119         new XMLElementProviderImpl(attributeParsers, oracle, logger, designTime).get(documentElement);
1120     this.rendered = tokenator.detokenate(parseDocumentElement(elem));
1121     printWriter.print(rendered);
1122   }
1123 
1124   private void addElementParser(String gwtClass, String parser) {
1125     elementParsers.put(gwtClass, parser);
1126   }
1127 
1128   private void addWidgetParser(String className) {
1129     String gwtClass = "com.google.gwt.user.client.ui." + className;
1130     String parser = "com.google.gwt.uibinder.elementparsers." + className + "Parser";
1131     addElementParser(gwtClass, parser);
1132   }
1133 
1134   /**
1135    * Declares a field of the given type name, returning the name of the declared
1136    * field. If the element has a field or id attribute, use its value.
1137    * Otherwise, create and return a new, private field name for it.
1138    */
1139   private FieldWriter declareField(XMLElement source, String typeName)
1140       throws UnableToCompleteException {
1141     JClassType type = oracle.findType(typeName);
1142     if (type == null) {
1143       die(source, "Unknown type %s", typeName);
1144     }
1145 
1146     String fieldName = getFieldName(source);
1147     if (fieldName == null) {
1148       // TODO(rjrjr) could collide with user declared name, as is
1149       // also a worry in HandlerEvaluator. Need a general scheme for
1150       // anonymous fields. See the note in HandlerEvaluator and do
1151       // something like that, but in FieldManager.
1152       fieldName = "f_" + source.getLocalName() + ++fieldIndex;
1153     }
1154     fieldName = normalizeFieldName(fieldName);
1155     return fieldManager.registerField(type, fieldName);
1156   }
1157 
1158   private void dieGettingEventTypeName(JMethod jMethod, Exception e)
1159       throws UnableToCompleteException {
1160     die("Could not obtain DomEvent.Type object for first parameter of %s (%s)",
1161         formatMethodError(jMethod), e.getMessage());
1162   }
1163 
1164   /**
1165    * Ensures that all of the internal data structures are cleaned up correctly
1166    * at the end of parsing the document.
1167    *
1168    * @throws IllegalStateException
1169    */
1170   private void ensureAttachmentCleanedUp() {
1171     if (!attachSectionElements.isEmpty()) {
1172       throw new IllegalStateException("Attachments not cleaned up: " + attachSectionElements);
1173     }
1174     if (!detachStatementsStack.isEmpty()) {
1175       throw new IllegalStateException("Detach not cleaned up: " + detachStatementsStack);
1176     }
1177   }
1178 
1179   /**
1180    * Add call to {@code com.google.gwt.resources.client.CssResource#ensureInjected()}
1181    * on each CSS resource field.
1182    */
1183   private void ensureInjectedCssFields() {
1184     for (ImplicitCssResource css : bundleClass.getCssMethods()) {
1185       String fieldName = css.getName();
1186       FieldWriter cssField = fieldManager.require(fieldName);
1187       cssField.addStatement("%s.ensureInjected();", fieldName);
1188     }
1189   }
1190 
1191   /**
1192    * Evaluate whether all @UiField attributes are also defined in the template.
1193    * Dies if not.
1194    */
1195   private void evaluateUiFields() throws UnableToCompleteException {
1196     if (designTime.isDesignTime()) {
1197       return;
1198     }
1199     for (OwnerField ownerField : getOwnerClass().getUiFields()) {
1200       String fieldName = ownerField.getName();
1201       FieldWriter fieldWriter = fieldManager.lookup(fieldName);
1202 
1203       if (fieldWriter == null) {
1204         die("Template %s has no %s attribute for %s.%s#%s", templatePath,
1205             getUiFieldAttributeName(), uiOwnerType.getPackage().getName(), uiOwnerType.getName(),
1206             fieldName);
1207       }
1208     }
1209   }
1210 
1211   /**
1212    * Given a DOM tag name, return the corresponding JSO subclass.
1213    */
1214   private JClassType findDomElementTypeForTag(String tag) {
1215     JClassType elementClass = oracle.findType("com.google.gwt.dom.client.Element");
1216     JClassType[] types = elementClass.getSubtypes();
1217     for (JClassType type : types) {
1218       TagName annotation = type.getAnnotation(TagName.class);
1219       if (annotation != null) {
1220         for (String annotationTag : annotation.value()) {
1221           if (annotationTag.equals(tag)) {
1222             return type;
1223           }
1224         }
1225       }
1226     }
1227     return elementClass;
1228   }
1229 
1230   /**
1231    * Calls {@code getType().getName()} on subclasses of {@code DomEvent}.
1232    */
1233   private String findEventTypeName(JMethod jMethod)
1234       throws UnableToCompleteException {
1235     // Get the event class name (i.e. ClickEvent)
1236     String eventTypeName = jMethod.getParameterTypes()[0].getQualifiedSourceName();
1237 
1238     Class<?> domType;
1239 
1240     // Get the class instance
1241     try {
1242       domType = Class.forName(eventTypeName);
1243     } catch (ClassNotFoundException e) {
1244       die("Could not find type %s in %s", eventTypeName, formatMethodError(jMethod));
1245       return null;
1246     }
1247 
1248     // Reflectively obtain the type (i.e. ClickEvent.getType())
1249     try {
1250       return ((Type<?>) domType.getMethod("getType", (Class[]) null).invoke(null,
1251           (Object[]) null)).getName();
1252     } catch (IllegalArgumentException e) {
1253       dieGettingEventTypeName(jMethod, e);
1254     } catch (SecurityException e) {
1255       dieGettingEventTypeName(jMethod, e);
1256     } catch (IllegalAccessException e) {
1257       dieGettingEventTypeName(jMethod, e);
1258     } catch (InvocationTargetException e) {
1259       dieGettingEventTypeName(jMethod, e);
1260     } catch (NoSuchMethodException e) {
1261       dieGettingEventTypeName(jMethod, e);
1262     }
1263     // Unreachable, but appeases the compiler
1264     return null;
1265   }
1266 
1267   /**
1268    * Use this method to format code. It forces the use of the en-US locale, so
1269    * that things like decimal format don't get mangled.
1270    */
1271   private String formatCode(String format, Object... params) {
1272     String r = String.format(Locale.US, format, params);
1273     return r;
1274   }
1275 
1276   /**
1277    * Inspects this element for a gwt:field attribute. If one is found, the
1278    * attribute is consumed and its value returned.
1279    *
1280    * @return The field name declared by an element, or null if none is declared
1281    */
1282   private String getFieldName(XMLElement elem) throws UnableToCompleteException {
1283     String fieldName = null;
1284     boolean hasOldSchoolId = false;
1285     if (elem.hasAttribute("id") && isWidgetElement(elem)) {
1286       hasOldSchoolId = true;
1287       // If an id is specified on the element, use that.
1288       fieldName = elem.consumeRawAttribute("id");
1289       warn(elem, "Deprecated use of id=\"%1$s\" for field name. "
1290           + "Please switch to gwt:field=\"%1$s\" instead. " + "This will soon be a compile error!",
1291           fieldName);
1292     }
1293     if (elem.hasAttribute(getUiFieldAttributeName())) {
1294       if (hasOldSchoolId) {
1295         die(elem, "Cannot declare both id and field on the same element");
1296       }
1297       fieldName = elem.consumeRawAttribute(getUiFieldAttributeName());
1298     }
1299     return fieldName;
1300   }
1301 
1302   // laaglu
1303   private String getAnnotatedParserForClass(JClassType uiClass) {
1304 	    String parserClassName = null;
1305 	    if (uiClass.isAnnotationPresent(ElementParserToUse.class)) {
1306 	      String uiClassName = uiClass.getQualifiedSourceName();
1307 	      parserClassName = uiClass.getAnnotation(ElementParserToUse.class).className();
1308 	      elementParsers.put(uiClassName, parserClassName);
1309 	    }
1310 	    return parserClassName;
1311 	  }
1312   // laaglu
1313 
1314   private Class<? extends ElementParser> getParserForClass(JClassType uiClass) {
1315     // Find the associated parser.
1316     String uiClassName = uiClass.getQualifiedSourceName();
1317     String parserClassName = elementParsers.get(uiClassName);
1318     if (parserClassName == null) {
1319         // laaglu
1320         parserClassName = getAnnotatedParserForClass(uiClass);
1321         if (parserClassName == null) {
1322           return null;
1323         }
1324         // laaglu
1325     }
1326 
1327     // And instantiate it.
1328     try {
1329       return Class.forName(parserClassName).asSubclass(ElementParser.class);
1330     } catch (ClassNotFoundException e) {
1331       throw new RuntimeException("Unable to instantiate parser", e);
1332     } catch (ClassCastException e) {
1333       throw new RuntimeException(parserClassName + " must extend ElementParser");
1334     }
1335   }
1336 
1337   /**
1338    * Find a set of element parsers for the given ui type.
1339    *
1340    * The list of parsers will be returned in order from most- to least-specific.
1341    */
1342   private Iterable<ElementParser> getParsersForClass(JClassType type) {
1343     List<ElementParser> parsers = new ArrayList<ElementParser>();
1344 
1345     /*
1346      * Let this non-widget parser go first (it finds <m:attribute/> elements).
1347      * Any other such should land here too.
1348      *
1349      * TODO(rjrjr) Need a scheme to associate these with a namespace uri or
1350      * something?
1351      */
1352     parsers.add(new AttributeMessageParser());
1353     parsers.add(new UiChildParser(uiBinderCtx));
1354 
1355     for (JClassType curType : getClassHierarchyBreadthFirst(type)) {
1356       try {
1357         Class<? extends ElementParser> cls = getParserForClass(curType);
1358         if (cls != null) {
1359           ElementParser parser = cls.newInstance();
1360           parsers.add(parser);
1361         }
1362       } catch (InstantiationException e) {
1363         throw new RuntimeException("Unable to instantiate " + curType.getName(), e);
1364       } catch (IllegalAccessException e) {
1365         throw new RuntimeException("Unable to instantiate " + curType.getName(), e);
1366       }
1367     }
1368 
1369     parsers.add(new BeanParser(uiBinderCtx));
1370     parsers.add(new IsEmptyParser());
1371 
1372     return parsers;
1373   }
1374 
1375   /**
1376    * Writes a field setter if the field is not provided and the field class is
1377    * compatible with its respective template field.
1378    */
1379   private void maybeWriteFieldSetter(IndentedWriter niceWriter, OwnerField ownerField,
1380       JClassType templateClass, String templateField) throws UnableToCompleteException {
1381     JClassType fieldType = ownerField.getType().getRawType();
1382 
1383     if (!ownerField.isProvided()) {
1384       /*
1385        * Normally check that the type the template created can be slammed into
1386        * the @UiField annotated field in the owning class
1387        */
1388       if (!templateClass.isAssignableTo(fieldType)) {
1389         die("In @UiField %s, template field and owner field types don't match: %s is not assignable to %s",
1390             ownerField.getName(), templateClass.getQualifiedSourceName(),
1391             fieldType.getQualifiedSourceName());
1392       }
1393       /*
1394        * And initialize the field
1395        */
1396       niceWriter.write("owner.%1$s = %2$s;", ownerField.getName(), templateField);
1397     } else {
1398       /*
1399        * But with @UiField(provided=true) the user builds it, so reverse the
1400        * direction of the assignability check and do no init.
1401        */
1402       if (!fieldType.isAssignableTo(templateClass)) {
1403         die("In UiField(provided = true) %s, template field and field types don't match: "
1404             + "@UiField(provided=true)%s is not assignable to %s", ownerField.getName(),
1405             fieldType.getQualifiedSourceName(), templateClass.getQualifiedSourceName());
1406       }
1407     }
1408   }
1409 
1410   private String nextToken(XMLElement source, String expression) {
1411     String nextToken = tokenator.nextToken(source, expression);
1412     return nextToken;
1413   }
1414 
1415   private String normalizeFieldName(String fieldName) {
1416     // If a field name has a '.' in it, replace it with '$' to make it a legal
1417     // identifier. This can happen with the field names associated with nested
1418     // classes.
1419     return fieldName.replace('.', '$');
1420   }
1421 
1422   /**
1423    * Parse the document element and return the source of the Java class that
1424    * will implement its UiBinder.
1425    */
1426   private String parseDocumentElement(XMLElement elem) throws UnableToCompleteException {
1427     fieldManager.registerFieldOfGeneratedType(oracle.findType(ClientBundle.class.getName()),
1428         bundleClass.getPackageName(), bundleClass.getClassName(), bundleClass.getFieldName());
1429 
1430     // Allow GWT.create() to init the field, the default behavior
1431 
1432     FieldWriter rootField = new UiBinderParser(this, messages, fieldManager, oracle, bundleClass,
1433             binderUri, uiBinderCtx, resourceOracle, gssOptions).parse(elem);
1434 
1435     fieldManager.validate();
1436 
1437     StringWriter stringWriter = new StringWriter();
1438     IndentedWriter niceWriter = new IndentedWriter(new PrintWriter(stringWriter));
1439 
1440     if (isRenderer) {
1441       ensureInjectedCssFields();
1442       writeRenderer(niceWriter, rootField);
1443     } else if (useLazyWidgetBuilders) {
1444       ensureInjectedCssFields();
1445       writeBinderForRenderableStrategy(niceWriter, rootField);
1446     } else {
1447       writeBinder(niceWriter, rootField);
1448     }
1449     ensureAttachmentCleanedUp();
1450     return stringWriter.toString();
1451   }
1452 
1453   private void registerParsers() {
1454     // TODO(rjrjr): Allow third-party parsers to register themselves
1455     // automagically
1456 
1457     addElementParser("com.google.gwt.dom.client.Element",
1458         "com.google.gwt.uibinder.elementparsers.DomElementParser");
1459 
1460     // Register widget parsers.
1461     addWidgetParser("UIObject");
1462     addWidgetParser("HasText");
1463     addWidgetParser("HasHTML");
1464     addWidgetParser("HasTreeItems");
1465     addWidgetParser("HasWidgets");
1466     addWidgetParser("HTMLPanel");
1467     addWidgetParser("FlowPanel");
1468     addWidgetParser("AbsolutePanel");
1469     addWidgetParser("DockPanel");
1470     addWidgetParser("StackPanel");
1471     addWidgetParser("DisclosurePanel");
1472     addWidgetParser("TabPanel");
1473     addWidgetParser("MenuItem");
1474     addWidgetParser("MenuBar");
1475     addWidgetParser("CellPanel");
1476     addWidgetParser("CustomButton");
1477     addWidgetParser("DialogBox");
1478     addWidgetParser("LayoutPanel");
1479     addWidgetParser("DockLayoutPanel");
1480     addWidgetParser("StackLayoutPanel");
1481     addWidgetParser("TabLayoutPanel");
1482     addWidgetParser("Image");
1483     addWidgetParser("ListBox");
1484     addWidgetParser("Grid");
1485     addWidgetParser("HasAlignment");
1486     addWidgetParser("DateLabel");
1487     addWidgetParser("NumberLabel");
1488     if (useLazyWidgetBuilders) {
1489       addWidgetParser("LazyPanel");
1490       addWidgetParser("RenderablePanel");
1491     }
1492   }
1493 
1494   /**
1495    * Validates each {@code eventMethod} (e.g. {@code onBrowserEvent(HandlerType o, NativeEvent e,
1496    * Element parent, A a, B b, ...)}).
1497    * <ul>
1498    * <li> The second parameter type is {@code NativeEvent}
1499    * <li> The third parameter type is {@code Element}
1500    * <li> All the handler methods in the type of the first parameter
1501    *      (any methods annotated with {@code @UiHandler})
1502    *      have a signature compatible with the {@code eventMethod}
1503    * </ul>
1504    */
1505   private void validateEventMethod(JMethod eventMethod) throws UnableToCompleteException {
1506     JParameter[] parameters = eventMethod.getParameters();
1507     if (parameters.length < 3) {
1508       die("Too few parameters in %s",
1509           formatMethodError(eventMethod));
1510     }
1511 
1512     String nativeEventName = NativeEvent.class.getCanonicalName();
1513     JClassType nativeEventType = oracle.findType(nativeEventName);
1514     if (!nativeEventType.equals(parameters[1].getType())) {
1515       die("Second parameter must be of type %s in %s", nativeEventName,
1516           formatMethodError(eventMethod));
1517     }
1518 
1519     String elementName = com.google.gwt.dom.client.Element.class.getCanonicalName();
1520     JClassType elementType = oracle.findType(elementName);
1521     if (!elementType.equals(parameters[2].getType())) {
1522       die("Third parameter must be of type %s in %s", elementName,
1523           formatMethodError(eventMethod));
1524     }
1525 
1526     if (parameters[0].getType().isClassOrInterface() == null) {
1527       die("First parameter must be a class or interface in %s",
1528           formatMethodError(eventMethod));
1529     }
1530 
1531     JClassType eventReceiver = parameters[0].getType().isClassOrInterface();
1532 
1533     validateEventReceiver(parameters, eventReceiver, eventMethod);
1534   }
1535 
1536   /**
1537    * Validates the signature of all methods annotated with {@code @UiHandler}
1538    * in the {@code eventReceiver} type. All event handlers must have the same signature
1539    * where:
1540    * <ul>
1541    * <li> The annotation must list valid {@code ui:field}s
1542    * <li> The first parameter must be assignable to
1543    *      {@link com.google.gwt.event.dom.client.DomEvent DomEvent}
1544    * <li> If present, the second parameter must be of type
1545    *      {@link com.google.gwt.dom.client.Element Element}
1546    * <li> For all other parameters in position {@code n} must be of the same type as
1547    *      {@code parameters[n + 1]}
1548    * </ul>
1549    */
1550   private void validateEventReceiver(JParameter[] onBrowserEventParameters,
1551       JClassType eventReceiver, JMethod sourceMethod)
1552       throws UnableToCompleteException {
1553 
1554     // Pre-compute the expected parameter types (after the first one, that is)
1555     JType[] onBrowserEventParamTypes = new JType[onBrowserEventParameters.length - 2];
1556 
1557     // If present, second parameter must be an Element
1558     onBrowserEventParamTypes[0] = oracle.findType(com.google.gwt.dom.client.Element.class
1559         .getCanonicalName());
1560     // And the rest must be the same type
1561     for (int i = 3; i < onBrowserEventParameters.length; i++) {
1562       onBrowserEventParamTypes[i - 2] = onBrowserEventParameters[i].getType();
1563     }
1564 
1565     for (JMethod jMethod : eventReceiver.getInheritableMethods()) {
1566       Class<UiHandler> annotationClass = UiHandler.class;
1567       UiHandler annotation = jMethod.getAnnotation(annotationClass);
1568       // Ignore methods not annotated with @UiHandler
1569       if (annotation == null) {
1570         continue;
1571       }
1572       // Are the fields in @UiHandler known?
1573       String[] fields = annotation.value();
1574       if (fields == null) {
1575         die("@UiHandler returns null from its value in %s",
1576             formatMethodError(jMethod));
1577       }
1578       for (String fieldName : fields) {
1579         FieldWriter field = fieldManager.lookup(fieldName);
1580         if (field == null) {
1581           die("\"%s\" is not a known field name as listed in the @UiHandler annotation in %s",
1582               fieldName, formatMethodError(jMethod));
1583         }
1584       }
1585 
1586       // First parameter
1587       JParameter[] eventHandlerParameters = jMethod.getParameters();
1588       JClassType domEventType = oracle.findType(DomEvent.class.getCanonicalName());
1589       JClassType firstParamType = eventHandlerParameters[0].getType().isClassOrInterface();
1590       if (firstParamType == null || !firstParamType.isAssignableTo(domEventType)) {
1591         die("First parameter must be assignable to com.google.gwt.dom.client.DomEvent in %s",
1592             formatMethodError(jMethod));
1593       }
1594 
1595       // All others
1596       if (onBrowserEventParamTypes.length < eventHandlerParameters.length - 1) {
1597         die("Too many parameters in %s", formatMethodError(jMethod));
1598       }
1599       for (int i = 1; i < eventHandlerParameters.length; i++) {
1600         if (!eventHandlerParameters[i].getType().equals(onBrowserEventParamTypes[i - 1])) {
1601           die("Parameter %s in %s is not of the same type as parameter %s in %s",
1602               eventHandlerParameters[i].getName(), formatMethodError(jMethod),
1603               onBrowserEventParameters[i + 1].getName(),
1604               formatMethodError(sourceMethod));
1605         }
1606       }
1607     }
1608   }
1609 
1610   /**
1611    * Scan the base class for the getter methods. Assumes getters begin with
1612    * "get" and validates that each corresponds to a field declared with
1613    * {@code ui:field}. If the getter return type is assignable to
1614    * {@code Element}, the getter must have a single parameter and the parameter
1615    * must be assignable to {@code Element}. If the getter return type is assignable
1616    * to {@code com.google.gwt.resources.client.CssResource}, the getter must
1617    * have no parameters.
1618    */
1619   private void validateRendererGetters(JClassType owner) throws UnableToCompleteException {
1620     for (JMethod jMethod : owner.getInheritableMethods()) {
1621       String getterName = jMethod.getName();
1622       if (getterName.startsWith("get")) {
1623         String fieldName = getterToFieldName(getterName);
1624         FieldWriter field = fieldManager.lookup(fieldName);
1625         if (field == null || (!FieldWriterType.DEFAULT.equals(field.getFieldType())
1626             && !FieldWriterType.GENERATED_CSS.equals(field.getFieldType()))) {
1627           die("%s does not match a \"ui:field='%s'\" declaration in %s, "
1628               + "or '%s' refers to something other than a ui:style"
1629               + " or an HTML element in the template", getterName, fieldName,
1630               owner.getQualifiedSourceName(), fieldName);
1631         }
1632         if (FieldWriterType.DEFAULT.equals(field.getFieldType())
1633              && jMethod.getParameterTypes().length != 1) {
1634           die("Field getter %s must have exactly one parameter in %s", getterName,
1635               owner.getQualifiedSourceName());
1636         } else if (FieldWriterType.GENERATED_CSS.equals(field.getFieldType())
1637             && jMethod.getParameterTypes().length != 0) {
1638           die("Style getter %s must have no parameters in %s", getterName,
1639               owner.getQualifiedSourceName());
1640         } else if (jMethod.getParameterTypes().length == 1) {
1641           String elementClassName = com.google.gwt.dom.client.Element.class.getCanonicalName();
1642           JClassType elementType = oracle.findType(elementClassName);
1643           JClassType getterParamType =
1644               jMethod.getParameterTypes()[0].getErasedType().isClassOrInterface();
1645 
1646           if (!elementType.isAssignableFrom(getterParamType)) {
1647             die("Getter %s must have exactly one parameter of type assignable to %s in %s",
1648                 getterName, elementClassName, owner.getQualifiedSourceName());
1649           }
1650         }
1651       } else if (!getterName.equals("render") && !getterName.equals("onBrowserEvent")
1652           && !getterName.equals("isParentOrRenderer")) {
1653         die("Unexpected method \"%s\" found in %s", getterName, owner.getQualifiedSourceName());
1654       }
1655     }
1656   }
1657 
1658   /**
1659    * Scans a class to validate that it contains a single method called render,
1660    * which has a {@code void} return type, and its first parameter is of type
1661    * {@code SafeHtmlBuilder}.
1662    */
1663   private void validateRenderParameters(JClassType owner) throws UnableToCompleteException {
1664     JMethod[] methods = owner.getInheritableMethods();
1665     JMethod renderMethod = null;
1666 
1667     for (JMethod jMethod : methods) {
1668       if (jMethod.getName().equals("render")) {
1669         if (renderMethod == null) {
1670           renderMethod = jMethod;
1671         } else {
1672           die("%s declares more than one method named render", owner.getQualifiedSourceName());
1673         }
1674       }
1675     }
1676 
1677     if (renderMethod == null
1678         || renderMethod.getParameterTypes().length < 1
1679         || !renderMethod.getParameterTypes()[0].getErasedType().getQualifiedSourceName().equals(
1680             SafeHtmlBuilder.class.getCanonicalName())) {
1681       die("%s does not declare a render(SafeHtmlBuilder ...) method",
1682           owner.getQualifiedSourceName());
1683     }
1684     if (!JPrimitiveType.VOID.equals(renderMethod.getReturnType())) {
1685       die("%s#render(SafeHtmlBuilder ...) does not return void", owner.getQualifiedSourceName());
1686     }
1687   }
1688 
1689   /**
1690    * Write statements that parsers created via calls to {@link #addStatement}.
1691    * Such statements will assume that {@link #writeGwtFields} has already been
1692    * called.
1693    */
1694   private void writeAddedStatements(IndentedWriter niceWriter) {
1695     for (String s : statements) {
1696       niceWriter.write(s);
1697     }
1698   }
1699 
1700   /**
1701    * Writes the UiBinder's source.
1702    */
1703   private void writeBinder(IndentedWriter w, FieldWriter rootField) throws UnableToCompleteException {
1704     writePackage(w);
1705 
1706     writeImports(w);
1707     w.newline();
1708 
1709     writeClassOpen(w);
1710     writeStatics(w);
1711     w.newline();
1712 
1713     // Create SafeHtml Template
1714     writeTemplatesInterface(w);
1715     w.newline();
1716 
1717     // createAndBindUi method
1718     w.write("public %s createAndBindUi(final %s owner) {",
1719         uiRootType.getParameterizedQualifiedSourceName(),
1720         uiOwnerType.getParameterizedQualifiedSourceName());
1721     w.indent();
1722     w.newline();
1723 
1724     writeGwtFields(w);
1725     w.newline();
1726 
1727     designTime.writeAttributes(this);
1728     writeAddedStatements(w);
1729     w.newline();
1730 
1731     writeInitStatements(w);
1732     w.newline();
1733 
1734     writeHandlers(w);
1735     w.newline();
1736 
1737     writeOwnerFieldSetters(w);
1738 
1739     writeCssInjectors(w);
1740 
1741     w.write("return %s;", rootField.getNextReference());
1742     w.outdent();
1743     w.write("}");
1744 
1745     // Close class
1746     w.outdent();
1747     w.write("}");
1748   }
1749 
1750   /**
1751    * Writes a different optimized UiBinder's source for the renderable strategy.
1752    */
1753   private void writeBinderForRenderableStrategy(IndentedWriter w, FieldWriter rootField)
1754       throws UnableToCompleteException {
1755     writePackage(w);
1756 
1757     writeImports(w);
1758     w.newline();
1759 
1760     writeClassOpen(w);
1761     writeStatics(w);
1762     w.newline();
1763 
1764     writeTemplatesInterface(w);
1765 
1766     w.newline();
1767 
1768     // createAndBindUi method
1769     w.write("public %s createAndBindUi(final %s owner) {",
1770         uiRootType.getParameterizedQualifiedSourceName(),
1771         uiOwnerType.getParameterizedQualifiedSourceName());
1772     w.indent();
1773     w.newline();
1774 
1775     designTime.writeAttributes(this);
1776     w.newline();
1777 
1778     w.write("return new Widgets(owner).%s;", rootField.getNextReference());
1779     w.outdent();
1780     w.write("}");
1781 
1782     // Writes the inner class Widgets.
1783     w.newline();
1784     w.write("/**");
1785     w.write(" * Encapsulates the access to all inner widgets");
1786     w.write(" */");
1787     w.write("class Widgets {");
1788     w.indent();
1789 
1790     String ownerClassType = uiOwnerType.getParameterizedQualifiedSourceName();
1791     w.write("private final %s owner;", ownerClassType);
1792     w.newline();
1793 
1794     writeHandlers(w);
1795     w.newline();
1796 
1797     w.write("public Widgets(final %s owner) {", ownerClassType);
1798     w.indent();
1799     w.write("this.owner = owner;");
1800     fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
1801     w.outdent();
1802     w.write("}");
1803     w.newline();
1804 
1805     htmlTemplates.writeTemplateCallers(w);
1806 
1807     evaluateUiFields();
1808 
1809     fieldManager.writeFieldDefinitions(w, getOracle(), getOwnerClass(), getDesignTime());
1810 
1811     w.outdent();
1812     w.write("}");
1813 
1814     // Close class
1815     w.outdent();
1816     w.write("}");
1817   }
1818 
1819   private void writeClassOpen(IndentedWriter w) {
1820     if (!isRenderer) {
1821       w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
1822           uiRootType.getParameterizedQualifiedSourceName(),
1823           uiOwnerType.getParameterizedQualifiedSourceName(),
1824           baseClass.getParameterizedQualifiedSourceName());
1825     } else {
1826       w.write("public class %s extends %s implements %s {", implClassName,
1827           AbstractUiRenderer.class.getName(),
1828           baseClass.getParameterizedQualifiedSourceName());
1829     }
1830     w.indent();
1831   }
1832 
1833   private void writeCssInjectors(IndentedWriter w) {
1834     for (ImplicitCssResource css : bundleClass.getCssMethods()) {
1835       w.write("%s.%s().ensureInjected();", bundleClass.getFieldName(), css.getName());
1836     }
1837     w.newline();
1838   }
1839 
1840   /**
1841    * Write declarations for variables or fields to hold elements declared with
1842    * gwt:field in the template. For those that have not had constructor
1843    * generation suppressed, emit GWT.create() calls instantiating them (or die
1844    * if they have no default constructor).
1845    *
1846    * @throws UnableToCompleteException on constructor problem
1847    */
1848   private void writeGwtFields(IndentedWriter niceWriter) throws UnableToCompleteException {
1849     // For each provided field in the owner class, initialize from the owner
1850     Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
1851     for (OwnerField ownerField : ownerFields) {
1852       if (ownerField.isProvided()) {
1853         String fieldName = ownerField.getName();
1854         FieldWriter fieldWriter = fieldManager.lookup(fieldName);
1855 
1856         // TODO why can this be null?
1857         if (fieldWriter != null) {
1858           String initializer;
1859           if (designTime.isDesignTime()) {
1860             String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
1861             initializer = designTime.getProvidedField(typeName, ownerField.getName());
1862           } else {
1863             initializer = formatCode("owner.%1$s", fieldName);
1864           }
1865           fieldManager.lookup(fieldName).setInitializer(initializer);
1866         }
1867       }
1868     }
1869 
1870     fieldManager.writeGwtFieldsDeclaration(niceWriter);
1871   }
1872 
1873   private void writeHandlers(IndentedWriter w) throws UnableToCompleteException {
1874     if (designTime.isDesignTime()) {
1875       return;
1876     }
1877     handlerEvaluator.run(w, fieldManager, "owner");
1878   }
1879 
1880   private void writeImports(IndentedWriter w) {
1881     w.write("import com.google.gwt.core.client.GWT;");
1882     w.write("import com.google.gwt.dom.client.Element;");
1883     if (!(htmlTemplates.isEmpty())) {
1884       w.write("import com.google.gwt.safehtml.client.SafeHtmlTemplates;");
1885       w.write("import com.google.gwt.safehtml.shared.SafeHtml;");
1886       w.write("import com.google.gwt.safehtml.shared.SafeHtmlUtils;");
1887       w.write("import com.google.gwt.safehtml.shared.SafeHtmlBuilder;");
1888       w.write("import com.google.gwt.safehtml.shared.SafeUri;");
1889       w.write("import com.google.gwt.safehtml.shared.UriUtils;");
1890       w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
1891     }
1892 
1893     if (!isRenderer) {
1894       w.write("import com.google.gwt.uibinder.client.UiBinder;");
1895       w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
1896       w.write("import %s.%s;", uiRootType.getPackage().getName(), uiRootType.getName());
1897     } else {
1898       w.write("import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;");
1899     }
1900   }
1901 
1902   /**
1903    * Write statements created by {@link #addInitStatement}. This code must be
1904    * placed after all instantiation code.
1905    */
1906   private void writeInitStatements(IndentedWriter niceWriter) {
1907     for (String s : initStatements) {
1908       niceWriter.write(s);
1909     }
1910   }
1911 
1912   /**
1913    * Write the statements to fill in the fields of the UI owner.
1914    */
1915   private void writeOwnerFieldSetters(IndentedWriter niceWriter) throws UnableToCompleteException {
1916     if (designTime.isDesignTime()) {
1917       return;
1918     }
1919     for (OwnerField ownerField : getOwnerClass().getUiFields()) {
1920       String fieldName = ownerField.getName();
1921       FieldWriter fieldWriter = fieldManager.lookup(fieldName);
1922 
1923       if (fieldWriter != null) {
1924         // ownerField is a widget.
1925         JClassType type = fieldWriter.getInstantiableType();
1926         if (type != null) {
1927           maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getInstantiableType(),
1928               fieldName);
1929         } else {
1930           // Must be a generated type
1931           if (!ownerField.isProvided()) {
1932             niceWriter.write("owner.%1$s = %1$s;", fieldName);
1933           }
1934         }
1935 
1936       } else {
1937         // ownerField was not found as bundle resource or widget, must die.
1938         die("Template %s has no %s attribute for %s.%s#%s", templatePath,
1939             getUiFieldAttributeName(), uiOwnerType.getPackage().getName(), uiOwnerType.getName(),
1940             fieldName);
1941       }
1942     }
1943   }
1944 
1945   private void writePackage(IndentedWriter w) {
1946     String packageName = baseClass.getPackage().getName();
1947     if (packageName.length() > 0) {
1948       w.write("package %1$s;", packageName);
1949       w.newline();
1950     }
1951   }
1952 
1953   /**
1954    * Writes the UiRenderer's source for the renderable strategy.
1955    */
1956   private void writeRenderer(IndentedWriter w, FieldWriter rootField) throws UnableToCompleteException {
1957     validateRendererGetters(baseClass);
1958     validateRenderParameters(baseClass);
1959     JMethod[] eventMethods = findEventMethods(baseClass);
1960     for (JMethod jMethod : eventMethods) {
1961       validateEventMethod(jMethod);
1962     }
1963 
1964     writePackage(w);
1965 
1966     writeImports(w);
1967     w.newline();
1968 
1969     writeClassOpen(w);
1970     writeStatics(w);
1971     w.newline();
1972 
1973     // Create SafeHtml Template
1974     writeTemplatesInterface(w);
1975     w.newline();
1976     htmlTemplates.writeTemplateCallers(w);
1977 
1978     w.newline();
1979 
1980     JParameter[] renderParameters = findRenderParameters(baseClass);
1981     for (JParameter param : renderParameters) {
1982       // Prevent fields from render() parameters from being optimized.
1983       fieldManager.disableOptimization(param.getName());
1984     }
1985 
1986     // public UiRendererImplClass() {
1987     w.write("public %s() {", implClassName);
1988     w.indent();
1989     w.write("build_fields();");
1990     w.outdent();
1991     // }
1992     w.write("}");
1993     w.newline();
1994 
1995     // private build_fields() {
1996     w.write("private void build_fields() {");
1997     w.indent();
1998     fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
1999     w.outdent();
2000     // }
2001     w.write("}");
2002     w.newline();
2003 
2004     String renderParameterDeclarations = renderMethodParameters(renderParameters);
2005     w.write("public void render(final %s sb%s%s) {", SafeHtmlBuilder.class.getName(),
2006         renderParameterDeclarations.length() != 0 ? ", " : "", renderParameterDeclarations);
2007     w.indent();
2008     w.newline();
2009 
2010     writeRenderParameterInitializers(w, renderParameters);
2011 
2012     w.write("uiId = com.google.gwt.dom.client.Document.get().createUniqueId();");
2013     w.newline();
2014 
2015     w.write("build_fields();");
2016     w.newline();
2017 
2018     String safeHtml = rootField.getSafeHtml();
2019 
2020     // TODO(rchandia) it should be possible to add the attribute when parsing
2021     // the UiBinder file
2022     w.write(
2023         "sb.append(stampUiRendererAttribute(%s, RENDERED_ATTRIBUTE, uiId));",
2024         safeHtml);
2025     w.outdent();
2026 
2027     w.write("}");
2028     w.newline();
2029 
2030     fieldManager.writeFieldDefinitions(w, getOracle(), getOwnerClass(), getDesignTime());
2031 
2032     writeRendererGetters(w, baseClass, rootField.getName());
2033 
2034     writeRendererEventMethods(w, eventMethods, rootField.getName());
2035 
2036     // Close class
2037     w.outdent();
2038     w.write("}");
2039   }
2040 
2041   private void writeRendererDispatcher(IndentedWriter w, String dispatcherName,
2042       JClassType targetType, String rootFieldName, JMethod[] uiHandlerMethods, JMethod sourceMethod)
2043       throws UnableToCompleteException {
2044 
2045     // static class UiRendererDispatcherForFoo extends UiRendererDispatcher<Foo> {
2046     w.write("static class %s extends UiRendererDispatcher<%s> {", dispatcherName,
2047         targetType.getQualifiedSourceName());
2048     w.indent();
2049 
2050     writeRendererDispatcherTableInit(w, rootFieldName, uiHandlerMethods,
2051         dispatcherName);
2052 
2053     writeRendererDispatcherExtraParameters(w, sourceMethod);
2054 
2055     writeRendererDispatcherFire(w, sourceMethod);
2056 
2057     w.write("@SuppressWarnings(\"rawtypes\")");
2058     w.write("@Override");
2059     // public void fireEvent(GwtEvent<?> somethingUnlikelyToCollideWithParamNames) {
2060     w.write("public void fireEvent(com.google.gwt.event.shared.GwtEvent<?> %sEvent) {",
2061         SAFE_VAR_PREFIX);
2062     w.indent();
2063     //   switch (getMethodIndex()) {
2064     w.write("switch (getMethodIndex()) {");
2065     w.indent();
2066     for (int j = 0; j < uiHandlerMethods.length; j++) {
2067       JMethod uiMethod = uiHandlerMethods[j];
2068 
2069       // case 0:
2070       w.write("case %s:", j);
2071       w.indent();
2072 
2073       //   getEventTarget().onClickRoot((ClickEvent) somethingUnlikelyToCollideWithParamNames,
2074       //       getRoot(), a, b);
2075       StringBuffer sb = new StringBuffer();
2076       JParameter[] sourceParameters = sourceMethod.getParameters();
2077       // Cat the extra parameters i.e. ", a, b"
2078       JType[] uiHandlerParameterTypes = uiMethod.getParameterTypes();
2079       if (uiHandlerParameterTypes.length >= 2) {
2080         sb.append(", getRoot()");
2081       }
2082       for (int k = 2; k < uiHandlerParameterTypes.length; k++) {
2083         JParameter sourceParam = sourceParameters[k + 1];
2084         sb.append(", ");
2085         sb.append(sourceParam.getName());
2086       }
2087       w.write("getEventTarget().%s((%s) %sEvent%s);", uiMethod.getName(),
2088           uiHandlerParameterTypes[0].getQualifiedSourceName(), SAFE_VAR_PREFIX,
2089           sb.toString());
2090       //   break;
2091       w.write("break;");
2092       w.newline();
2093       w.outdent();
2094     }
2095     //    default:
2096     w.write("default:");
2097     w.indent();
2098     //      break;
2099     w.write("break;");
2100     w.outdent();
2101     w.outdent();
2102     w.write("}");
2103 
2104     w.outdent();
2105     w.write("}");
2106 
2107     w.outdent();
2108     w.write("}");
2109   }
2110 
2111   private void writeRendererDispatcherExtraParameters(IndentedWriter w, JMethod sourceMethod) {
2112     for (int i = 3; i < sourceMethod.getParameters().length; i++) {
2113       JParameter param = sourceMethod.getParameters()[i];
2114 
2115       // private int a;
2116       // private String b;
2117       w.write("private %s %s;", param.getType().getParameterizedQualifiedSourceName(),
2118           param.getName());
2119     }
2120   }
2121 
2122   private void writeRendererDispatcherFire(IndentedWriter w, JMethod sourceMethod) {
2123     // public void fire(Foo o, NativeEvent e, Element parent, int a, String b) {
2124     w.write("public void fire(");
2125     w.indent();
2126     JParameter[] sourceParameters = sourceMethod.getParameters();
2127     for (int i = 0; i < sourceParameters.length; i++) {
2128       JParameter param = sourceParameters[i];
2129       w.write(i == 0 ? "%s %s" : ", %s %s", param.getType().getQualifiedSourceName(), param.getName());
2130     }
2131     w.write(") {");
2132     w.indent();
2133 
2134     // this.a = a;
2135     for (int i = 3; i < sourceParameters.length; i++) {
2136       JParameter sourceParam = sourceParameters[i];
2137       w.write("this.%s = %s;", sourceParam.getName(), sourceParam.getName());
2138     }
2139 
2140     // fireEvent(o, e, parent);
2141     w.write("fireEvent(%s, %s, %s);", sourceParameters[0].getName(), sourceParameters[1].getName(),
2142         sourceParameters[2].getName());
2143 
2144     w.outdent();
2145     w.write("}");
2146     w.newline();
2147   }
2148 
2149   private void writeRendererDispatcherTableInit(IndentedWriter w,
2150       String rootFieldName, JMethod[] uiHandlerMethods, String dispatcherName)
2151       throws UnableToCompleteException {
2152     ArrayList<String> keys = new ArrayList<String>();
2153     ArrayList<Integer> values = new ArrayList<Integer>();
2154 
2155     // Collect the event types and field names to form the dispatch table
2156     for (int i = 0; i < uiHandlerMethods.length; i++) {
2157       JMethod jMethod = uiHandlerMethods[i];
2158       String eventType = findEventTypeName(jMethod);
2159       String[] fieldNames = jMethod.getAnnotation(UiHandler.class).value();
2160       for (String fieldName : fieldNames) {
2161         if (rootFieldName.equals(fieldName)) {
2162           fieldName = AbstractUiRenderer.ROOT_FAKE_NAME;
2163         }
2164         keys.add(eventType + AbstractUiRenderer.UI_ID_SEPARATOR + fieldName);
2165         values.add(i);
2166       }
2167     }
2168 
2169     // private static String[] somethingUnlikelyToCollideWithParamNames_keys;
2170     w.write("private static String[] %s_keys;", SAFE_VAR_PREFIX);
2171     // private static Integer[] somethingUnlikelyToCollideWithParamNames_values;
2172     w.write("private static Integer[] %s_values;", SAFE_VAR_PREFIX);
2173 
2174     w.write("static {");
2175     w.indent();
2176     // private static String[] somethingUnlikelyToCollideWithParamNames_keys = new String[] {
2177     w.write("%s_keys = new String[] {", SAFE_VAR_PREFIX);
2178     w.indent();
2179     for (String key : keys) {
2180       // "click:aField",
2181       w.write("\"%s\",", key);
2182     }
2183     w.outdent();
2184     w.write("};");
2185     w.newline();
2186 
2187     // somethingUnlikelyToCollideWithParamNames_values = {0,1};
2188     w.write("%s_values = new Integer[] {", SAFE_VAR_PREFIX);
2189     w.indent();
2190     StringBuffer commaSeparatedValues = new StringBuffer();
2191     for (Integer value : values) {
2192       commaSeparatedValues.append(value);
2193       commaSeparatedValues.append(",");
2194     }
2195     // "0,0,0,1,1,",
2196     w.write("%s", commaSeparatedValues.toString());
2197     w.outdent();
2198     w.write("};");
2199     w.newline();
2200 
2201     w.outdent();
2202     w.write("}");
2203     w.newline();
2204 
2205     // public Foo() {
2206     w.write("public %s() {", dispatcherName);
2207     w.indent();
2208     // initDispatchTable(keys, values);
2209     w.write("initDispatchTable(%s_keys, %s_values);", SAFE_VAR_PREFIX, SAFE_VAR_PREFIX);
2210 
2211     // This ensures the DomEvent#TYPE fields are properly initialized and registered
2212     // ClickEvent.getType();
2213     HashSet<String> eventTypes = new HashSet<String>();
2214     for (JMethod uiMethod : uiHandlerMethods) {
2215       eventTypes.add(uiMethod.getParameterTypes()[0].getQualifiedSourceName());
2216     }
2217     for (String eventType : eventTypes) {
2218       w.write("%s.getType();", eventType);
2219     }
2220 
2221 
2222     w.outdent();
2223     w.write("}");
2224     w.newline();
2225   }
2226 
2227   private void writeRendererEventMethods(IndentedWriter w, JMethod[] eventMethods,
2228       String rootField) throws UnableToCompleteException {
2229     for (JMethod jMethod : eventMethods) {
2230       JClassType eventTargetType = jMethod.getParameterTypes()[0].isClassOrInterface();
2231       String eventTargetSimpleName = eventTargetType.getSimpleSourceName();
2232       String dispatcherClassName = UI_RENDERER_DISPATCHER_PREFIX + eventTargetSimpleName;
2233       JMethod[] uiHandlerMethods = findUiHandlerMethods(eventTargetType);
2234 
2235       // public void onBrowserEvent(Foo f, NativeEvent event, Element parent, A a, B b ...) {
2236       w.write("@Override");
2237       w.write("public %s {", jMethod.getReadableDeclaration(true, true, true, true, true));
2238 
2239       if (uiHandlerMethods.length != 0) {
2240         w.indent();
2241         //  if (singletonUiRendererDispatcherForFoo == null) {
2242         w.write("if (singleton%s == null) {", dispatcherClassName);
2243         w.indent();
2244         // singletonUiRendererDispatcherForFoo = new UiRendererDispatcherForFoo();
2245         w.write("singleton%s = new %s();", dispatcherClassName, dispatcherClassName);
2246 
2247         w.outdent();
2248         w.write("}");
2249 
2250         // singletonUiRendererDispatcherForFoo.fire(o, event, parent, a, b);
2251         StringBuffer sb = new StringBuffer();
2252         JParameter[] parameters = jMethod.getParameters();
2253         for (int i = 0; i < parameters.length; i++) {
2254           JParameter callParam = parameters[i];
2255           if (i != 0) {
2256             sb.append(", ");
2257           }
2258           sb.append(callParam.getName());
2259         }
2260         w.write("singleton%s.fire(%s);", dispatcherClassName, sb.toString());
2261         w.outdent();
2262       }
2263 
2264       w.write("}");
2265       w.newline();
2266 
2267       if (uiHandlerMethods.length != 0) {
2268         // private static UiRendererDispatcherForFoo singletonUiRendererDispatcherForFoo;
2269         w.write("private static %s singleton%s;", dispatcherClassName, dispatcherClassName);
2270 
2271         writeRendererDispatcher(w, dispatcherClassName, eventTargetType, rootField, uiHandlerMethods,
2272             jMethod);
2273       }
2274     }
2275   }
2276 
2277   private void writeRendererGetters(IndentedWriter w, JClassType owner, String rootFieldName) {
2278     List<JMethod> getters = findGetterNames(owner);
2279 
2280     // For every requested getter
2281     for (JMethod getter : getters) {
2282       // public ElementSubclass getFoo(Element parent) {
2283       w.write("%s {", getter.getReadableDeclaration(false, false, false, false, true));
2284       w.indent();
2285       String getterFieldName = getterToFieldName(getter.getName());
2286       // Is this a CSS resource field?
2287       FieldWriter fieldWriter = fieldManager.lookup(getterFieldName);
2288       if (FieldWriterType.GENERATED_CSS.equals(fieldWriter.getFieldType())) {
2289         // return (CssResourceSubclass) get_styleField;
2290         w.write("return (%s) %s;", getter.getReturnType().getErasedType().getQualifiedSourceName(),
2291                 FieldManager.getFieldGetter(getterFieldName));
2292       } else {
2293         // Else the non-root elements are found by id
2294         String elementParameter = getter.getParameters()[0].getName();
2295         if (!getterFieldName.equals(rootFieldName)) {
2296           // return (ElementSubclass) findInnerField(parent, "foo", RENDERED_ATTRIBUTE);
2297           w.write("return (%s) findInnerField(%s, \"%s\", RENDERED_ATTRIBUTE);",
2298               getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter,
2299               getterFieldName);
2300         } else {
2301           // return (ElementSubclass) findRootElement(parent);
2302           w.write("return (%s) findRootElement(%s, RENDERED_ATTRIBUTE);",
2303               getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter);
2304         }
2305       }
2306       w.outdent();
2307       w.write("}");
2308     }
2309   }
2310 
2311   private void writeRenderParameterInitializers(IndentedWriter w, JParameter[] renderParameters) {
2312     for (int i = 0; i < renderParameters.length; i++) {
2313       JParameter parameter = renderParameters[i];
2314       if (fieldManager.lookup(parameter.getName()) != null) {
2315         w.write("this.%s = %s;", parameter.getName(), parameter.getName());
2316         w.newline();
2317       }
2318     }
2319   }
2320 
2321   private void writeStaticMessagesInstance(IndentedWriter niceWriter) {
2322     if (messages.hasMessages()) {
2323       niceWriter.write(messages.getDeclaration());
2324     }
2325   }
2326 
2327   private void writeStatics(IndentedWriter w) {
2328     writeStaticMessagesInstance(w);
2329     designTime.addDeclarations(w);
2330   }
2331 
2332   /**
2333    * Write statements created by {@link HtmlTemplatesWriter#addSafeHtmlTemplate}
2334    * . This code must be placed after all instantiation code.
2335    */
2336   private void writeTemplatesInterface(IndentedWriter w) {
2337     if (!(htmlTemplates.isEmpty())) {
2338       assert useSafeHtmlTemplates : "SafeHtml is off, but templates were made.";
2339       htmlTemplates.writeInterface(w);
2340       w.newline();
2341     }
2342   }
2343 }