| /* |
| * 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.typeinfo.JClassType; |
| 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 java.util.Arrays; |
| import java.util.LinkedHashSet; |
| |
| /** |
| * Represents a <code>{field.reference}</code>. Collects all the types a |
| * particular reference has been asked to return, and can validate that it |
| * actually does so. |
| */ |
| public class FieldReference { |
| private static class LeftHand { |
| /** |
| * The type of values acceptible to this LHS, in order of preference |
| */ |
| private final JType[] types; |
| /** |
| * The element on the LHS, for error reporting |
| */ |
| private final XMLElement source; |
| |
| LeftHand(XMLElement source, JType... types) { |
| this.types = Arrays.copyOf(types, types.length); |
| this.source = source; |
| } |
| } |
| |
| public static String renderTypesList(JType[] types) { |
| StringBuilder b = new StringBuilder(); |
| for (int i = 0; i < types.length; i++) { |
| if (i > 0 && i == types.length - 1) { |
| b.append(" or "); |
| } else if (i > 0) { |
| b.append(", "); |
| } |
| b.append(types[i].getQualifiedSourceName()); |
| } |
| |
| return b.toString(); |
| } |
| |
| private final FieldManager fieldManager; |
| private final XMLElement source; |
| private final String debugString; |
| private final String[] elements; |
| |
| private final LinkedHashSet<LeftHand> leftHandTypes = new LinkedHashSet<LeftHand>(); |
| |
| private final TypeOracle typeOracle; |
| |
| FieldReference(String reference, XMLElement source, FieldManager fieldManager, |
| TypeOracle typeOracle) { |
| this.source = source; |
| this.debugString = "{" + reference + "}"; |
| this.fieldManager = fieldManager; |
| this.typeOracle = typeOracle; |
| elements = reference.split("\\."); |
| } |
| |
| public void addLeftHandType(XMLElement source, JType... types) { |
| leftHandTypes.add(new LeftHand(source, types)); |
| } |
| |
| public String getFieldName() { |
| return elements[0]; |
| } |
| |
| public JType getReturnType() { |
| return getReturnType(null); |
| } |
| |
| /** |
| * Returns the type returned by this field ref. |
| * |
| * @param logger optional logger to report errors on, may be null |
| * @return the field ref, or null |
| */ |
| public JType getReturnType(MonitoredLogger logger) { |
| FieldWriter field = fieldManager.lookup(elements[0]); |
| if (field == null) { |
| if (logger != null) { |
| /* |
| * It's null when called from HtmlTemplateMethodWriter, which fires |
| * after validation has already succeeded. |
| */ |
| logger.error(source, "in %s, no field named %s", this, elements[0]); |
| } |
| return null; |
| } |
| |
| return field.getReturnType(elements, logger); |
| } |
| |
| public XMLElement getSource() { |
| return source; |
| } |
| |
| @Override |
| public String toString() { |
| return debugString; |
| } |
| |
| public void validate(MonitoredLogger logger) { |
| JType myReturnType = getReturnType(logger); |
| if (myReturnType == null) { |
| return; |
| } |
| |
| for (LeftHand left : leftHandTypes) { |
| ensureAssignable(left, myReturnType, logger); |
| } |
| } |
| |
| /** |
| * Returns a failure message if the types don't mesh, or null on success. |
| */ |
| private void ensureAssignable(LeftHand left, JType rightHandType, MonitoredLogger logger) { |
| assert left.types.length > 0; |
| |
| for (JType leftType : left.types) { |
| |
| if (leftType == rightHandType) { |
| return; |
| } |
| |
| if (matchingNumberTypes(leftType, rightHandType)) { |
| return; |
| } |
| |
| boolean[] explicitFailure = {false}; |
| if (handleMismatchedNonNumericPrimitives(leftType, rightHandType, explicitFailure)) { |
| if (explicitFailure[0]) { |
| continue; |
| } |
| } |
| |
| JClassType leftClass = leftType.isClassOrInterface(); |
| if (leftClass != null) { |
| JClassType rightClass = rightHandType.isClassOrInterface(); |
| if ((rightClass == null) || !leftClass.isAssignableFrom(rightClass)) { |
| continue; |
| } |
| } |
| |
| /* |
| * If we have reached the bottom of the loop, we don't see a problem with |
| * assigning to this left hand type. Return without logging any error. |
| * This is pretty conservative -- we have a white list of bad conditions, |
| * not an exhaustive check of valid assignments. We're not confident that |
| * we know every error case, and are more worried about being artificially |
| * restrictive. |
| */ |
| return; |
| } |
| |
| /* |
| * Every possible left hand type had some kind of failure. Log this sad |
| * fact, which will halt processing. |
| */ |
| logger.error(left.source, "%s required, but %s returns %s", renderTypesList(left.types), |
| FieldReference.this, rightHandType.getQualifiedSourceName()); |
| } |
| |
| private boolean handleMismatchedNonNumericPrimitives(JType leftType, JType rightHandType, |
| boolean[] explicitFailure) { |
| JPrimitiveType leftPrimitive = leftType.isPrimitive(); |
| JPrimitiveType rightPrimitive = rightHandType.isPrimitive(); |
| |
| if (leftPrimitive == null && rightPrimitive == null) { |
| return false; |
| } |
| |
| if (leftPrimitive != null) { |
| JClassType autobox = typeOracle.findType(leftPrimitive.getQualifiedBoxedSourceName()); |
| if (rightHandType != autobox) { |
| explicitFailure[0] = true; |
| } |
| } else { // rightPrimitive != null |
| JClassType autobox = typeOracle.findType(rightPrimitive.getQualifiedBoxedSourceName()); |
| if (leftType != autobox) { |
| explicitFailure[0] = true; |
| } |
| } |
| |
| return true; |
| } |
| |
| private boolean isNumber(JType type) { |
| JClassType numberType = typeOracle.findType(Number.class.getCanonicalName()); |
| |
| JClassType asClass = type.isClass(); |
| if (asClass != null) { |
| return numberType.isAssignableFrom(asClass); |
| } |
| |
| JPrimitiveType asPrimitive = type.isPrimitive(); |
| if (asPrimitive != null) { |
| JClassType autoboxed = typeOracle.findType(asPrimitive.getQualifiedBoxedSourceName()); |
| return numberType.isAssignableFrom(autoboxed); |
| } |
| |
| return false; |
| } |
| |
| private boolean matchingNumberTypes(JType leftHandType, JType rightHandType) { |
| /* |
| * int i = (int) 1.0 is okay Integer i = (int) 1.0 is okay int i = (int) |
| * Double.valueOf(1.0) is not |
| */ |
| if (isNumber(leftHandType) && isNumber(rightHandType) // |
| && (rightHandType.isPrimitive() != null)) { |
| return true; // They will be cast into submission |
| } |
| |
| return false; |
| } |
| } |