blob: 30925253d113891a6e75ffdaed640b4bbcafab73 [file] [log] [blame]
/*
* 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;
}
}