| /* |
| * Copyright 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.uibinder.rebind; |
| |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.core.ext.typeinfo.JClassType; |
| import com.google.gwt.core.ext.typeinfo.JField; |
| import com.google.gwt.core.ext.typeinfo.JMethod; |
| import com.google.gwt.core.ext.typeinfo.JPrimitiveType; |
| import com.google.gwt.core.ext.typeinfo.JType; |
| import com.google.gwt.core.ext.typeinfo.TypeOracle; |
| import com.google.gwt.resources.client.CssResource; |
| import com.google.gwt.resources.client.DataResource; |
| import com.google.gwt.resources.client.ImageResource; |
| import com.google.gwt.resources.client.ImageResource.RepeatStyle; |
| import com.google.gwt.uibinder.rebind.messages.MessagesWriter; |
| import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle; |
| import com.google.gwt.uibinder.rebind.model.ImplicitCssResource; |
| import com.google.gwt.uibinder.rebind.model.ImplicitDataResource; |
| import com.google.gwt.uibinder.rebind.model.ImplicitImageResource; |
| import com.google.gwt.uibinder.rebind.model.OwnerField; |
| |
| import java.util.LinkedHashSet; |
| |
| /** |
| * Parses the root UiBinder element, and kicks of the parsing of the rest of the |
| * document. |
| */ |
| public class UiBinderParser { |
| |
| private enum Resource { |
| DATA { |
| @Override |
| void create(UiBinderParser parser, XMLElement elem) |
| throws UnableToCompleteException { |
| parser.createData(elem); |
| } |
| }, |
| IMAGE { |
| @Override |
| void create(UiBinderParser parser, XMLElement elem) |
| throws UnableToCompleteException { |
| parser.createImage(elem); |
| } |
| }, |
| IMPORT { |
| @Override |
| void create(UiBinderParser parser, XMLElement elem) |
| throws UnableToCompleteException { |
| parser.createImport(elem); |
| } |
| }, |
| STYLE { |
| @Override |
| void create(UiBinderParser parser, XMLElement elem) |
| throws UnableToCompleteException { |
| parser.createStyle(elem); |
| } |
| }, |
| WITH { |
| @Override |
| void create(UiBinderParser parser, XMLElement elem) |
| throws UnableToCompleteException { |
| parser.createResource(elem); |
| } |
| }; |
| |
| abstract void create(UiBinderParser parser, XMLElement elem) |
| throws UnableToCompleteException; |
| } |
| |
| private static final String FLIP_RTL_ATTRIBUTE = "flipRtl"; |
| private static final String FIELD_ATTRIBUTE = "field"; |
| private static final String REPEAT_STYLE_ATTRIBUTE = "repeatStyle"; |
| private static final String SOURCE_ATTRIBUTE = "src"; |
| private static final String TYPE_ATTRIBUTE = "type"; |
| |
| // TODO(rjrjr) Make all the ElementParsers receive their dependencies via |
| // constructor like this one does, and make this an ElementParser. I want |
| // guice!!! |
| |
| private static final String IMPORT_ATTRIBUTE = "import"; |
| private static final String TAG = "UiBinder"; |
| private final UiBinderWriter writer; |
| private final TypeOracle oracle; |
| private final MessagesWriter messagesWriter; |
| private final FieldManager fieldManager; |
| private final ImplicitClientBundle bundleClass; |
| private final JClassType cssResourceType; |
| private final JClassType imageResourceType; |
| |
| private final JClassType dataResourceType; |
| |
| public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter, |
| FieldManager fieldManager, TypeOracle oracle, |
| ImplicitClientBundle bundleClass) { |
| this.writer = writer; |
| this.oracle = oracle; |
| this.messagesWriter = messagesWriter; |
| this.fieldManager = fieldManager; |
| this.bundleClass = bundleClass; |
| this.cssResourceType = oracle.findType(CssResource.class.getCanonicalName()); |
| this.imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); |
| this.dataResourceType = oracle.findType(DataResource.class.getCanonicalName()); |
| } |
| |
| /** |
| * Parses the root UiBinder element, and kicks off the parsing of the rest of |
| * the document. |
| */ |
| public String parse(XMLElement elem) throws UnableToCompleteException { |
| if (!writer.isBinderElement(elem)) { |
| writer.die(elem, "Bad prefix on <%s:%s>? The root element must be in " |
| + "xml namespace \"%s\" (usually with prefix \"ui:\"), " |
| + "but this has prefix \"%s\"", elem.getPrefix(), |
| elem.getLocalName(), UiBinderGenerator.BINDER_URI, elem.getPrefix()); |
| } |
| |
| if (!TAG.equals(elem.getLocalName())) { |
| writer.die(elem, "Root element must be %s:%s", elem.getPrefix(), TAG); |
| } |
| |
| findResources(elem); |
| messagesWriter.findMessagesConfig(elem); |
| XMLElement uiRoot = elem.consumeSingleChildElement(); |
| return writer.parseElementToField(uiRoot); |
| } |
| |
| private JClassType consumeCssResourceType(XMLElement elem) |
| throws UnableToCompleteException { |
| String typeName = elem.consumeRawAttribute(TYPE_ATTRIBUTE, null); |
| if (typeName == null) { |
| return cssResourceType; |
| } |
| |
| return findCssResourceType(elem, typeName); |
| } |
| |
| private JClassType consumeTypeAttribute(XMLElement elem) |
| throws UnableToCompleteException { |
| String resourceTypeName = elem.consumeRequiredRawAttribute(TYPE_ATTRIBUTE); |
| |
| JClassType resourceType = oracle.findType(resourceTypeName); |
| if (resourceType == null) { |
| writer.die(elem, "No such type %s", resourceTypeName); |
| } |
| |
| return resourceType; |
| } |
| |
| /** |
| * Interprets <ui:data> elements. |
| */ |
| private void createData(XMLElement elem) throws UnableToCompleteException { |
| String name = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE); |
| String source = elem.consumeRequiredRawAttribute(SOURCE_ATTRIBUTE); |
| ImplicitDataResource dataMethod = bundleClass.createDataResource(name, |
| source); |
| FieldWriter field = fieldManager.registerField(dataResourceType, |
| dataMethod.getName()); |
| field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(), |
| dataMethod.getName())); |
| } |
| |
| /** |
| * Interprets <ui:image> elements. |
| */ |
| private void createImage(XMLElement elem) throws UnableToCompleteException { |
| String name = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE); |
| // @source is optional on ImageResource |
| String source = elem.consumeRawAttribute(SOURCE_ATTRIBUTE, null); |
| |
| Boolean flipRtl = elem.consumeBooleanConstantAttribute(FLIP_RTL_ATTRIBUTE); |
| |
| RepeatStyle repeatStyle = null; |
| if (elem.hasAttribute(REPEAT_STYLE_ATTRIBUTE)) { |
| String value = elem.consumeRawAttribute(REPEAT_STYLE_ATTRIBUTE); |
| try { |
| repeatStyle = RepeatStyle.valueOf(value); |
| } catch (IllegalArgumentException e) { |
| writer.die(elem, "Bad repeatStyle value %s", value); |
| } |
| } |
| |
| ImplicitImageResource imageMethod = bundleClass.createImageResource(name, |
| source, flipRtl, repeatStyle); |
| |
| FieldWriter field = fieldManager.registerField(imageResourceType, |
| imageMethod.getName()); |
| field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(), |
| imageMethod.getName())); |
| } |
| |
| /** |
| * Process <code><ui:import field="com.example.Blah.CONSTANT"></code>. |
| */ |
| private void createImport(XMLElement elem) throws UnableToCompleteException { |
| String rawFieldName = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE); |
| if (elem.getAttributeCount() > 0) { |
| writer.die(elem, "Should only find attribute \"%s\"", FIELD_ATTRIBUTE); |
| } |
| |
| int idx = rawFieldName.lastIndexOf('.'); |
| if (idx < 1) { |
| writer.die(elem, "Attribute %s does not look like a static import " |
| + "reference", FIELD_ATTRIBUTE); |
| } |
| String enclosingName = rawFieldName.substring(0, idx); |
| String constantName = rawFieldName.substring(idx + 1); |
| |
| JClassType enclosingType = oracle.findType(enclosingName); |
| if (enclosingType == null) { |
| writer.die(elem, "Unable to locate type %s", enclosingName); |
| } |
| |
| if ("*".equals(constantName)) { |
| for (JField field : enclosingType.getFields()) { |
| if (!field.isStatic()) { |
| continue; |
| } else if (field.isPublic()) { |
| // OK |
| } else if (field.isProtected() || field.isPrivate()) { |
| continue; |
| } else if (!enclosingType.getPackage().equals( |
| writer.getOwnerClass().getOwnerType().getPackage())) { |
| // package-protected in another package |
| continue; |
| } |
| createSingleImport(elem, enclosingType, enclosingName + "." |
| + field.getName(), field.getName()); |
| } |
| } else { |
| createSingleImport(elem, enclosingType, rawFieldName, constantName); |
| } |
| } |
| |
| /** |
| * Interprets <ui:with> elements. |
| */ |
| private void createResource(XMLElement elem) throws UnableToCompleteException { |
| String resourceName = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE); |
| JClassType resourceType = consumeTypeAttribute(elem); |
| if (elem.getAttributeCount() > 0) { |
| writer.die(elem, "Should only find attributes \"field\" and \"type\""); |
| } |
| |
| FieldWriter fieldWriter = fieldManager.registerField(resourceType, |
| resourceName); |
| OwnerField ownerField = writer.getOwnerClass().getUiField(resourceName); |
| |
| /* Perhaps it is provided via @UiField */ |
| |
| if (ownerField != null) { |
| if (!resourceType.getErasedType().equals(ownerField.getType().getRawType() |
| .getErasedType())) { |
| writer.die(elem, "Type must match %s", ownerField); |
| } |
| if (ownerField.isProvided()) { |
| String initializer; |
| if (writer.getDesignTime().isDesignTime()) { |
| String typeName = ownerField.getType().getRawType().getQualifiedSourceName(); |
| initializer = writer.getDesignTime().getProvidedField(typeName, |
| ownerField.getName()); |
| } else { |
| initializer = "owner." + ownerField.getName(); |
| } |
| fieldWriter.setInitializer(initializer); |
| return; |
| } |
| } |
| |
| /* Nope. Maybe a @UiFactory will make it */ |
| |
| JMethod factoryMethod = writer.getOwnerClass().getUiFactoryMethod( |
| resourceType); |
| if (factoryMethod != null) { |
| String initializer; |
| if (writer.getDesignTime().isDesignTime()) { |
| String typeName = factoryMethod.getReturnType().getQualifiedSourceName(); |
| initializer = writer.getDesignTime().getProvidedFactory(typeName, |
| factoryMethod.getName(), ""); |
| } else { |
| initializer = String.format("owner.%s()", factoryMethod.getName()); |
| } |
| fieldWriter.setInitializer(initializer); |
| } |
| |
| /* |
| * If neither of the above, the FieldWriter's default GWT.create call will |
| * do just fine. |
| */ |
| } |
| |
| private void createSingleImport(XMLElement elem, JClassType enclosingType, |
| String rawFieldName, String constantName) |
| throws UnableToCompleteException { |
| JField field = enclosingType.findField(constantName); |
| if (field == null) { |
| writer.die(elem, "Unable to locate a field named %s in %s", constantName, |
| enclosingType.getQualifiedSourceName()); |
| } else if (!field.isStatic()) { |
| writer.die(elem, "Field %s in type %s is not static", constantName, |
| enclosingType.getQualifiedSourceName()); |
| } |
| |
| JType importType = field.getType(); |
| JClassType fieldType; |
| if (importType instanceof JPrimitiveType) { |
| fieldType = oracle.findType(((JPrimitiveType) importType).getQualifiedBoxedSourceName()); |
| } else { |
| fieldType = (JClassType) importType; |
| } |
| |
| FieldWriter fieldWriter = fieldManager.registerField(fieldType, |
| constantName); |
| fieldWriter.setInitializer(rawFieldName); |
| } |
| |
| private void createStyle(XMLElement elem) throws UnableToCompleteException { |
| String body = elem.consumeUnescapedInnerText(); |
| String[] source = elem.consumeRawArrayAttribute(SOURCE_ATTRIBUTE); |
| |
| if (0 == body.length() && 0 == source.length) { |
| writer.die(elem, "Must have either a src attribute or body text"); |
| } |
| |
| String name = elem.consumeRawAttribute(FIELD_ATTRIBUTE, "style"); |
| JClassType publicType = consumeCssResourceType(elem); |
| |
| String[] importTypeNames = elem.consumeRawArrayAttribute(IMPORT_ATTRIBUTE); |
| LinkedHashSet<JClassType> importTypes = new LinkedHashSet<JClassType>(); |
| for (String type : importTypeNames) { |
| importTypes.add(findCssResourceType(elem, type)); |
| } |
| |
| ImplicitCssResource cssMethod = bundleClass.createCssResource(name, source, |
| publicType, body, importTypes); |
| |
| FieldWriter field = fieldManager.registerFieldForGeneratedCssResource(cssMethod); |
| field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(), |
| cssMethod.getName())); |
| } |
| |
| private JClassType findCssResourceType(XMLElement elem, String typeName) |
| throws UnableToCompleteException { |
| JClassType publicType = oracle.findType(typeName); |
| if (publicType == null) { |
| writer.die(elem, "No such type %s", typeName); |
| } |
| |
| if (!publicType.isAssignableTo(cssResourceType)) { |
| writer.die(elem, "Type %s does not extend %s", |
| publicType.getQualifiedSourceName(), |
| cssResourceType.getQualifiedSourceName()); |
| } |
| return publicType; |
| } |
| |
| private void findResources(XMLElement binderElement) |
| throws UnableToCompleteException { |
| binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() { |
| public Boolean interpretElement(XMLElement elem) |
| throws UnableToCompleteException { |
| |
| if (writer.isBinderElement(elem)) { |
| try { |
| Resource.valueOf(elem.getLocalName().toUpperCase()).create( |
| UiBinderParser.this, elem); |
| } catch (IllegalArgumentException e) { |
| writer.die(elem, |
| "Unknown tag %s, or is not appropriate as a top level element", |
| elem.getLocalName()); |
| } |
| return true; |
| } |
| return false; // leave it be |
| } |
| }); |
| } |
| } |