blob: 2f44123023d1d71a7859ec76e57f0bd1ea3fe3a0 [file] [log] [blame]
/*
* Copyright 2008 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.user.rebind.rpc;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JRawType;
import com.google.gwt.core.ext.typeinfo.JRealClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.JWildcardType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* This class defines the method
* {@link #constrainTypeBy(JClassType, JClassType)}.
*/
public class TypeConstrainer {
/**
* Check whether two base types have any subclasses in common. Note: this
* could surely be implemented much more efficiently.
*/
private static boolean baseTypesOverlap(JClassType type1, JClassType type2) {
assert (type1 == getBaseType(type1));
assert (type2 == getBaseType(type2));
if (type1 == type2) {
return true;
}
HashSet<JClassType> subtypes1 = new HashSet<JClassType>();
subtypes1.add(type1);
for (JClassType sub1 : type1.getSubtypes()) {
subtypes1.add(getBaseType(sub1));
}
List<JClassType> subtypes2 = new ArrayList<JClassType>();
subtypes2.add(type2);
for (JClassType sub2 : type2.getSubtypes()) {
subtypes2.add(getBaseType(sub2));
}
for (JClassType sub2 : subtypes2) {
if (subtypes1.contains(sub2)) {
return true;
}
}
return false;
}
private static JClassType getBaseType(JClassType type) {
return SerializableTypeOracleBuilder.getBaseType(type);
}
private static boolean isRealOrParameterized(JClassType type) {
if (type.isParameterized() != null) {
return true;
}
if (type instanceof JRealClassType) {
return true;
}
return false;
}
private static JClassType[] makeArray(JClassType... classTypes) {
return classTypes;
}
/**
* Check whether <code>param</code> occurs anywhere within <code>type</code>.
*/
private static boolean occurs(final JTypeParameter param, JClassType type) {
class OccursVisitor extends JTypeVisitor {
boolean foundIt = false;
@Override
public void endVisit(JTypeParameter seenParam) {
if (seenParam == param) {
foundIt = true;
}
}
}
OccursVisitor visitor = new OccursVisitor();
visitor.accept(type);
return visitor.foundIt;
}
private int freshTypeVariableCounter;
private final TypeOracle typeOracle;
public TypeConstrainer(TypeOracle typeOracle) {
this.typeOracle = typeOracle;
}
/**
* Return a subtype of <code>subType</code> that includes all values in both
* <code>subType</code> and <code>superType</code>. The returned type
* must have the same base type as <code>subType</code>. If there are
* definitely no such values, return <code>null</code>.
*/
public JClassType constrainTypeBy(JClassType subType, JClassType superType) {
JParameterizedType superAsParameterized = superType.isParameterized();
if (superAsParameterized == null) {
// If the supertype is not parameterized, it will not be possible to
// constrain
// the subtype further.
return subType;
}
// Replace each wildcard in the subType with a fresh type variable.
// These type variables will be the ones that are constrained.
Map<JTypeParameter, JClassType> constraints = new HashMap<JTypeParameter, JClassType>();
JClassType subWithWildcardsReplaced = replaceWildcardsWithFreshTypeVariables(
subType, constraints);
// Rewrite subType so that it has the same base type as superType.
JParameterizedType subAsParameterized = subWithWildcardsReplaced.asParameterizationOf(superAsParameterized.getBaseType());
if (subAsParameterized == null) {
// The subtype's base does not inherit from the supertype's base,
// so again no constraint will be possible.
return subType;
}
// Check the rewritten type against superType
if (!typesMatch(subAsParameterized, superAsParameterized, constraints)) {
// The types are completely incompatible
return null;
}
// Apply the revised constraints to the original type
return substitute(subWithWildcardsReplaced, constraints);
}
/**
* Check whether two types can have any values in common. The
* <code>constraints</code> field holds known constraints for type
* parameters that appear in <code>type1</code>; this method may take
* advantage of those constraints in its decision, and it may tighten them so
* long as the tightening does not reject any values from the overlap of the
* two types.
*
* As an invariant, no key in <code>constraints</code> may occur inside any
* value in <code>constraints</code>.
*
* Note that this algorithm looks for overlap matches in the arguments of
* parameterized types rather than looking for exact matches. Looking for
* overlaps simplifies the algorithm but returns true more often than it has
* to.
*/
boolean typesMatch(JClassType type1, JClassType type2,
Map<JTypeParameter, JClassType> constraints) {
JGenericType type1Generic = type1.isGenericType();
if (type1Generic != null) {
return typesMatch(type1Generic.asParameterizedByWildcards(), type2,
constraints);
}
JGenericType type2Generic = type2.isGenericType();
if (type2Generic != null) {
return typesMatch(type1, type2Generic.asParameterizedByWildcards(),
constraints);
}
JWildcardType type1Wild = type1.isWildcard();
if (type1Wild != null) {
return typesMatch(type1Wild.getUpperBound(), type2, constraints);
}
JWildcardType type2Wild = type2.isWildcard();
if (type2Wild != null) {
return typesMatch(type1, type2Wild.getUpperBound(), constraints);
}
JRawType type1Raw = type1.isRawType();
if (type1Raw != null) {
return typesMatch(type1Raw.asParameterizedByWildcards(), type2,
constraints);
}
JRawType type2Raw = type2.isRawType();
if (type2Raw != null) {
return typesMatch(type1, type2Raw.asParameterizedByWildcards(),
constraints);
}
// The following assertions are known to be true, given the tests above.
// assert (type1Generic == null);
// assert (type2Generic == null);
// assert (type1Wild == null);
// assert (type2Wild == null);
// assert (type1Raw == null);
// assert (type2Raw == null);
if (type1 == type2) {
return true;
}
if (constraints.containsKey(type1)) {
JTypeParameter type1Parameter = (JTypeParameter) type1;
JClassType type2Class = type2;
JClassType type1Bound = constraints.get(type1Parameter);
assert (!occurs(type1Parameter, type1Bound));
if (!typesMatch(type1Bound, type2, constraints)) {
return false;
}
if (type1Bound.isAssignableFrom(type2Class)) {
constraints.put(type1Parameter, type2Class);
}
}
if (type1 == typeOracle.getJavaLangObject()) {
return true;
}
if (type2 == typeOracle.getJavaLangObject()) {
return true;
}
JTypeParameter type1Param = type1.isTypeParameter();
if (type1Param != null) {
// It would be nice to check that type1Param's bound is a match
// for type2, but that can introduce infinite recursions.
return true;
}
JTypeParameter type2Param = type2.isTypeParameter();
if (type2Param != null) {
// It would be nice to check that type1Param's bound is a match
// for type2, but that can introduce infinite recursions.
return true;
}
JArrayType type1Array = type1.isArray();
JArrayType type2Array = type2.isArray();
if (type1Array != null && type2Array != null) {
if (typesMatch(type1Array.getComponentType(),
type2Array.getComponentType(), constraints)) {
return true;
}
}
if (isRealOrParameterized(type1) && isRealOrParameterized(type2)) {
JClassType baseType1 = getBaseType(type1);
JClassType baseType2 = getBaseType(type2);
JParameterizedType type1Parameterized = type1.isParameterized();
JParameterizedType type2Parameterized = type2.isParameterized();
if (baseType1 == baseType2 && type1Parameterized != null
&& type2Parameterized != null) {
// type1 and type2 are parameterized types with the same base type;
// compare their arguments
JClassType[] args1 = type1Parameterized.getTypeArgs();
JClassType[] args2 = type2Parameterized.getTypeArgs();
boolean allMatch = true;
for (int i = 0; i < args1.length; i++) {
if (!typesMatch(args1[i], args2[i], constraints)) {
allMatch = false;
}
}
if (allMatch) {
return true;
}
} else {
// The types have different base types, so just compare the base types
// for overlap.
if (baseTypesOverlap(baseType1, baseType2)) {
return true;
}
}
}
return false;
}
/**
* The same as {@link #typesMatch(JClassType, JClassType, Map)}, but
* additionally support primitive types as well as class types.
*/
boolean typesMatch(JType type1, JType type2,
Map<JTypeParameter, JClassType> constraints) {
if (type1 == type2) {
// This covers the case where both are primitives
return true;
}
JClassType type1Class = type1.isClassOrInterface();
JClassType type2Class = type2.isClassOrInterface();
if (type1Class != null && type2Class != null
&& typesMatch(type1Class, type2Class, constraints)) {
return true;
}
return false;
}
/**
* Replace all wildcards in <code>type</code> with a fresh type variable.
* For each type variable created, add an entry in <code>constraints</code>
* mapping the type variable to its upper bound.
*/
private JClassType replaceWildcardsWithFreshTypeVariables(JClassType type,
final Map<JTypeParameter, JClassType> constraints) {
JModTypeVisitor replacer = new JModTypeVisitor() {
@Override
public void endVisit(JWildcardType wildcardType) {
JTypeParameter newParam = new JTypeParameter("TP$"
+ freshTypeVariableCounter++, -1);
newParam.setBounds(makeArray(typeOracle.getJavaLangObject()));
constraints.put(newParam, wildcardType.getUpperBound());
replacement = newParam;
}
};
return replacer.transform(type);
}
/**
* Substitute all occurrences in <code>type</code> of type parameters in
* <code>constraints</code> for a wildcard bounded by the parameter's entry
* in <code>constraints</code>. If the argument is <code>null</code>,
* return <code>null</code>.
*/
private JClassType substitute(JClassType type,
final Map<JTypeParameter, JClassType> constraints) {
JModTypeVisitor substituter = new JModTypeVisitor() {
@Override
public void endVisit(JTypeParameter param) {
JClassType constr = constraints.get(param);
if (constr != null) {
replacement = constr;
}
}
};
return substituter.transform(type);
}
}