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.JPackage;
36  import com.google.gwt.core.ext.typeinfo.JParameterizedType;
37  import com.google.gwt.core.ext.typeinfo.JTypeParameter;
38  import com.google.gwt.core.ext.typeinfo.TypeOracle;
39  import com.google.gwt.dom.client.TagName;
40  import com.google.gwt.resources.client.ClientBundle;
41  import com.google.gwt.uibinder.attributeparsers.AttributeParser;
42  import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
43  import com.google.gwt.uibinder.attributeparsers.BundleAttributeParser;
44  import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
45  import com.google.gwt.uibinder.client.ElementParserToUse;
46  import com.google.gwt.uibinder.client.LazyDomElement;
47  import com.google.gwt.uibinder.client.UiBinder;
48  import com.google.gwt.uibinder.elementparsers.AttributeMessageParser;
49  import com.google.gwt.uibinder.elementparsers.BeanParser;
50  import com.google.gwt.uibinder.elementparsers.ElementParser;
51  import com.google.gwt.uibinder.elementparsers.IsEmptyParser;
52  import com.google.gwt.uibinder.elementparsers.UiChildParser;
53  import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
54  import com.google.gwt.uibinder.rebind.model.HtmlTemplates;
55  import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
56  import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
57  import com.google.gwt.uibinder.rebind.model.OwnerClass;
58  import com.google.gwt.uibinder.rebind.model.OwnerField;
59  import com.google.gwt.user.client.ui.IsRenderable;
60  import com.google.gwt.user.client.ui.IsWidget;
61  
62  /**
63   * Writer for UiBinder generated classes.
64   *
65   * TODO(rdamazio): Refactor this, extract model classes, improve ordering
66   * guarantees, etc.
67   *
68   * TODO(rjrjr): Line numbers in error messages.
69   */
70  @SuppressWarnings("deprecation")
71  public class UiBinderWriter implements Statements {
72    private static final String PACKAGE_URI_SCHEME = "urn:import:";
73  
74    // TODO(rjrjr) Another place that we need a general anonymous field
75    // mechanism
76    private static final String CLIENT_BUNDLE_FIELD =
77        "clientBundleFieldNameUnlikelyToCollideWithUserSpecifiedFieldOkay";
78  
79    public static String asCommaSeparatedList(String... args) {
80      StringBuilder b = new StringBuilder();
81      for (String arg : args) {
82        if (b.length() > 0) {
83          b.append(", ");
84        }
85        b.append(arg);
86      }
87  
88      return b.toString();
89    }
90  
91    /**
92     * Escape text that will be part of a string literal to be interpreted at
93     * runtime as an HTML attribute value.
94     */
95    public static String escapeAttributeText(String text) {
96      text = escapeText(text, false);
97  
98      /*
99       * Escape single-quotes to make them safe to be interpreted at runtime as an
100      * HTML attribute value (for which we by convention use single quotes).
101      */
102     text = text.replaceAll("'", "'");
103     return text;
104   }
105 
106   /**
107    * Escape text that will be part of a string literal to be interpreted at
108    * runtime as HTML, optionally preserving whitespace.
109    */
110   public static String escapeText(String text, boolean preserveWhitespace) {
111     // Replace reserved XML characters with entities. Note that we *don't*
112     // replace single- or double-quotes here, because they're safe in text
113     // nodes.
114     text = text.replaceAll("&", "&");
115     text = text.replaceAll("<", "&lt;");
116     text = text.replaceAll(">", "&gt;");
117 
118     if (!preserveWhitespace) {
119       text = text.replaceAll("\\s+", " ");
120     }
121 
122     return escapeTextForJavaStringLiteral(text);
123   }
124 
125   /**
126    * Escape characters that would mess up interpretation of this string as a
127    * string literal in generated code (that is, protect \, \n and " ).
128    */
129   public static String escapeTextForJavaStringLiteral(String text) {
130     text = text.replace("\\", "\\\\");
131     text = text.replace("\"", "\\\"");
132     text = text.replace("\n", "\\n");
133 
134     return text;
135   }
136 
137   /**
138    * Returns a list of the given type and all its superclasses and implemented
139    * interfaces in a breadth-first traversal.
140    *
141    * @param type the base type
142    * @return a breadth-first collection of its type hierarchy
143    */
144   static Iterable<JClassType> getClassHierarchyBreadthFirst(JClassType type) {
145     LinkedList<JClassType> list = new LinkedList<JClassType>();
146     LinkedList<JClassType> q = new LinkedList<JClassType>();
147 
148     q.add(type);
149     while (!q.isEmpty()) {
150       // Pop the front of the queue and add it to the result list.
151       JClassType curType = q.removeFirst();
152       list.add(curType);
153 
154       // Add implemented interfaces to the back of the queue (breadth first,
155       // remember?)
156       for (JClassType intf : curType.getImplementedInterfaces()) {
157         q.add(intf);
158       }
159       // Add then add superclasses
160       JClassType superClass = curType.getSuperclass();
161       if (superClass != null) {
162         q.add(superClass);
163       }
164     }
165 
166     return list;
167   }
168 
169   private static String capitalizePropName(String propName) {
170     return propName.substring(0, 1).toUpperCase() + propName.substring(1);
171   }
172 
173   private final MortalLogger logger;
174 
175   /**
176    * Class names of parsers for various ui types, keyed by the classname of the
177    * UI class they can build.
178    */
179   private final Map<String, String> elementParsers = new HashMap<String, String>();
180 
181   private final List<String> initStatements = new ArrayList<String>();
182   private final List<String> statements = new ArrayList<String>();
183   private final HtmlTemplates htmlTemplates = new HtmlTemplates();
184   private final HandlerEvaluator handlerEvaluator;
185   private final MessagesWriter messages;
186   private final DesignTimeUtils designTime;
187   private final Tokenator tokenator = new Tokenator();
188 
189   private final String templatePath;
190   private final TypeOracle oracle;
191   /**
192    * The type we have been asked to generated, e.g. MyUiBinder
193    */
194   private final JClassType baseClass;
195 
196   /**
197    * The name of the class we're creating, e.g. MyUiBinderImpl
198    */
199   private final String implClassName;
200 
201   private final JClassType uiOwnerType;
202 
203   private final JClassType uiRootType;
204 
205   private final JClassType isRenderableClassType;
206 
207   private final JClassType lazyDomElementClass;
208 
209   private final OwnerClass ownerClass;
210 
211   private final FieldManager fieldManager;
212 
213   private final ImplicitClientBundle bundleClass;
214 
215   private final boolean useLazyWidgetBuilders;
216 
217   private final boolean useSafeHtmlTemplates;
218 
219   private int domId = 0;
220 
221   private int fieldIndex;
222 
223   private String gwtPrefix;
224 
225   private String rendered;
226   /**
227    * Stack of element variable names that have been attached.
228    */
229   private final LinkedList<String> attachSectionElements = new LinkedList<String>();
230   /**
231    * Maps from field element name to the temporary attach record variable name.
232    */
233   private final Map<String, String> attachedVars = new HashMap<String, String>();
234 
235   private int nextAttachVar = 0;
236   /**
237    * Stack of statements to be executed after we detach the current attach
238    * section.
239    */
240   private final LinkedList<List<String>> detachStatementsStack = new LinkedList<List<String>>();
241   private final AttributeParsers attributeParsers;
242 
243   private final BundleAttributeParsers bundleParsers;
244 
245   private final UiBinderContext uiBinderCtx;
246 
247   private final String binderUri;
248 
249   public UiBinderWriter(JClassType baseClass, String implClassName,
250       String templatePath, TypeOracle oracle, MortalLogger logger,
251       FieldManager fieldManager, MessagesWriter messagesWriter,
252       DesignTimeUtils designTime, UiBinderContext uiBinderCtx,
253       boolean useSafeHtmlTemplates, boolean useLazyWidgetBuilders, 
254       String binderUri)
255       throws UnableToCompleteException {
256     this.baseClass = baseClass;
257     this.implClassName = implClassName;
258     this.oracle = oracle;
259     this.logger = logger;
260     this.templatePath = templatePath;
261     this.fieldManager = fieldManager;
262     this.messages = messagesWriter;
263     this.designTime = designTime;
264     this.uiBinderCtx = uiBinderCtx;
265     this.useSafeHtmlTemplates = useSafeHtmlTemplates;
266     this.useLazyWidgetBuilders = useLazyWidgetBuilders;
267     this.binderUri = binderUri;
268 
269     // Check for possible misuse 'GWT.create(UiBinder.class)'
270     JClassType uibinderItself = oracle.findType(UiBinder.class.getCanonicalName());
271     if (uibinderItself.equals(baseClass)) {
272       die("You must use a subtype of UiBinder in GWT.create(). E.g.,\n"
273           + "  interface Binder extends UiBinder<Widget, MyClass> {}\n"
274           + "  GWT.create(Binder.class);");
275     }
276 
277     JClassType[] uiBinderTypes = baseClass.getImplementedInterfaces();
278     if (uiBinderTypes.length == 0) {
279       throw new RuntimeException("No implemented interfaces for "
280           + baseClass.getName());
281     }
282     JClassType uiBinderType = uiBinderTypes[0];
283 
284     JClassType[] typeArgs = uiBinderType.isParameterized().getTypeArgs();
285     if (typeArgs.length < 2) {
286       throw new RuntimeException(
287           "Root and owner type parameters are required for type %s"
288               + uiBinderType.getName());
289     }
290     uiRootType = typeArgs[0];
291     uiOwnerType = typeArgs[1];
292 
293     isRenderableClassType = oracle.findType(IsRenderable.class.getCanonicalName());
294     lazyDomElementClass = oracle.findType(LazyDomElement.class.getCanonicalName());
295 
296     ownerClass = new OwnerClass(uiOwnerType, logger, uiBinderCtx);
297     bundleClass = new ImplicitClientBundle(baseClass.getPackage().getName(),
298         this.implClassName, CLIENT_BUNDLE_FIELD, logger);
299     handlerEvaluator = new HandlerEvaluator(
300         ownerClass, logger, oracle, useLazyWidgetBuilders);
301 
302     attributeParsers = new AttributeParsers(oracle, fieldManager, logger);
303     bundleParsers = new BundleAttributeParsers(oracle, logger, getOwnerClass(),
304         templatePath, uiOwnerType);
305   }
306 
307   /**
308    * Add a statement to be executed right after the current attached element is
309    * detached. This is useful for doing things that might be expensive while the
310    * element is attached to the DOM.
311    *
312    * @param format
313    * @param args
314    * @see #beginAttachedSection(String)
315    */
316   public void addDetachStatement(String format, Object... args) {
317     detachStatementsStack.getFirst().add(String.format(format, args));
318   }
319 
320   /**
321    * Add a statement to be run after everything has been instantiated, in the
322    * style of {@link String#format}.
323    */
324   public void addInitStatement(String format, Object... params) {
325     initStatements.add(formatCode(format, params));
326   }
327 
328   /**
329    * Adds a statement to the block run after fields are declared, in the style
330    * of {@link String#format}.
331    */
332   public void addStatement(String format, Object... args) {
333     String code = formatCode(format, args);
334 
335     if (useLazyWidgetBuilders) {
336       /**
337        * I'm intentionally over-simplifying this and assuming that the input
338        * comes always in the format: field.somestatement();
339        * Thus, field can be extracted easily and the element parsers don't
340        * need to be changed all at once.
341        */
342       int idx = code.indexOf(".");
343       String fieldName = code.substring(0, idx);
344       fieldManager.require(fieldName).addStatement(format, args);
345     } else {
346       statements.add(code);
347     }
348   }
349 
350   /**
351    * Begin a section where a new attachable element is being parsed--that is,
352    * one that will be constructed as a big innerHTML string, and then briefly
353    * attached to the dom to allow fields accessing its to be filled (at the
354    * moment, HasHTMLParser, HTMLPanelParser, and DomElementParser.).
355    * <p>
356    * Succeeding calls made to {@link #ensureAttached} and
357    * {@link #ensureCurrentFieldAttached} must refer to children of this element,
358    * until {@link #endAttachedSection} is called.
359    *
360    * @param element Java expression for the generated code that will return the
361    *          dom element to be attached.
362    */
363   public void beginAttachedSection(String element) {
364     attachSectionElements.addFirst(element);
365     detachStatementsStack.addFirst(new ArrayList<String>());
366   }
367 
368   /**
369    * Declare a field that will hold an Element instance. Returns a token that
370    * the caller must set as the id attribute of that element in whatever
371    * innerHTML expression will reproduce it at runtime.
372    * <P>
373    * In the generated code, this token will be replaced by an expression to
374    * generate a unique dom id at runtime. Further code will be generated to be
375    * run after widgets are instantiated, to use that dom id in a getElementById
376    * call and assign the Element instance to its field.
377    *
378    * @param fieldName The name of the field being declared
379    * @param ancestorField The name of fieldName parent
380    */
381   public String declareDomField(String fieldName, String ancestorField)
382       throws UnableToCompleteException {
383     ensureAttached();
384     String name = declareDomIdHolder();
385 
386     if (useLazyWidgetBuilders) {
387       // Create and initialize the dom field with LazyDomElement.
388       FieldWriter field = fieldManager.require(fieldName);
389 
390       /**
391        * But if the owner field is an instance of LazyDomElement then the code
392        * can be optimized, no cast is needed and the getter doesn't need to be
393        * called in its ancestral.
394        */
395       if (isOwnerFieldLazyDomElement(fieldName)) {
396         field.setInitializer(formatCode("new %s(%s)",
397             field.getQualifiedSourceName(),
398             fieldManager.convertFieldToGetter(name)));
399       } else {
400 
401         field.setInitializer(formatCode("new %s(%s).get().cast()",
402             LazyDomElement.class.getCanonicalName(),
403             fieldManager.convertFieldToGetter(name)));
404 
405         // The dom must be created by its ancestor.
406         fieldManager.require(ancestorField).addAttachStatement(
407             fieldManager.convertFieldToGetter(fieldName) + ";");
408       }
409     } else {
410       setFieldInitializer(fieldName, "null");
411       addInitStatement(
412           "%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
413           fieldName, name);
414       addInitStatement("%s.removeAttribute(\"id\");", fieldName);
415     }
416 
417     return tokenForStringExpression(fieldManager.convertFieldToGetter(name));
418   }
419 
420   /**
421    * Declare a variable that will be filled at runtime with a unique id, safe
422    * for use as a dom element's id attribute.
423    *
424    * @return that variable's name.
425    */
426   public String declareDomIdHolder() throws UnableToCompleteException {
427     String domHolderName = "domId" + domId++;
428     FieldWriter domField = fieldManager.registerField(FieldWriterType.DOM_ID_HOLDER,
429         oracle.findType(String.class.getName()), domHolderName);
430     domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
431 
432     return domHolderName;
433   }
434 
435   /**
436    * Declares a field of the given type name, returning the name of the declared
437    * field. If the element has a field or id attribute, use its value.
438    * Otherwise, create and return a new, private field name for it.
439    */
440   public String declareField(String typeName, XMLElement elem)
441       throws UnableToCompleteException {
442     JClassType type = oracle.findType(typeName);
443     if (type == null) {
444       die(elem, "Unknown type %s", typeName);
445     }
446 
447     String fieldName = getFieldName(elem);
448     if (fieldName == null) {
449       // TODO(rjrjr) could collide with user declared name, as is
450       // also a worry in HandlerEvaluator. Need a general scheme for
451       // anonymous fields. See the note in HandlerEvaluator and do
452       // something like that, but in FieldManager.
453       fieldName = "f_" + elem.getLocalName() + ++fieldIndex;
454     }
455     fieldName = normalizeFieldName(fieldName);
456     fieldManager.registerField(type, fieldName);
457     return fieldName;
458   }
459 
460   /**
461    * If this element has a gwt:field attribute, create a field for it of the
462    * appropriate type, and return the field name. If no gwt:field attribute is
463    * found, do nothing and return null
464    *
465    * @return The new field name, or null if no field is created
466    */
467   public String declareFieldIfNeeded(XMLElement elem)
468       throws UnableToCompleteException {
469     String fieldName = getFieldName(elem);
470     if (fieldName != null) {
471 
472       /**
473        * We can switch types if useLazyWidgetBuilders is enabled and the
474        * respective owner field is a LazyDomElement.
475        */
476       if (useLazyWidgetBuilders && isOwnerFieldLazyDomElement(fieldName)) {
477         fieldManager.registerFieldForLazyDomElement(findFieldType(elem), ownerClass.getUiField(fieldName));
478       } else {
479         fieldManager.registerField(findFieldType(elem), fieldName);
480       }
481     }
482     return fieldName;
483   }
484 
485   /**
486    * Writes a new SafeHtml template to the generated BinderImpl.
487    *
488    * @return The invocation of the SafeHtml template function with the arguments
489    * filled in
490    */
491   public String declareTemplateCall(String html)
492     throws IllegalArgumentException {
493     if (!useSafeHtmlTemplates) {
494       return '"' + html + '"';
495     }
496 
497     return htmlTemplates.addSafeHtmlTemplate(html, tokenator);
498   }
499 
500   /**
501    * Given a string containing tokens returned by {@link #tokenForStringExpression},
502    * {@link #tokenForSafeHtmlExpression} or {@link #declareDomField}, return a
503    * string with those tokens replaced by the appropriate expressions. (It is
504    * not normally necessary for an {@link XMLElement.Interpreter} or
505    * {@link ElementParser} to make this call, as the tokens are typically
506    * replaced by the TemplateWriter itself.)
507    */
508   public String detokenate(String betokened) {
509     return tokenator.detokenate(betokened);
510   }
511 
512   /**
513    * Post an error message and halt processing. This method always throws an
514    * {@link UnableToCompleteException}
515    */
516   public void die(String message) throws UnableToCompleteException {
517     logger.die(message);
518   }
519 
520   /**
521    * Post an error message and halt processing. This method always throws an
522    * {@link UnableToCompleteException}
523    */
524   public void die(String message, Object... params)
525       throws UnableToCompleteException {
526     logger.die(message, params);
527   }
528 
529   /**
530    * Post an error message about a specific XMLElement and halt processing. This
531    * method always throws an {@link UnableToCompleteException}
532    */
533   public void die(XMLElement context, String message, Object... params)
534       throws UnableToCompleteException {
535     logger.die(context, message, params);
536   }
537 
538   /**
539    * End the current attachable section. This will detach the element if it was
540    * ever attached and execute any detach statements.
541    *
542    * @see #beginAttachedSection(String)
543    */
544   public void endAttachedSection() {
545     String elementVar = attachSectionElements.removeFirst();
546     List<String> detachStatements = detachStatementsStack.removeFirst();
547     if (attachedVars.containsKey(elementVar)) {
548       String attachedVar = attachedVars.remove(elementVar);
549       addInitStatement("%s.detach();", attachedVar);
550       for (String statement : detachStatements) {
551         addInitStatement(statement);
552       }
553     }
554   }
555 
556   /**
557    * Ensure that the specified element is attached to the DOM.
558    *
559    * @see #beginAttachedSection(String)
560    */
561   public void ensureAttached() {
562     String attachSectionElement = attachSectionElements.getFirst();
563     if (!attachedVars.containsKey(attachSectionElement)) {
564       String attachedVar = "attachRecord" + nextAttachVar;
565       addInitStatement(
566           "UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
567           attachedVar, attachSectionElement);
568       attachedVars.put(attachSectionElement, attachedVar);
569       nextAttachVar++;
570     }
571   }
572 
573   /**
574    * Ensure that the specified field is attached to the DOM. The field must hold
575    * an object that responds to Element getElement(). Convenience wrapper for
576    * {@link #ensureAttached}<code>(field + ".getElement()")</code>.
577    *
578    * @see #beginAttachedSection(String)
579    */
580   public void ensureCurrentFieldAttached() {
581     ensureAttached();
582   }
583 
584   /**
585    * Finds the JClassType that corresponds to this XMLElement, which must be a
586    * Widget or an Element.
587    *
588    * @throws UnableToCompleteException If no such widget class exists
589    * @throws RuntimeException if asked to handle a non-widget, non-DOM element
590    */
591   public JClassType findFieldType(XMLElement elem)
592       throws UnableToCompleteException {
593     String tagName = elem.getLocalName();
594 
595     // laaglu
596     String uri = elem.getNamespaceUri();
597     if (SVGConstants.SVG_NAMESPACE_URI.equals(uri)) {
598     	return findSvgDomElementTypeForTag(tagName);
599     }
600     // laaglu
601 
602     if (!isImportedElement(elem)) {
603       return findDomElementTypeForTag(tagName);
604     }
605 
606     String ns = elem.getNamespaceUri();
607     String packageName = ns;
608     String className = tagName;
609 
610     while (true) {
611       JPackage pkg = parseNamespacePackage(packageName);
612       if (pkg == null) {
613         throw new RuntimeException("No such package: " + packageName);
614       }
615 
616       JClassType rtn = pkg.findType(className);
617       if (rtn != null) {
618         return rtn;
619       }
620 
621       // Try again: shift one element of the class name onto the package name.
622       // If the class name has only one element left, fail.
623       int index = className.indexOf(".");
624       if (index == -1) {
625         die(elem, "No class matching \"%s\" in %s", tagName, ns);
626       }
627       packageName = packageName + "." + className.substring(0, index);
628       className = className.substring(index + 1);
629     }
630   }
631 
632   // laaglu
633   /**
634    * Given a SVG tag name, return the corresponding
635    * {@link org.vectomatic.dom.svg.OMSVGElement} subclass.
636    */
637   private JClassType findSvgDomElementTypeForTag(String tag) {
638     JClassType elementClass = oracle.findType(OMSVGElement.class.getName());
639     JClassType[] types = elementClass.getSubtypes();
640     for (JClassType type : types) {
641       TagName annotation = type.getAnnotation(TagName.class);
642       if (annotation != null) {
643         for (String annotationTag : annotation.value()) {
644           if (annotationTag.equals(tag)) {
645             return type;
646           }
647         }
648       }
649     }
650 
651     return elementClass;
652   }
653   // laaglu
654 
655 
656   /**
657    * Generates the code to set a property value (assumes that 'value' is a valid
658    * Java expression).
659    */
660   public void genPropertySet(String fieldName, String propName, String value) {
661     addStatement("%1$s.set%2$s(%3$s);", fieldName,
662         capitalizePropName(propName), value);
663   }
664 
665   /**
666    * Generates the code to set a string property.
667    */
668   public void genStringPropertySet(String fieldName, String propName,
669       String value) {
670     genPropertySet(fieldName, propName, "\"" + value + "\"");
671   }
672 
673   /**
674    * Finds an attribute {@link BundleAttributeParser} for the given xml
675    * attribute, if any, based on its namespace uri.
676    *
677    * @return the parser or null
678    * @deprecated exists only to support {@link BundleAttributeParser}, which
679    *             will be leaving us soon.
680    */
681   @Deprecated
682   public AttributeParser getBundleAttributeParser(XMLAttribute attribute)
683       throws UnableToCompleteException {
684     return bundleParsers.get(attribute);
685   }
686 
687   public ImplicitClientBundle getBundleClass() {
688     return bundleClass;
689   }
690 
691   /**
692    * Returns the {@link DesignTimeUtils}, not <code>null</code>.
693    */
694   public DesignTimeUtils getDesignTime() {
695     return designTime;
696   }
697 
698   public FieldManager getFieldManager() {
699     return fieldManager;
700   }
701 
702   /**
703    * Returns the logger, at least until we get get it handed off to parsers via
704    * constructor args.
705    */
706   public MortalLogger getLogger() {
707     return logger;
708   }
709 
710   /**
711    * Get the {@link MessagesWriter} for this UI, generating it if necessary.
712    */
713   public MessagesWriter getMessages() {
714     return messages;
715   }
716 
717   /**
718    * Gets the type oracle.
719    */
720   public TypeOracle getOracle() {
721     return oracle;
722   }
723 
724   public OwnerClass getOwnerClass() {
725     return ownerClass;
726   }
727 
728   public String getUiFieldAttributeName() {
729     return gwtPrefix + ":field";
730   }
731 
732   public boolean isBinderElement(XMLElement elem) {
733     String uri = elem.getNamespaceUri();
734     return uri != null && binderUri.equals(uri);
735   }
736 
737   public boolean isElementAssignableTo(XMLElement elem, Class<?> possibleSuperclass)
738       throws UnableToCompleteException {
739     JClassType classType = oracle.findType(possibleSuperclass.getCanonicalName());
740     return isElementAssignableTo(elem, classType);
741   }
742 
743   public boolean isElementAssignableTo(XMLElement elem, JClassType possibleSupertype)
744       throws UnableToCompleteException {
745     /*
746      * Things like <W extends IsWidget & IsPlaid> 
747      */
748     JTypeParameter typeParameter = possibleSupertype.isTypeParameter();
749     if (typeParameter != null) {
750       JClassType[] bounds = typeParameter.getBounds();
751       for (JClassType bound : bounds) {
752         if (!isElementAssignableTo(elem, bound)) {
753           return false;
754         }
755       }
756       return true;
757     }
758     
759     /*
760      * Binder fields are always declared raw, so we're cheating if the
761      * user is playing with parameterized types. We're happy enough if the
762      * raw types match, and rely on them to make sure the specific types
763      * really do work.
764      */
765     JParameterizedType parameterized = possibleSupertype.isParameterized();
766     if (parameterized != null) {
767       return isElementAssignableTo(elem, parameterized.getRawType());
768     }
769 
770     JClassType fieldtype = findFieldType(elem);
771     if (fieldtype == null) {
772       return false;
773     }
774     return fieldtype.isAssignableTo(possibleSupertype);
775   }
776 
777   public boolean isImportedElement(XMLElement elem) {
778     String uri = elem.getNamespaceUri();
779     return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);  
780   }
781 
782   /**
783    * Checks whether the given owner field name is a LazyDomElement or not.
784    */
785   public boolean isOwnerFieldLazyDomElement(String fieldName) {
786     OwnerField ownerField = ownerClass.getUiField(fieldName);
787     if (ownerField == null) {
788       return false;
789     }
790 
791     return lazyDomElementClass.isAssignableFrom(ownerField.getType().getRawType());
792   }
793 
794   public boolean isRenderableElement(XMLElement elem)
795       throws UnableToCompleteException {
796     return findFieldType(elem).isAssignableTo(isRenderableClassType);
797   }
798 
799   public boolean isWidgetElement(XMLElement elem) throws UnableToCompleteException {
800     return isElementAssignableTo(elem, IsWidget.class);
801   }
802 
803   /**
804    * Parses the object associated with the specified element, and returns the
805    * name of the field (possibly private) that will hold it. The element is
806    * likely to make recursive calls back to this method to have its children
807    * parsed.
808    *
809    * @param elem the xml element to be parsed
810    * @return the name of the field containing the parsed widget
811    */
812   public String parseElementToField(XMLElement elem)
813       throws UnableToCompleteException {
814     /**
815      * TODO(hermes,rjrjr,rdcastro): seems bad we have to run
816      * parseElementToFieldWriter(), get the field writer and
817      * then call fieldManager.convertFieldToGetter().
818      *
819      * Can't we move convertFieldToGetter() to FieldWriter?
820      *
821      * The current answer is no because convertFieldToGetter() might be called
822      * before a given FieldWriter is actually created.
823      */
824     FieldWriter field = parseElementToFieldWriter(elem);
825     return fieldManager.convertFieldToGetter(field.getName());
826   }
827 
828   /**
829    * Parses the object associated with the specified element, and returns the
830    * field writer that will hold it. The element is likely to make recursive
831    * calls back to this method to have its children parsed.
832    *
833    * @param elem the xml element to be parsed
834    * @return the field holder just created
835    */
836   public FieldWriter parseElementToFieldWriter(XMLElement elem)
837       throws UnableToCompleteException {
838     if (elementParsers.isEmpty()) {
839       registerParsers();
840     }
841 
842     // Get the class associated with this element.
843     JClassType type = findFieldType(elem);
844 
845     // Declare its field.
846     String fieldName = declareField(type.getQualifiedSourceName(), elem);
847 
848     FieldWriter field = fieldManager.lookup(fieldName);
849 
850     // Push the field that will hold this widget on top of the parsedFieldStack
851     // to ensure that fields registered by its parsers will be noted as
852     // dependencies of the new widget. See registerField.
853     fieldManager.push(field);
854 
855     // Give all the parsers a chance to generate their code.
856     for (ElementParser parser : getParsersForClass(type)) {
857       parser.parse(elem, fieldName, type, this);
858     }
859     fieldManager.pop();
860 
861     return field;
862   }
863 
864   /**
865    * Gives the writer the initializer to use for this field instead of the
866    * default GWT.create call.
867    *
868    * @throws IllegalStateException if an initializer has already been set
869    */
870   public void setFieldInitializer(String fieldName, String factoryMethod) {
871     fieldManager.lookup(fieldName).setInitializer(factoryMethod);
872   }
873 
874   /**
875    * Instructs the writer to initialize the field with a specific constructor
876    * invocation, instead of the default GWT.create call.
877    *
878    * @param fieldName the field to initialize
879    * @param type the type of the field
880    * @param args arguments to the constructor call
881    */
882   public void setFieldInitializerAsConstructor(String fieldName,
883       JClassType type, String... args) {
884     setFieldInitializer(fieldName, formatCode("new %s(%s)",
885         type.getQualifiedSourceName(), asCommaSeparatedList(args)));
886   }
887 
888   /**
889    * Like {@link #tokenForStringExpression}, but used for runtime expressions
890    * that we trust to be safe to interpret at runtime as HTML without escaping,
891    * like translated messages with simple formatting. Wrapped in a call to
892    * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant} to
893    * keep the expression from being escaped by the SafeHtml template.
894    *
895    * @param expression must resolve to trusted HTML string
896    */
897   public String tokenForSafeConstant(String expression) {
898     if (!useSafeHtmlTemplates) {
899       return tokenForStringExpression(expression);
900     }
901 
902     expression = "SafeHtmlUtils.fromSafeConstant(" + expression + ")";
903     htmlTemplates.noteSafeConstant(expression);
904     return tokenator.nextToken(expression);
905   }
906 
907   /**
908    * Like {@link #tokenForStringExpression}, but used for runtime
909    * {@link com.google.gwt.safehtml.shared.SafeHtml SafeHtml} instances.
910    * 
911    * @param expression must resolve to SafeHtml object
912    */
913   public String tokenForSafeHtmlExpression(String expression) {
914     if (!useSafeHtmlTemplates) {
915       return tokenForStringExpression(expression + ".asString()");
916     }
917 
918     htmlTemplates.noteSafeConstant(expression);
919     return tokenator.nextToken(expression);
920   }
921 
922   /**
923    * Returns a string token that can be used in place the given expression
924    * inside any string literals. Before the generated code is written, the
925    * expression will be stitched back into the generated code in place of the
926    * token, surrounded by plus signs. This is useful in strings to be handed to
927    * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
928    * other runtime expression in the string.
929    *
930    * @param expression must resolve to String
931    */
932   public String tokenForStringExpression(String expression) {
933     return tokenator.nextToken(("\" + " + expression + " + \""));
934   }
935 
936   public boolean useLazyWidgetBuilders() {
937     return useLazyWidgetBuilders;
938   }
939 
940   /**
941    * @return true of SafeHtml integration is in effect
942    */
943   public boolean useSafeHtmlTemplates() {
944     return useSafeHtmlTemplates;
945   }
946 
947   /**
948    * Post a warning message.
949    */
950   public void warn(String message) {
951     logger.warn(message);
952   }
953 
954   /**
955    * Post a warning message.
956    */
957   public void warn(String message, Object... params) {
958     logger.warn(message, params);
959   }
960 
961   /**
962    * Post a warning message.
963    */
964   public void warn(XMLElement context, String message, Object... params) {
965     logger.warn(context, message, params);
966   }
967 
968   /**
969    * Entry point for the code generation logic. It generates the
970    * implementation's superstructure, and parses the root widget (leading to all
971    * of its children being parsed as well).
972    *
973    * @param doc TODO
974    */
975   void parseDocument(Document doc, PrintWriter printWriter)
976       throws UnableToCompleteException {
977     JClassType uiBinderClass = getOracle().findType(UiBinder.class.getName());
978     if (!baseClass.isAssignableTo(uiBinderClass)) {
979       die(baseClass.getName() + " must implement UiBinder");
980     }
981 
982     Element documentElement = doc.getDocumentElement();
983     gwtPrefix = documentElement.lookupPrefix(binderUri);
984 
985     XMLElement elem = new XMLElementProviderImpl(attributeParsers,
986         bundleParsers, oracle, logger, designTime).get(documentElement);
987     this.rendered = tokenator.detokenate(parseDocumentElement(elem));
988     printWriter.print(rendered);
989   }
990 
991   private void addElementParser(String gwtClass, String parser) {
992     elementParsers.put(gwtClass, parser);
993   }
994 
995   private void addWidgetParser(String className) {
996     String gwtClass = "com.google.gwt.user.client.ui." + className;
997     String parser = "com.google.gwt.uibinder.elementparsers." + className
998         + "Parser";
999     addElementParser(gwtClass, parser);
1000   }
1001 
1002   /**
1003    * Outputs a bundle resource for a given bundle attribute parser.
1004    */
1005   private String declareStaticField(BundleAttributeParser parser) {
1006     if (!parser.isBundleStatic()) {
1007       return null;
1008     }
1009 
1010     String fullBundleClassName = parser.fullBundleClassName();
1011 
1012     StringBuilder b = new StringBuilder();
1013     b.append("static ").append(fullBundleClassName).append(" ").append(
1014         parser.bundleInstance()).append(" = GWT.create(").append(
1015         fullBundleClassName).append(".class);");
1016 
1017     return b.toString();
1018   }
1019 
1020   /**
1021    * Ensures that all of the internal data structures are cleaned up correctly
1022    * at the end of parsing the document.
1023    *
1024    * @throws IllegalStateException
1025    */
1026   private void ensureAttachmentCleanedUp() {
1027     if (!attachSectionElements.isEmpty()) {
1028       throw new IllegalStateException("Attachments not cleaned up: "
1029           + attachSectionElements);
1030     }
1031     if (!detachStatementsStack.isEmpty()) {
1032       throw new IllegalStateException("Detach not cleaned up: "
1033           + detachStatementsStack);
1034     }
1035   }
1036 
1037   /**
1038    * Evaluate whether all @UiField attributes are also defined in the
1039    * template. Dies if not.
1040    */
1041   private void evaluateUiFields() throws UnableToCompleteException {
1042     if (designTime.isDesignTime()) {
1043       return;
1044     }
1045     for (OwnerField ownerField : getOwnerClass().getUiFields()) {
1046       String fieldName = ownerField.getName();
1047       FieldWriter fieldWriter = fieldManager.lookup(fieldName);
1048 
1049       if (fieldWriter == null) {
1050         die("Template %s has no %s attribute for %s.%s#%s", templatePath,
1051             getUiFieldAttributeName(), uiOwnerType.getPackage().getName(),
1052             uiOwnerType.getName(), fieldName);
1053       }
1054     }
1055   }
1056 
1057   /**
1058    * Given a DOM tag name, return the corresponding JSO subclass.
1059    */
1060   private JClassType findDomElementTypeForTag(String tag) {
1061     JClassType elementClass = oracle.findType("com.google.gwt.dom.client.Element");
1062     JClassType[] types = elementClass.getSubtypes();
1063     for (JClassType type : types) {
1064       TagName annotation = type.getAnnotation(TagName.class);
1065       if (annotation != null) {
1066         for (String annotationTag : annotation.value()) {
1067           if (annotationTag.equals(tag)) {
1068             return type;
1069           }
1070         }
1071       }
1072     }
1073     return elementClass;
1074   }
1075 
1076   /**
1077    * Use this method to format code. It forces the use of the en-US locale, so
1078    * that things like decimal format don't get mangled.
1079    */
1080   private String formatCode(String format, Object... params) {
1081     String r = String.format(Locale.US, format, params);
1082     return r;
1083   }
1084 
1085   /**
1086    * Inspects this element for a gwt:field attribute. If one is found, the
1087    * attribute is consumed and its value returned.
1088    *
1089    * @return The field name declared by an element, or null if none is declared
1090    */
1091   private String getFieldName(XMLElement elem) throws UnableToCompleteException {
1092     String fieldName = null;
1093     boolean hasOldSchoolId = false;
1094     if (elem.hasAttribute("id") && isWidgetElement(elem)) {
1095       hasOldSchoolId = true;
1096       // If an id is specified on the element, use that.
1097       fieldName = elem.consumeRawAttribute("id");
1098       warn(elem, "Deprecated use of id=\"%1$s\" for field name. "
1099           + "Please switch to gwt:field=\"%1$s\" instead. "
1100           + "This will soon be a compile error!", fieldName);
1101     }
1102     if (elem.hasAttribute(getUiFieldAttributeName())) {
1103       if (hasOldSchoolId) {
1104         die(elem, "Cannot declare both id and field on the same element");
1105       }
1106       fieldName = elem.consumeRawAttribute(getUiFieldAttributeName());
1107     }
1108     return fieldName;
1109   }
1110 
1111   // laaglu
1112   private String getAnnotatedParserForClass(JClassType uiClass) {
1113 	    String parserClassName = null;
1114 	    if (uiClass.isAnnotationPresent(ElementParserToUse.class)) {
1115 	      String uiClassName = uiClass.getQualifiedSourceName();
1116 	      parserClassName = uiClass.getAnnotation(ElementParserToUse.class).className();
1117 	      elementParsers.put(uiClassName, parserClassName);
1118 	    }
1119 	    return parserClassName;
1120 	  }
1121   // laaglu
1122 
1123   private Class<? extends ElementParser> getParserForClass(JClassType uiClass) {
1124     // Find the associated parser.
1125     String uiClassName = uiClass.getQualifiedSourceName();
1126     String parserClassName = elementParsers.get(uiClassName);
1127     if (parserClassName == null) {
1128         // laaglu
1129         parserClassName = getAnnotatedParserForClass(uiClass);
1130         if (parserClassName == null) {
1131           return null;
1132         }
1133         // laaglu
1134     }
1135     
1136     // And instantiate it.
1137     try {
1138       return Class.forName(parserClassName).asSubclass(ElementParser.class);
1139     } catch (ClassNotFoundException e) {
1140       throw new RuntimeException("Unable to instantiate parser", e);
1141     } catch (ClassCastException e) {
1142       throw new RuntimeException(parserClassName + " must extend ElementParser");
1143     }
1144   }
1145 
1146   /**
1147    * Find a set of element parsers for the given ui type.
1148    *
1149    * The list of parsers will be returned in order from most- to least-specific.
1150    */
1151   private Iterable<ElementParser> getParsersForClass(JClassType type) {
1152     List<ElementParser> parsers = new ArrayList<ElementParser>();
1153 
1154     /*
1155      * Let this non-widget parser go first (it finds <m:attribute/> elements).
1156      * Any other such should land here too.
1157      *
1158      * TODO(rjrjr) Need a scheme to associate these with a namespace uri or
1159      * something?
1160      */
1161     parsers.add(new AttributeMessageParser());
1162     parsers.add(new UiChildParser(uiBinderCtx));
1163 
1164     for (JClassType curType : getClassHierarchyBreadthFirst(type)) {
1165       try {
1166         Class<? extends ElementParser> cls = getParserForClass(curType);
1167         if (cls != null) {
1168           ElementParser parser = cls.newInstance();
1169           parsers.add(parser);
1170         }
1171       } catch (InstantiationException e) {
1172         throw new RuntimeException(
1173             "Unable to instantiate " + curType.getName(), e);
1174       } catch (IllegalAccessException e) {
1175         throw new RuntimeException(
1176             "Unable to instantiate " + curType.getName(), e);
1177       }
1178     }
1179 
1180     parsers.add(new BeanParser(uiBinderCtx));
1181     parsers.add(new IsEmptyParser());
1182 
1183     return parsers;
1184   }
1185 
1186   /**
1187    * Writes a field setter if the field is not provided and the field class is
1188    * compatible with its respective template field.
1189    */
1190   private void maybeWriteFieldSetter(IndentedWriter niceWriter,
1191       OwnerField ownerField, JClassType templateClass, String templateField)
1192       throws UnableToCompleteException {
1193     JClassType fieldType = ownerField.getType().getRawType();
1194 
1195     if (!ownerField.isProvided()) {
1196       /*
1197        * Normally check that the type the template created can be slammed into
1198        * the @UiField annotated field in the owning class
1199        */
1200       if (!templateClass.isAssignableTo(fieldType)) {
1201         die(
1202             "In @UiField %s, template field and owner field types don't match: %s is not assignable to %s",
1203             ownerField.getName(), templateClass.getQualifiedSourceName(),
1204             fieldType.getQualifiedSourceName());
1205       }
1206       /*
1207        * And initialize the field
1208        */
1209       niceWriter.write("owner.%1$s = %2$s;", ownerField.getName(),
1210           templateField);
1211     } else {
1212       /*
1213        * But with @UiField(provided=true) the user builds it, so reverse the
1214        * direction of the assignability check and do no init.
1215        */
1216       if (!fieldType.isAssignableTo(templateClass)) {
1217         die(
1218             "In UiField(provided = true) %s, template field and field types don't match: "
1219                 + "@UiField(provided=true)%s is not assignable to %s",
1220             ownerField.getName(), fieldType.getQualifiedSourceName(),
1221             templateClass.getQualifiedSourceName());
1222       }
1223     }
1224   }
1225 
1226   private String normalizeFieldName(String fieldName) {
1227     // If a field name has a '.' in it, replace it with '$' to make it a legal
1228     // identifier. This can happen with the field names associated with nested
1229     // classes.
1230     return fieldName.replace('.', '$');
1231   }
1232 
1233   /**
1234    * Parse the document element and return the source of the Java class that
1235    * will implement its UiBinder.
1236    */
1237   private String parseDocumentElement(XMLElement elem)
1238       throws UnableToCompleteException {
1239     fieldManager.registerFieldOfGeneratedType(
1240         oracle.findType(ClientBundle.class.getName()),
1241         bundleClass.getPackageName(), bundleClass.getClassName(),
1242         bundleClass.getFieldName());
1243 
1244     // Allow GWT.create() to init the field, the default behavior
1245 
1246     String rootField = new UiBinderParser(this, messages, fieldManager, oracle,
1247         bundleClass, binderUri).parse(elem);
1248 
1249     fieldManager.validate();
1250 
1251     StringWriter stringWriter = new StringWriter();
1252     IndentedWriter niceWriter = new IndentedWriter(
1253         new PrintWriter(stringWriter));
1254 
1255     if (useLazyWidgetBuilders) {
1256       for (ImplicitCssResource css : bundleClass.getCssMethods()) {
1257         String fieldName = css.getName();
1258         FieldWriter cssField = fieldManager.require(fieldName);
1259         cssField.addStatement("%s.ensureInjected();", fieldName);
1260       }
1261       writeBinderForRenderableStrategy(niceWriter, rootField);
1262     } else {
1263       writeBinder(niceWriter, rootField);
1264     }
1265 
1266     ensureAttachmentCleanedUp();
1267     return stringWriter.toString();
1268   }
1269 
1270   /**
1271    * Parses a package uri (e.g., package://com.google...).
1272    *
1273    * @throws UnableToCompleteException on bad package name
1274    */
1275   private JPackage parseNamespacePackage(String ns)
1276       throws UnableToCompleteException {
1277     if (ns.startsWith(PACKAGE_URI_SCHEME)) {
1278       String pkgName = ns.substring(PACKAGE_URI_SCHEME.length());
1279 
1280       JPackage pkg = oracle.findPackage(pkgName);
1281       if (pkg == null) {
1282         die("Package not found: " + pkgName);
1283       }
1284 
1285       return pkg;
1286     }
1287 
1288     return null;
1289   }
1290 
1291   private void registerParsers() {
1292     // TODO(rjrjr): Allow third-party parsers to register themselves
1293     // automagically
1294 
1295     addElementParser("com.google.gwt.dom.client.Element",
1296         "com.google.gwt.uibinder.elementparsers.DomElementParser");
1297 
1298     // Register widget parsers.
1299     addWidgetParser("UIObject");
1300     addWidgetParser("HasText");
1301     addWidgetParser("HasHTML");
1302     addWidgetParser("HasTreeItems");
1303     addWidgetParser("HasWidgets");
1304     addWidgetParser("HTMLPanel");
1305     addWidgetParser("AbsolutePanel");
1306     addWidgetParser("DockPanel");
1307     addWidgetParser("StackPanel");
1308     addWidgetParser("DisclosurePanel");
1309     addWidgetParser("TabPanel");
1310     addWidgetParser("MenuItem");
1311     addWidgetParser("MenuBar");
1312     addWidgetParser("CellPanel");
1313     addWidgetParser("CustomButton");
1314     addWidgetParser("DialogBox");
1315     addWidgetParser("LayoutPanel");
1316     addWidgetParser("DockLayoutPanel");
1317     addWidgetParser("StackLayoutPanel");
1318     addWidgetParser("TabLayoutPanel");
1319     addWidgetParser("Image");
1320     addWidgetParser("ListBox");
1321     addWidgetParser("Grid");
1322     addWidgetParser("HasAlignment");
1323     addWidgetParser("DateLabel");
1324     addWidgetParser("NumberLabel");
1325     if (useLazyWidgetBuilders) {
1326       addWidgetParser("LazyPanel");
1327       addWidgetParser("RenderablePanel");
1328     }
1329   }
1330 
1331   /**
1332    * Write statements that parsers created via calls to {@link #addStatement}.
1333    * Such statements will assume that {@link #writeGwtFields} has already been
1334    * called.
1335    */
1336   private void writeAddedStatements(IndentedWriter niceWriter) {
1337     for (String s : statements) {
1338       niceWriter.write(s);
1339     }
1340   }
1341 
1342   /**
1343    * Writes the UiBinder's source.
1344    */
1345   private void writeBinder(IndentedWriter w, String rootField)
1346       throws UnableToCompleteException {
1347     writePackage(w);
1348 
1349     writeImports(w);
1350     w.newline();
1351 
1352     writeClassOpen(w);
1353     writeStatics(w);
1354     w.newline();
1355 
1356     // Create SafeHtml Template
1357     writeSafeHtmlTemplates(w);
1358 
1359     // createAndBindUi method
1360     w.write("public %s createAndBindUi(final %s owner) {",
1361         uiRootType.getParameterizedQualifiedSourceName(),
1362         uiOwnerType.getParameterizedQualifiedSourceName());
1363     w.indent();
1364     w.newline();
1365 
1366     writeGwtFields(w);
1367     w.newline();
1368 
1369     designTime.writeAttributes(this);
1370     writeAddedStatements(w);
1371     w.newline();
1372 
1373     writeInitStatements(w);
1374     w.newline();
1375 
1376     writeHandlers(w);
1377     w.newline();
1378 
1379     writeOwnerFieldSetters(w);
1380 
1381     writeCssInjectors(w);
1382 
1383     w.write("return %s;", rootField);
1384     w.outdent();
1385     w.write("}");
1386 
1387     // Close class
1388     w.outdent();
1389     w.write("}");
1390   }
1391 
1392   /**
1393    * Writes a different optimized UiBinder's source for the renderable
1394    * strategy.
1395    */
1396   private void writeBinderForRenderableStrategy(
1397       IndentedWriter w, String rootField) throws UnableToCompleteException {
1398     writePackage(w);
1399 
1400     writeImports(w);
1401     w.newline();
1402 
1403     writeClassOpen(w);
1404     writeStatics(w);
1405     w.newline();
1406 
1407     // Create SafeHtml Template
1408     writeSafeHtmlTemplates(w);
1409 
1410     w.newline();
1411 
1412     // createAndBindUi method
1413     w.write("public %s createAndBindUi(final %s owner) {",
1414         uiRootType.getParameterizedQualifiedSourceName(),
1415         uiOwnerType.getParameterizedQualifiedSourceName());
1416     w.indent();
1417     w.newline();
1418 
1419     designTime.writeAttributes(this);
1420     w.newline();
1421 
1422     w.write("return new Widgets(owner).%s;", rootField);
1423     w.outdent();
1424     w.write("}");
1425 
1426     // Writes the inner class Widgets.
1427     w.newline();
1428     w.write("/**");
1429     w.write(" * Encapsulates the access to all inner widgets");
1430     w.write(" */");
1431     w.write("class Widgets {");
1432     w.indent();
1433 
1434     String ownerClassType = uiOwnerType.getParameterizedQualifiedSourceName();
1435     w.write("private final %s owner;", ownerClassType);
1436     w.newline();
1437 
1438     writeHandlers(w);
1439     w.newline();
1440 
1441     w.write("public Widgets(final %s owner) {", ownerClassType);
1442     w.indent();
1443     w.write("this.owner = owner;");
1444     fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
1445     w.outdent();
1446     w.write("}");
1447 
1448     evaluateUiFields();
1449 
1450     fieldManager.writeFieldDefinitions(
1451         w, getOracle(), getOwnerClass(), getDesignTime());
1452 
1453     w.outdent();
1454     w.write("}");
1455 
1456     // Close class
1457     w.outdent();
1458     w.write("}");
1459   }
1460 
1461   private void writeClassOpen(IndentedWriter w) {
1462     w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
1463         uiRootType.getParameterizedQualifiedSourceName(),
1464         uiOwnerType.getParameterizedQualifiedSourceName(),
1465         baseClass.getParameterizedQualifiedSourceName());
1466     w.indent();
1467   }
1468 
1469   private void writeCssInjectors(IndentedWriter w) {
1470     for (ImplicitCssResource css : bundleClass.getCssMethods()) {
1471       w.write("%s.%s().ensureInjected();", bundleClass.getFieldName(),
1472           css.getName());
1473     }
1474     w.newline();
1475   }
1476 
1477   /**
1478    * Write declarations for variables or fields to hold elements declared with
1479    * gwt:field in the template. For those that have not had constructor
1480    * generation suppressed, emit GWT.create() calls instantiating them (or die
1481    * if they have no default constructor).
1482    *
1483    * @throws UnableToCompleteException on constructor problem
1484    */
1485   private void writeGwtFields(IndentedWriter niceWriter)
1486       throws UnableToCompleteException {
1487     // For each provided field in the owner class, initialize from the owner
1488     Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
1489     for (OwnerField ownerField : ownerFields) {
1490       if (ownerField.isProvided()) {
1491         String fieldName = ownerField.getName();
1492         FieldWriter fieldWriter = fieldManager.lookup(fieldName);
1493 
1494         // TODO why can this be null?
1495         if (fieldWriter != null) {
1496           String initializer;
1497           if (designTime.isDesignTime()) {
1498             String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
1499             initializer = designTime.getProvidedField(typeName,
1500                 ownerField.getName());
1501           } else {
1502             initializer = formatCode("owner.%1$s", fieldName);
1503           }
1504           fieldManager.lookup(fieldName).setInitializer(initializer);
1505         }
1506       }
1507     }
1508 
1509     // Write gwt field declarations.
1510     fieldManager.writeGwtFieldsDeclaration(niceWriter, uiOwnerType.getName());
1511   }
1512 
1513   private void writeHandlers(IndentedWriter w) throws UnableToCompleteException {
1514     if (designTime.isDesignTime()) {
1515       return;
1516     }
1517     handlerEvaluator.run(w, fieldManager, "owner");
1518   }
1519 
1520   private void writeImports(IndentedWriter w) {
1521     w.write("import com.google.gwt.core.client.GWT;");
1522     w.write("import com.google.gwt.dom.client.Element;");
1523     if (!(htmlTemplates.isEmpty())) {
1524       w.write("import com.google.gwt.safehtml.client.SafeHtmlTemplates;");
1525       w.write("import com.google.gwt.safehtml.shared.SafeHtml;");
1526       w.write("import com.google.gwt.safehtml.shared.SafeHtmlUtils;");
1527     }
1528     w.write("import com.google.gwt.uibinder.client.UiBinder;");
1529     w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
1530     w.write("import %s.%s;", uiRootType.getPackage().getName(),
1531         uiRootType.getName());
1532   }
1533 
1534   /**
1535    * Write statements created by {@link #addInitStatement}. This code must be
1536    * placed after all instantiation code.
1537    */
1538   private void writeInitStatements(IndentedWriter niceWriter) {
1539     for (String s : initStatements) {
1540       niceWriter.write(s);
1541     }
1542   }
1543 
1544   /**
1545    * Write the statements to fill in the fields of the UI owner.
1546    */
1547   private void writeOwnerFieldSetters(IndentedWriter niceWriter)
1548       throws UnableToCompleteException {
1549     if (designTime.isDesignTime()) {
1550       return;
1551     }
1552     for (OwnerField ownerField : getOwnerClass().getUiFields()) {
1553       String fieldName = ownerField.getName();
1554       FieldWriter fieldWriter = fieldManager.lookup(fieldName);
1555 
1556       BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType());
1557 
1558       if (bundleParser != null) {
1559         // ownerField is a bundle resource.
1560         maybeWriteFieldSetter(niceWriter, ownerField,
1561             bundleParser.bundleClass(), bundleParser.bundleInstance());
1562 
1563       } else if (fieldWriter != null) {
1564         // ownerField is a widget.
1565         JClassType type = fieldWriter.getInstantiableType();
1566         if (type != null) {
1567           maybeWriteFieldSetter(niceWriter, ownerField,
1568               fieldWriter.getInstantiableType(), fieldName);
1569         } else {
1570           // Must be a generated type
1571           if (!ownerField.isProvided()) {
1572             niceWriter.write("owner.%1$s = %1$s;", fieldName);
1573           }
1574         }
1575 
1576       } else {
1577         // ownerField was not found as bundle resource or widget, must die.
1578         die("Template %s has no %s attribute for %s.%s#%s", templatePath,
1579             getUiFieldAttributeName(), uiOwnerType.getPackage().getName(),
1580             uiOwnerType.getName(), fieldName);
1581       }
1582     }
1583   }
1584 
1585   private void writePackage(IndentedWriter w) {
1586     String packageName = baseClass.getPackage().getName();
1587     if (packageName.length() > 0) {
1588       w.write("package %1$s;", packageName);
1589       w.newline();
1590     }
1591   }
1592 
1593   /**
1594    * Write statements created by {@link HtmlTemplates#addSafeHtmlTemplate}. This
1595    * code must be placed after all instantiation code.
1596    */
1597   private void writeSafeHtmlTemplates(IndentedWriter w) {
1598     if (!(htmlTemplates.isEmpty())) {
1599       assert useSafeHtmlTemplates : "SafeHtml is off, but templates were made.";
1600 
1601       w.write("interface Template extends SafeHtmlTemplates {");
1602       w.indent();
1603 
1604       htmlTemplates.writeTemplates(w);
1605 
1606       w.outdent();
1607       w.write("}");
1608       w.newline();
1609       w.write("Template template = GWT.create(Template.class);");
1610       w.newline();
1611     }
1612   }
1613 
1614   /**
1615    * Generates instances of any bundle classes that have been referenced by a
1616    * namespace entry in the top level element. This must be called *after* all
1617    * parsing is through, as the bundle list is generated lazily as dom elements
1618    * are parsed.
1619    */
1620   private void writeStaticBundleInstances(IndentedWriter niceWriter) {
1621     // TODO(rjrjr) It seems bad that this method has special
1622     // knowledge of BundleAttributeParser, but that'll die soon so...
1623 
1624     Map<String, BundleAttributeParser> bpMap = bundleParsers.getMap();
1625     for (String key : bpMap.keySet()) {
1626       String declaration = declareStaticField(bpMap.get(key));
1627       if (declaration != null) {
1628         niceWriter.write(declaration);
1629       }
1630     }
1631   }
1632 
1633   private void writeStaticMessagesInstance(IndentedWriter niceWriter) {
1634     if (messages.hasMessages()) {
1635       niceWriter.write(messages.getDeclaration());
1636     }
1637   }
1638 
1639   private void writeStatics(IndentedWriter w) {
1640     writeStaticMessagesInstance(w);
1641     writeStaticBundleInstances(w);
1642     designTime.addDeclarations(w);
1643   }
1644 }