| /* |
| * Copyright 2010 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.elementparsers; |
| |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.core.ext.typeinfo.JAbstractMethod; |
| import com.google.gwt.core.ext.typeinfo.JClassType; |
| import com.google.gwt.core.ext.typeinfo.JMethod; |
| import com.google.gwt.core.ext.typeinfo.JParameter; |
| import com.google.gwt.dev.util.Pair; |
| import com.google.gwt.uibinder.rebind.UiBinderContext; |
| import com.google.gwt.uibinder.rebind.UiBinderWriter; |
| import com.google.gwt.uibinder.rebind.XMLElement; |
| import com.google.gwt.uibinder.rebind.XMLElement.Interpreter; |
| import com.google.gwt.uibinder.rebind.model.OwnerFieldClass; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| /** |
| * Parses any children of widgets that use the |
| * {@link com.google.gwt.uibinder.client.UiChild UIChild} annotation. |
| */ |
| public class UiChildParser implements ElementParser { |
| |
| private String fieldName; |
| |
| /** |
| * Mapping of child tag to the number of times it has been called. |
| */ |
| private Map<String, Integer> numCallsToChildMethod = new HashMap<String, Integer>(); |
| private Map<String, Pair<JMethod, Integer>> uiChildMethods; |
| private UiBinderWriter writer; |
| private final UiBinderContext uiBinderCtx; |
| |
| /** |
| * @param uiBinderCtx |
| */ |
| public UiChildParser(UiBinderContext uiBinderCtx) { |
| this.uiBinderCtx = uiBinderCtx; |
| } |
| |
| public void parse(final XMLElement elem, String fieldName, JClassType type, |
| UiBinderWriter writer) throws UnableToCompleteException { |
| this.fieldName = fieldName; |
| this.writer = writer; |
| |
| OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type, |
| writer.getLogger(), uiBinderCtx); |
| |
| uiChildMethods = ownerFieldClass.getUiChildMethods(); |
| |
| // Parse children. |
| elem.consumeChildElements(new Interpreter<Boolean>() { |
| public Boolean interpretElement(XMLElement child) |
| throws UnableToCompleteException { |
| if (isValidChildElement(elem, child)) { |
| handleChild(child); |
| return true; |
| } |
| return false; |
| } |
| }); |
| } |
| |
| /** |
| * Checks if this call will go over the limit for the number of valid calls. |
| * If it won't, it will increment the number of calls made. |
| * |
| * @throws UnableToCompleteException |
| */ |
| private void checkLimit(int limit, String tag, XMLElement toAdd) |
| throws UnableToCompleteException { |
| Integer priorCalls = numCallsToChildMethod.get(tag); |
| if (priorCalls == null) { |
| priorCalls = 0; |
| } |
| if (limit > 0 && priorCalls > 0 && priorCalls + 1 > limit) { |
| writer.die(toAdd, "Can only use the @UiChild tag " + tag + " " + limit |
| + " time(s)."); |
| } |
| numCallsToChildMethod.put(tag, priorCalls + 1); |
| } |
| |
| private JClassType getFirstParamType(JMethod method) { |
| return method.getParameters()[0].getType().isClassOrInterface(); |
| } |
| |
| /** |
| * Process a child element that should be added using a |
| * {@link com.google.gwt.uibinder.client.UiChild UiChild} method. |
| */ |
| private void handleChild(XMLElement child) throws UnableToCompleteException { |
| String tag = child.getLocalName(); |
| Pair<JMethod, Integer> methodPair = uiChildMethods.get(tag); |
| JMethod method = methodPair.left; |
| int limit = methodPair.right; |
| Iterator<XMLElement> children = child.consumeChildElements().iterator(); |
| |
| // If the UiChild tag has no children just return. |
| if (!children.hasNext()) { |
| return; |
| } |
| XMLElement toAdd = children.next(); |
| |
| if (!writer.isImportedElement(toAdd)) { |
| writer.die(child, "Expected child from a urn:import namespace, found %s", |
| toAdd); |
| } |
| |
| JClassType paramClass = getFirstParamType(method); |
| if (!writer.isElementAssignableTo(toAdd, paramClass)) { |
| writer.die(child, "Expected child of type %s in %s, found %s", |
| paramClass.getSimpleSourceName(), child, toAdd); |
| } |
| |
| // Make sure that there is only one element per tag. |
| if (children.hasNext()) { |
| writer.die(toAdd, "Can only have one element per @UiChild parser tag."); |
| } |
| |
| // Check that this element won't put us over the limit. |
| checkLimit(limit, tag, toAdd); |
| |
| // Add the child using the @UiChild function |
| String[] parameters = makeArgsList(child, method, toAdd); |
| |
| writer.addStatement("%1$s.%2$s(%3$s);", fieldName, method.getName(), |
| UiBinderWriter.asCommaSeparatedList(parameters)); |
| } |
| |
| private boolean isValidChildElement(XMLElement parent, XMLElement child) { |
| if (child != null && child.getNamespaceUri() != null |
| && child.getNamespaceUri().equals(parent.getNamespaceUri()) |
| && uiChildMethods.containsKey(child.getLocalName())) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Go through all of the given method's required parameters and consume them |
| * from the given element's attributes. If a parameter is not present in the |
| * element, it will be passed null. Unexpected attributes are an error. |
| * |
| * @param element The element to find the necessary attributes for the |
| * parameters to the method. |
| * @param method The method to gather parameters for. |
| * @return The list of parameters to send to the function. |
| * @throws UnableToCompleteException |
| */ |
| private String[] makeArgsList(XMLElement element, JAbstractMethod method, |
| XMLElement toAdd) throws UnableToCompleteException { |
| JParameter[] params = method.getParameters(); |
| String[] args = new String[params.length]; |
| args[0] = writer.parseElementToField(toAdd).getNextReference(); |
| |
| // First parameter is the child widget |
| for (int index = 1; index < params.length; index++) { |
| JParameter param = params[index]; |
| String defaultValue = null; |
| |
| if (param.getType().isPrimitive() != null) { |
| defaultValue = param.getType().isPrimitive().getUninitializedFieldExpression(); |
| } |
| String value = element.consumeAttributeWithDefault(param.getName(), |
| defaultValue, param.getType()); |
| args[index] = value; |
| } |
| |
| if (element.getAttributeCount() > 0) { |
| writer.die(element, "Unexpected attributes"); |
| } |
| return args; |
| } |
| } |