1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
64
65
66
67
68
69
70 @SuppressWarnings("deprecation")
71 public class UiBinderWriter implements Statements {
72 private static final String PACKAGE_URI_SCHEME = "urn:import:";
73
74
75
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
93
94
95 public static String escapeAttributeText(String text) {
96 text = escapeText(text, false);
97
98
99
100
101
102 text = text.replaceAll("'", "'");
103 return text;
104 }
105
106
107
108
109
110 public static String escapeText(String text, boolean preserveWhitespace) {
111
112
113
114 text = text.replaceAll("&", "&");
115 text = text.replaceAll("<", "<");
116 text = text.replaceAll(">", ">");
117
118 if (!preserveWhitespace) {
119 text = text.replaceAll("\\s+", " ");
120 }
121
122 return escapeTextForJavaStringLiteral(text);
123 }
124
125
126
127
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
139
140
141
142
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
151 JClassType curType = q.removeFirst();
152 list.add(curType);
153
154
155
156 for (JClassType intf : curType.getImplementedInterfaces()) {
157 q.add(intf);
158 }
159
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
177
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
193
194 private final JClassType baseClass;
195
196
197
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
228
229 private final LinkedList<String> attachSectionElements = new LinkedList<String>();
230
231
232
233 private final Map<String, String> attachedVars = new HashMap<String, String>();
234
235 private int nextAttachVar = 0;
236
237
238
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
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
309
310
311
312
313
314
315
316 public void addDetachStatement(String format, Object... args) {
317 detachStatementsStack.getFirst().add(String.format(format, args));
318 }
319
320
321
322
323
324 public void addInitStatement(String format, Object... params) {
325 initStatements.add(formatCode(format, params));
326 }
327
328
329
330
331
332 public void addStatement(String format, Object... args) {
333 String code = formatCode(format, args);
334
335 if (useLazyWidgetBuilders) {
336
337
338
339
340
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
352
353
354
355
356
357
358
359
360
361
362
363 public void beginAttachedSection(String element) {
364 attachSectionElements.addFirst(element);
365 detachStatementsStack.addFirst(new ArrayList<String>());
366 }
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381 public String declareDomField(String fieldName, String ancestorField)
382 throws UnableToCompleteException {
383 ensureAttached();
384 String name = declareDomIdHolder();
385
386 if (useLazyWidgetBuilders) {
387
388 FieldWriter field = fieldManager.require(fieldName);
389
390
391
392
393
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
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
422
423
424
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
437
438
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
450
451
452
453 fieldName = "f_" + elem.getLocalName() + ++fieldIndex;
454 }
455 fieldName = normalizeFieldName(fieldName);
456 fieldManager.registerField(type, fieldName);
457 return fieldName;
458 }
459
460
461
462
463
464
465
466
467 public String declareFieldIfNeeded(XMLElement elem)
468 throws UnableToCompleteException {
469 String fieldName = getFieldName(elem);
470 if (fieldName != null) {
471
472
473
474
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
487
488
489
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
502
503
504
505
506
507
508 public String detokenate(String betokened) {
509 return tokenator.detokenate(betokened);
510 }
511
512
513
514
515
516 public void die(String message) throws UnableToCompleteException {
517 logger.die(message);
518 }
519
520
521
522
523
524 public void die(String message, Object... params)
525 throws UnableToCompleteException {
526 logger.die(message, params);
527 }
528
529
530
531
532
533 public void die(XMLElement context, String message, Object... params)
534 throws UnableToCompleteException {
535 logger.die(context, message, params);
536 }
537
538
539
540
541
542
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
558
559
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
575
576
577
578
579
580 public void ensureCurrentFieldAttached() {
581 ensureAttached();
582 }
583
584
585
586
587
588
589
590
591 public JClassType findFieldType(XMLElement elem)
592 throws UnableToCompleteException {
593 String tagName = elem.getLocalName();
594
595
596 String uri = elem.getNamespaceUri();
597 if (SVGConstants.SVG_NAMESPACE_URI.equals(uri)) {
598 return findSvgDomElementTypeForTag(tagName);
599 }
600
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
622
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
633
634
635
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
654
655
656
657
658
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
667
668 public void genStringPropertySet(String fieldName, String propName,
669 String value) {
670 genPropertySet(fieldName, propName, "\"" + value + "\"");
671 }
672
673
674
675
676
677
678
679
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
693
694 public DesignTimeUtils getDesignTime() {
695 return designTime;
696 }
697
698 public FieldManager getFieldManager() {
699 return fieldManager;
700 }
701
702
703
704
705
706 public MortalLogger getLogger() {
707 return logger;
708 }
709
710
711
712
713 public MessagesWriter getMessages() {
714 return messages;
715 }
716
717
718
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
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
761
762
763
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
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
805
806
807
808
809
810
811
812 public String parseElementToField(XMLElement elem)
813 throws UnableToCompleteException {
814
815
816
817
818
819
820
821
822
823
824 FieldWriter field = parseElementToFieldWriter(elem);
825 return fieldManager.convertFieldToGetter(field.getName());
826 }
827
828
829
830
831
832
833
834
835
836 public FieldWriter parseElementToFieldWriter(XMLElement elem)
837 throws UnableToCompleteException {
838 if (elementParsers.isEmpty()) {
839 registerParsers();
840 }
841
842
843 JClassType type = findFieldType(elem);
844
845
846 String fieldName = declareField(type.getQualifiedSourceName(), elem);
847
848 FieldWriter field = fieldManager.lookup(fieldName);
849
850
851
852
853 fieldManager.push(field);
854
855
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
866
867
868
869
870 public void setFieldInitializer(String fieldName, String factoryMethod) {
871 fieldManager.lookup(fieldName).setInitializer(factoryMethod);
872 }
873
874
875
876
877
878
879
880
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
890
891
892
893
894
895
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
909
910
911
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
924
925
926
927
928
929
930
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
942
943 public boolean useSafeHtmlTemplates() {
944 return useSafeHtmlTemplates;
945 }
946
947
948
949
950 public void warn(String message) {
951 logger.warn(message);
952 }
953
954
955
956
957 public void warn(String message, Object... params) {
958 logger.warn(message, params);
959 }
960
961
962
963
964 public void warn(XMLElement context, String message, Object... params) {
965 logger.warn(context, message, params);
966 }
967
968
969
970
971
972
973
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
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
1022
1023
1024
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
1039
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
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
1078
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
1087
1088
1089
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
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
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
1122
1123 private Class<? extends ElementParser> getParserForClass(JClassType uiClass) {
1124
1125 String uiClassName = uiClass.getQualifiedSourceName();
1126 String parserClassName = elementParsers.get(uiClassName);
1127 if (parserClassName == null) {
1128
1129 parserClassName = getAnnotatedParserForClass(uiClass);
1130 if (parserClassName == null) {
1131 return null;
1132 }
1133
1134 }
1135
1136
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
1148
1149
1150
1151 private Iterable<ElementParser> getParsersForClass(JClassType type) {
1152 List<ElementParser> parsers = new ArrayList<ElementParser>();
1153
1154
1155
1156
1157
1158
1159
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
1188
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
1198
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
1208
1209 niceWriter.write("owner.%1$s = %2$s;", ownerField.getName(),
1210 templateField);
1211 } else {
1212
1213
1214
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
1228
1229
1230 return fieldName.replace('.', '$');
1231 }
1232
1233
1234
1235
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
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
1272
1273
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
1293
1294
1295 addElementParser("com.google.gwt.dom.client.Element",
1296 "com.google.gwt.uibinder.elementparsers.DomElementParser");
1297
1298
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
1333
1334
1335
1336 private void writeAddedStatements(IndentedWriter niceWriter) {
1337 for (String s : statements) {
1338 niceWriter.write(s);
1339 }
1340 }
1341
1342
1343
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
1357 writeSafeHtmlTemplates(w);
1358
1359
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
1388 w.outdent();
1389 w.write("}");
1390 }
1391
1392
1393
1394
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
1408 writeSafeHtmlTemplates(w);
1409
1410 w.newline();
1411
1412
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
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
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
1479
1480
1481
1482
1483
1484
1485 private void writeGwtFields(IndentedWriter niceWriter)
1486 throws UnableToCompleteException {
1487
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
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
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
1536
1537
1538 private void writeInitStatements(IndentedWriter niceWriter) {
1539 for (String s : initStatements) {
1540 niceWriter.write(s);
1541 }
1542 }
1543
1544
1545
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
1560 maybeWriteFieldSetter(niceWriter, ownerField,
1561 bundleParser.bundleClass(), bundleParser.bundleInstance());
1562
1563 } else if (fieldWriter != null) {
1564
1565 JClassType type = fieldWriter.getInstantiableType();
1566 if (type != null) {
1567 maybeWriteFieldSetter(niceWriter, ownerField,
1568 fieldWriter.getInstantiableType(), fieldName);
1569 } else {
1570
1571 if (!ownerField.isProvided()) {
1572 niceWriter.write("owner.%1$s = %1$s;", fieldName);
1573 }
1574 }
1575
1576 } else {
1577
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
1595
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
1616
1617
1618
1619
1620 private void writeStaticBundleInstances(IndentedWriter niceWriter) {
1621
1622
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 }