/* * 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; } /** * 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) { // TODO: fix this to not assume the typemodel types. com.google.gwt.dev.javac.typemodel.JTypeParameter newParam = new com.google.gwt.dev.javac.typemodel.JTypeParameter("TP$" + freshTypeVariableCounter++, -1); newParam .setBounds(new com.google.gwt.dev.javac.typemodel.JClassType[] {(com.google.gwt.dev.javac.typemodel.JClassType) 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) { // further transform the substituted type recursively replacement = transform(constr); } } }; return substituter.transform(type); } }