| /* |
| * 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.TreeLogger.Type; |
| import com.google.gwt.core.ext.typeinfo.JClassType; |
| import com.google.gwt.core.ext.typeinfo.JType; |
| import com.google.gwt.core.ext.typeinfo.TypeOracle; |
| import com.google.gwt.uibinder.rebind.model.ImplicitCssResource; |
| |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.Map; |
| |
| /** |
| * This class handles all {@link FieldWriter} instances created for the current |
| * template. |
| */ |
| public class FieldManager { |
| |
| private static final String DUPLICATE_FIELD_ERROR = "Duplicate declaration of field %1$s."; |
| |
| private final TypeOracle types; |
| private final MortalLogger logger; |
| |
| /** |
| * Map of field name to FieldWriter. Note its a LinkedHashMap--we want to |
| * write these out in the order they're declared. |
| */ |
| private final LinkedHashMap<String, FieldWriter> fieldsMap = new LinkedHashMap<String, FieldWriter>(); |
| |
| /** |
| * A stack of the fields. |
| */ |
| private final LinkedList<FieldWriter> parsedFieldStack = new LinkedList<FieldWriter>(); |
| |
| private LinkedHashMap<String, FieldReference> fieldReferences = new LinkedHashMap<String, FieldReference>(); |
| |
| public FieldManager(TypeOracle types, MortalLogger logger) { |
| this.types = types; |
| this.logger = logger; |
| } |
| |
| /** |
| * @param fieldName the name of the {@link FieldWriter} to find |
| * @return the {@link FieldWriter} instance indexed by fieldName or |
| * <b>null</b> in case fieldName is not found |
| */ |
| public FieldWriter lookup(String fieldName) { |
| return fieldsMap.get(fieldName); |
| } |
| |
| /** |
| * Remove the field at the top of the {@link #parsedFieldStack}. |
| */ |
| public void pop() { |
| parsedFieldStack.removeFirst(); |
| } |
| |
| /** |
| * @param fieldWriter the field to push on the top of the |
| * {@link #parsedFieldStack} |
| */ |
| public void push(FieldWriter fieldWriter) { |
| parsedFieldStack.addFirst(fieldWriter); |
| } |
| |
| /** |
| * Used to declare fields of an existing type. If your field will hold a type |
| * that is being generated, see {@link #registerFieldOfGeneratedType}. |
| * <p> |
| * When making a field we peek at the {@link #parsedFieldStack} to make sure |
| * that the field that holds the widget currently being parsed will depended |
| * upon the field being declared. This ensures, for example, that dom id |
| * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel |
| * will be declared before it is. |
| * |
| * @param fieldType the type of the new field |
| * @param fieldName the name of the new field |
| * @return a new {@link FieldWriter} instance |
| * @throws UnableToCompleteException on duplicate name |
| */ |
| public FieldWriter registerField(JClassType fieldType, String fieldName) |
| throws UnableToCompleteException { |
| FieldWriter field = new FieldWriterOfExistingType(fieldType, fieldName, |
| logger); |
| return registerField(fieldName, field); |
| } |
| |
| /** |
| * Used to declare fields that will hold generated instances generated |
| * CssResource interfaces. If your field will hold a reference of an existing |
| * type, see {@link #registerField}. For other generated types, use |
| * {@link #registerFieldOfGeneratedType} |
| * {@link #registerFieldForGeneratedCssResource}. |
| * <p> |
| * When making a field we peek at the {@link #parsedFieldStack} to make sure |
| * that the field that holds the widget currently being parsed will depended |
| * upon the field being declared. This ensures, for example, that dom id |
| * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel |
| * will be declared before it is. |
| * |
| * @throws UnableToCompleteException on duplicate name |
| * @return a new {@link FieldWriter} instance |
| */ |
| public FieldWriter registerFieldForGeneratedCssResource( |
| ImplicitCssResource cssResource) throws UnableToCompleteException { |
| FieldWriter field = new FieldWriterOfGeneratedCssResource( |
| types.findType(String.class.getCanonicalName()), cssResource, logger); |
| return registerField(cssResource.getName(), field); |
| } |
| |
| /** |
| * Used to declare fields of a type (other than CssResource) that is to be |
| * generated. If your field will hold a reference of an existing type, see |
| * {@link #registerField}. For generated CssResources, see |
| * {@link #registerFieldForGeneratedCssResource}. |
| * <p> |
| * When making a field we peek at the {@link #parsedFieldStack} to make sure |
| * that the field that holds the widget currently being parsed will depended |
| * upon the field being declared. This ensures, for example, that dom id |
| * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel |
| * will be declared before it is. |
| * |
| * @param assignableType class or interface extened or implemented by this |
| * type |
| * @param typeName the full qualified name for the class associated with the |
| * field |
| * @param fieldName the name of the field |
| * @throws UnableToCompleteException on duplicate name |
| * @return a new {@link FieldWriter} instance |
| */ |
| public FieldWriter registerFieldOfGeneratedType(JClassType assignableType, |
| String typePackage, String typeName, String fieldName) |
| throws UnableToCompleteException { |
| FieldWriter field = new FieldWriterOfGeneratedType(assignableType, |
| typePackage, typeName, fieldName, logger); |
| return registerField(fieldName, field); |
| } |
| |
| /** |
| * Called to register a <code>{field.reference}</code> encountered during |
| * parsing, to be validated against the type oracle once parsing is complete. |
| */ |
| public void registerFieldReference(String fieldReferenceString, JType type) { |
| FieldReference fieldReference = fieldReferences.get(fieldReferenceString); |
| |
| if (fieldReference == null) { |
| fieldReference = new FieldReference(fieldReferenceString, this, types); |
| fieldReferences.put(fieldReferenceString, fieldReference); |
| } |
| |
| fieldReference.addLeftHandType(type); |
| } |
| |
| /** |
| * To be called after parsing is complete. Surveys all |
| * <code>{field.reference}</code>s and checks they refer to existing types, |
| * and have appropriate return types. |
| * |
| * @throws UnableToCompleteException if any <code>{field.references}</code> |
| * can't be resolved |
| */ |
| public void validate() throws UnableToCompleteException { |
| boolean failed = false; |
| |
| for (Map.Entry<String, FieldReference> entry : fieldReferences.entrySet()) { |
| FieldReference ref = entry.getValue(); |
| MonitoredLogger monitoredLogger = new MonitoredLogger( |
| logger.getTreeLogger().branch(Type.TRACE, "validating " + ref)); |
| ref.validate(monitoredLogger); |
| failed |= monitoredLogger.hasErrors(); |
| } |
| if (failed) { |
| throw new UnableToCompleteException(); |
| } |
| } |
| |
| /** |
| * Writes all stored gwt fields. |
| * |
| * @param writer the writer to output |
| * @param ownerTypeName the name of the class being processed |
| */ |
| public void writeGwtFieldsDeclaration(IndentedWriter writer, |
| String ownerTypeName) throws UnableToCompleteException { |
| Collection<FieldWriter> fields = fieldsMap.values(); |
| for (FieldWriter field : fields) { |
| field.write(writer); |
| } |
| } |
| |
| private FieldWriter registerField(String fieldName, FieldWriter field) |
| throws UnableToCompleteException { |
| requireUnique(fieldName); |
| fieldsMap.put(fieldName, field); |
| |
| if (parsedFieldStack.size() > 0) { |
| parsedFieldStack.getFirst().needs(field); |
| } |
| |
| return field; |
| } |
| |
| private void requireUnique(String fieldName) throws UnableToCompleteException { |
| if (fieldsMap.containsKey(fieldName)) { |
| logger.die(DUPLICATE_FIELD_ERROR, fieldName); |
| } |
| } |
| } |