blob: a05a8ada77840f9fbb70550f3286bb7db3d4b874 [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.dev.javac.typemodel;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.dev.util.collect.HashSet;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Type used to represent any non-primitive type.
*/
public abstract class JClassType implements
com.google.gwt.core.ext.typeinfo.JClassType {
/**
* Returns all of the superclasses and superinterfaces for a given type
* including the type itself. The returned set maintains an internal
* breadth-first ordering of the type, followed by its interfaces (and their
* super-interfaces), then the supertype and its interfaces, and so on.
*/
protected static Set<JClassType> getFlattenedSuperTypeHierarchy(
JClassType type) {
Set<JClassType> flattened = type.flattenedSupertypes;
if (flattened == null) {
flattened = new LinkedHashSet<JClassType>();
getFlattenedSuperTypeHierarchyRecursive(type, flattened);
// flattened.size() > 1 for all types other than Object
type.flattenedSupertypes = Collections.unmodifiableSet(flattened);
}
return flattened;
}
/**
* Returns <code>true</code> if the rhs array type can be assigned to the lhs
* array type.
*/
private static boolean areArraysAssignable(JArrayType lhsType,
JArrayType rhsType) {
// areClassTypesAssignable should prevent us from getting here if the types
// are referentially equal.
assert (lhsType != rhsType);
JType lhsComponentType = lhsType.getComponentType();
JType rhsComponentType = rhsType.getComponentType();
if (lhsComponentType.isPrimitive() != null
|| rhsComponentType.isPrimitive() != null) {
/*
* Arrays are referentially stable so there will only be one int[] no
* matter how many times it is referenced in the code. So, if either
* component type is a primitive then we know that we are not assignable.
*/
return false;
}
assert (lhsComponentType instanceof JClassType);
assert (rhsComponentType instanceof JClassType);
JClassType thisComponentClass = (JClassType) lhsComponentType;
JClassType subtypeComponentClass = (JClassType) rhsComponentType;
return areClassTypesAssignable(thisComponentClass, subtypeComponentClass);
}
/**
* Returns <code>true</code> if the rhsType can be assigned to the lhsType.
*/
private static boolean areClassTypesAssignable(
com.google.gwt.core.ext.typeinfo.JClassType lhsType,
com.google.gwt.core.ext.typeinfo.JClassType rhsType) {
// The supertypes of rhs will include rhs.
Set<JClassType> rhsSupertypes = getFlattenedSuperTypeHierarchy((JClassType) rhsType);
for (JClassType rhsSupertype : rhsSupertypes) {
if (areClassTypesAssignableNoSupers((JClassType) lhsType, rhsSupertype)) {
return true;
}
}
return false;
}
/**
* Returns <code>true</code> if the lhs and rhs are assignable without
* consideration of the supertypes of the rhs.
*
* @param lhsType
* @param rhsType
* @return true if rhsType can be assigned to lhsType
*/
private static boolean areClassTypesAssignableNoSupers(JClassType lhsType,
JClassType rhsType) {
if (lhsType == rhsType) {
// Done, these are the same types.
return true;
}
if (lhsType == lhsType.getOracle().getJavaLangObject()) {
// Done, any type can be assigned to object.
return true;
}
/*
* Get the generic base type, if there is one, for the lhs type and convert
* it to a raw type if it is generic.
*/
if (lhsType.isGenericType() != null) {
lhsType = lhsType.isGenericType().getRawType();
}
if (rhsType.isGenericType() != null) {
// Treat the generic rhs type as a raw type.
rhsType = rhsType.isGenericType().getRawType();
}
// Check for JTypeParameters.
JTypeParameter lhsTypeParam = lhsType.isTypeParameter();
JTypeParameter rhsTypeParam = rhsType.isTypeParameter();
if (lhsTypeParam != null) {
JClassType[] lhsTypeBounds = lhsTypeParam.getBounds();
for (JClassType lhsTypeBound : lhsTypeBounds) {
if (!areClassTypesAssignable(lhsTypeBound, rhsType)) {
// Done, the rhsType was not assignable to one of the bounds.
return false;
}
}
// Done, the rhsType was assignable to all of the bounds.
return true;
} else if (rhsTypeParam != null) {
JClassType[] possibleSubtypeBounds = rhsTypeParam.getBounds();
for (JClassType possibleSubtypeBound : possibleSubtypeBounds) {
if (areClassTypesAssignable(lhsType, possibleSubtypeBound)) {
// Done, at least one bound is assignable to this type.
return true;
}
}
return false;
}
/*
* Check for JWildcards. We have not examined this part in great detail
* since there should not be top level wildcard types.
*/
JWildcardType lhsWildcard = lhsType.isWildcard();
JWildcardType rhsWildcard = rhsType.isWildcard();
if (lhsWildcard != null && rhsWildcard != null) {
// Both types are wildcards.
return areWildcardsAssignable(lhsWildcard, rhsWildcard);
} else if (lhsWildcard != null) {
// The lhs type is a wildcard but the rhs is not.
// ? extends T, U OR ? super T, U
JClassType[] lowerBounds = lhsWildcard.getLowerBounds();
if (lowerBounds.length > 0) {
// ? super T will reach object no matter what the rhs type is
return true;
} else {
return areClassTypesAssignable(lhsWildcard.getFirstBound(), rhsType);
}
}
// Check for JArrayTypes.
JArrayType lhsArray = lhsType.isArray();
JArrayType rhsArray = rhsType.isArray();
if (lhsArray != null) {
if (rhsArray == null) {
return false;
} else {
return areArraysAssignable(lhsArray, rhsArray);
}
} else if (rhsArray != null) {
// Safe although perhaps not necessary
return false;
}
// Check for JParameterizedTypes and JRawTypes.
JMaybeParameterizedType lhsMaybeParameterized = lhsType.isMaybeParameterizedType();
JMaybeParameterizedType rhsMaybeParameterized = rhsType.isMaybeParameterizedType();
if (lhsMaybeParameterized != null && rhsMaybeParameterized != null) {
if (lhsMaybeParameterized.getBaseType() == rhsMaybeParameterized.getBaseType()) {
if (lhsMaybeParameterized.isRawType() != null
|| rhsMaybeParameterized.isRawType() != null) {
/*
* Any raw type can be assigned to or from any parameterization of its
* generic type.
*/
return true;
}
assert (lhsMaybeParameterized.isRawType() == null && rhsMaybeParameterized.isRawType() == null);
JParameterizedType lhsParameterized = lhsMaybeParameterized.isParameterized();
JParameterizedType rhsParameterized = rhsMaybeParameterized.isParameterized();
assert (lhsParameterized != null && rhsParameterized != null);
return areTypeArgumentsAssignable(lhsParameterized, rhsParameterized);
}
}
// Default to not being assignable.
return false;
}
/**
* Returns <code>true</code> if the type arguments of the rhs parameterized
* type are assignable to the type arguments of the lhs parameterized type.
*/
private static boolean areTypeArgumentsAssignable(JParameterizedType lhsType,
JParameterizedType rhsType) {
// areClassTypesAssignable should prevent us from getting here if the types
// are referentially equal.
assert (lhsType != rhsType);
assert (lhsType.getBaseType() == rhsType.getBaseType());
JClassType[] lhsTypeArgs = lhsType.getTypeArgs();
JClassType[] rhsTypeArgs = rhsType.getTypeArgs();
JGenericType lhsBaseType = lhsType.getBaseType();
// Compare at least as many formal type parameters as are declared on the
// generic base type. gwt.typeArgs could cause more types to be included.
JTypeParameter[] lhsTypeParams = lhsBaseType.getTypeParameters();
for (int i = 0; i < lhsTypeParams.length; ++i) {
if (!doesTypeArgumentContain(lhsTypeArgs[i], rhsTypeArgs[i])) {
return false;
}
}
return true;
}
/**
* Returns <code>true</code> if the rhsWildcard can be assigned to the
* lhsWildcard. This method does not consider supertypes of either lhs or rhs.
*/
private static boolean areWildcardsAssignable(JWildcardType lhsWildcard,
JWildcardType rhsWildcard) {
// areClassTypesAssignable should prevent us from getting here if the types
// are referentially equal.
assert (lhsWildcard != rhsWildcard);
assert (lhsWildcard != null && rhsWildcard != null);
if (lhsWildcard.getLowerBounds().length > 0
&& rhsWildcard.getLowerBounds().length > 0) {
// lhsType: ? super T, rhsType ? super U
return areClassTypesAssignable(rhsWildcard.getFirstBound(),
lhsWildcard.getFirstBound());
} else if (lhsWildcard.getUpperBounds().length > 0
&& lhsWildcard.getLowerBounds().length == 0
&& rhsWildcard.getUpperBounds().length > 0
&& rhsWildcard.getLowerBounds().length == 0) {
// lhsType: ? extends T, rhsType: ? extends U
return areClassTypesAssignable(lhsWildcard.getFirstBound(),
rhsWildcard.getFirstBound());
}
return false;
}
/**
* A restricted version of areClassTypesAssignable that is used for comparing
* the type arguments of parameterized types, where the lhsTypeArg is the
* container.
*/
private static boolean doesTypeArgumentContain(JClassType lhsTypeArg,
JClassType rhsTypeArg) {
if (lhsTypeArg == rhsTypeArg) {
return true;
}
// Check for wildcard types
JWildcardType lhsWildcard = lhsTypeArg.isWildcard();
JWildcardType rhsWildcard = rhsTypeArg.isWildcard();
if (lhsWildcard != null) {
if (rhsWildcard != null) {
return areWildcardsAssignable(lhsWildcard, rhsWildcard);
} else {
// LHS is a wildcard but the RHS is not.
if (lhsWildcard.getLowerBounds().length > 0) {
return areClassTypesAssignable(rhsTypeArg,
lhsWildcard.getFirstBound());
} else {
return areClassTypesAssignable(lhsWildcard.getFirstBound(),
rhsTypeArg);
}
}
}
/*
* At this point the arguments are not the same and they are not wildcards
* so, they cannot be assignable, Eh.
*/
return false;
}
private static void getFlattenedSuperTypeHierarchyRecursive(JClassType type,
Set<JClassType> typesSeen) {
if (typesSeen.contains(type)) {
return;
}
typesSeen.add(type);
// Check the interfaces
JClassType[] intfs = type.getImplementedInterfaces();
for (JClassType intf : intfs) {
typesSeen.addAll(getFlattenedSuperTypeHierarchy(intf));
}
// Superclass
JClassType superclass = type.getSuperclass();
if (superclass != null) {
typesSeen.addAll(getFlattenedSuperTypeHierarchy(superclass));
}
}
/**
* Cached set of supertypes for this type (including itself). If null, the set
* has not been calculated yet.
*/
private Set<JClassType> flattenedSupertypes;
/**
* True if this type may be enhanced with server-only fields. This property is
* 'sticky' and may be set but not unset, since we need to generate the
* relevant RPC code for handling the server fields if there is any chance the
* class will be enhanced.
*/
private boolean isEnhanced = false;
public JParameterizedType asParameterizationOf(
com.google.gwt.core.ext.typeinfo.JGenericType type) {
Set<JClassType> supertypes = getFlattenedSuperTypeHierarchy(this);
for (JClassType supertype : supertypes) {
JParameterizedType isParameterized = supertype.isParameterized();
if (isParameterized != null && isParameterized.getBaseType() == type) {
return isParameterized;
}
JRawType isRaw = supertype.isRawType();
if (isRaw != null && isRaw.getBaseType() == type) {
return isRaw.asParameterizedByWildcards();
}
}
return null;
}
/**
* All types use identity for comparison.
*/
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
/**
* Find an annotation on a type or on one of its superclasses or
* superinterfaces.
* <p>
* This provides semantics similar to that of
* {@link java.lang.annotation.Inherited} except that it checks all types to
* which this type is assignable. {@code @Inherited} only works on
* superclasses, not superinterfaces.
* <p>
* Annotations present on the superclass chain will be returned preferentially
* over those found in the superinterface hierarchy. Note that the annotation
* does not need to be tagged with {@code @Inherited} in order to be returned
* from the superclass chain.
*
* @param annotationType the type of the annotation to look for
* @return the desired annotation or <code>null</code> if the annotation is
* not present in the type's type hierarchy
*/
public <T extends Annotation> T findAnnotationInTypeHierarchy(
Class<T> annotationType) {
// Remember what we've seen to avoid loops
Set<JClassType> seen = new HashSet<JClassType>();
// Work queue
List<JClassType> searchTypes = new LinkedList<JClassType>();
searchTypes.add(this);
T toReturn = null;
while (!searchTypes.isEmpty()) {
JClassType current = searchTypes.remove(0);
if (!seen.add(current)) {
continue;
}
toReturn = current.getAnnotation(annotationType);
if (toReturn != null) {
/*
* First one wins. It might be desirable at some point to have a
* variation that can return more than one instance of the annotation if
* it is present on multiple supertypes.
*/
break;
}
if (current.getSuperclass() != null) {
// Add the superclass at the front of the list
searchTypes.add(0, current.getSuperclass());
}
// Superinterfaces
Collections.addAll(searchTypes, current.getImplementedInterfaces());
}
return toReturn;
}
public abstract JConstructor findConstructor(JType[] paramTypes);
public abstract JField findField(String name);
public abstract JMethod findMethod(String name, JType[] paramTypes);
public abstract JClassType findNestedType(String typeName);
public abstract <T extends Annotation> T getAnnotation(
Class<T> annotationClass);
public abstract Annotation[] getAnnotations();
public abstract JConstructor getConstructor(JType[] paramTypes)
throws NotFoundException;
public abstract JConstructor[] getConstructors();
public abstract Annotation[] getDeclaredAnnotations();
public abstract JClassType getEnclosingType();
public abstract JClassType getErasedType();
public abstract JField getField(String name);
public abstract JField[] getFields();
/**
* Returns all of the superclasses and superinterfaces for a given type
* including the type itself. The returned set maintains an internal
* breadth-first ordering of the type, followed by its interfaces (and their
* super-interfaces), then the supertype and its interfaces, and so on.
*/
public Set<JClassType> getFlattenedSupertypeHierarchy() {
// Retuns an immutable set
return getFlattenedSuperTypeHierarchy(this);
}
public abstract JClassType[] getImplementedInterfaces();
/**
* Iterates over the most-derived declaration of each unique inheritable
* method available in the type hierarchy of the specified type, including
* those found in superclasses and superinterfaces. A method is inheritable if
* its accessibility is <code>public</code>, <code>protected</code>, or
* package protected.
*
* This method offers a convenient way for Generators to find candidate
* methods to call from a subclass.
*
* @return an array of {@link JMethod} objects representing inheritable
* methods
*/
public abstract JMethod[] getInheritableMethods();
public abstract String getJNISignature();
public JType getLeafType() {
return this;
}
@Deprecated
public final String[][] getMetaData(String tagName) {
return TypeOracle.NO_STRING_ARR_ARR;
}
@Deprecated
public final String[] getMetaDataTags() {
return TypeOracle.NO_STRINGS;
}
public abstract JMethod getMethod(String name, JType[] paramTypes)
throws NotFoundException;
/*
* Returns the declared methods of this class (not any superclasses or
* superinterfaces).
*/
public abstract JMethod[] getMethods();
public abstract String getName();
public abstract JClassType getNestedType(String typeName)
throws NotFoundException;
public abstract JClassType[] getNestedTypes();
public abstract TypeOracle getOracle();
public abstract JMethod[] getOverloads(String name);
/**
* Iterates over the most-derived declaration of each unique overridable
* method available in the type hierarchy of the specified type, including
* those found in superclasses and superinterfaces. A method is overridable if
* it is not <code>final</code> and its accessibility is <code>public</code>,
* <code>protected</code>, or package protected.
*
* Deferred binding generators often need to generate method implementations;
* this method offers a convenient way to find candidate methods to implement.
*
* Note that the behavior does not match
* {@link Class#getMethod(String, Class[])}, which does not return the most
* derived method in some cases.
*
* @return an array of {@link JMethod} objects representing overridable
* methods
*/
public abstract JMethod[] getOverridableMethods();
public abstract JPackage getPackage();
public String getParameterizedQualifiedSourceName() {
return getQualifiedSourceName();
}
/**
* TODO(scottb): remove if we can resolve param names differently.
*/
public abstract String getQualifiedBinaryName();
public abstract String getQualifiedSourceName();
public abstract String getSimpleSourceName();
public abstract JClassType[] getSubtypes();
public abstract JClassType getSuperclass();
/**
* All types use identity for comparison.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
public abstract boolean isAbstract();
/**
* Returns this instance if it is a annotation or <code>null</code> if it is
* not.
*
* @return this instance if it is a annotation or <code>null</code> if it is
* not
*/
public JAnnotationType isAnnotation() {
return null;
}
public abstract boolean isAnnotationPresent(
Class<? extends Annotation> annotationClass);
public abstract JArrayType isArray();
/**
* Returns <code>true</code> if this {@link JClassType} is assignable from the
* specified {@link JClassType} parameter.
*
* @param possibleSubtype possible subtype of this {@link JClassType}
* @return <code>true</code> if this {@link JClassType} is assignable from the
* specified {@link JClassType} parameter
*
* @throws NullPointerException if <code>possibleSubtype</code> is
* <code>null</code>
*/
public boolean isAssignableFrom(
com.google.gwt.core.ext.typeinfo.JClassType possibleSubtype) {
if (possibleSubtype == null) {
throw new NullPointerException("possibleSubtype");
}
return areClassTypesAssignable(this, possibleSubtype);
}
/**
* Returns <code>true</code> if this {@link JClassType} is assignable to the
* specified {@link JClassType} parameter.
*
* @param possibleSupertype possible supertype of this {@link JClassType}
* @return <code>true</code> if this {@link JClassType} is assignable to the
* specified {@link JClassType} parameter
*
* @throws NullPointerException if <code>possibleSupertype</code> is
* <code>null</code>
*/
public boolean isAssignableTo(
com.google.gwt.core.ext.typeinfo.JClassType possibleSupertype) {
if (possibleSupertype == null) {
throw new NullPointerException("possibleSupertype");
}
return areClassTypesAssignable(possibleSupertype, this);
}
public abstract JClassType isClass();
public JClassType isClassOrInterface() {
JClassType type = isClass();
if (type != null) {
return type;
}
return isInterface();
}
/**
* Determines if the class can be constructed using a simple <code>new</code>
* operation. Specifically, the class must
* <ul>
* <li>be a class rather than an interface,</li>
* <li>have either no constructors or a parameterless constructor, and</li>
* <li>be a top-level class or a static nested class.</li>
* </ul>
*
* @return <code>true</code> if the type is default instantiable, or
* <code>false</code> otherwise
*/
public abstract boolean isDefaultInstantiable();
/**
* Returns true if the type may be enhanced on the server to contain extra
* fields that are unknown to client code.
*
* @return <code>true</code> if the type might be enhanced on the server
*/
public final boolean isEnhanced() {
return isEnhanced;
}
/**
* Returns this instance if it is an enumeration or <code>null</code> if it is
* not.
*
* @return this instance if it is an enumeration or <code>null</code> if it is
* not
*/
public abstract JEnumType isEnum();
public abstract boolean isFinal();
public abstract JGenericType isGenericType();
public abstract JClassType isInterface();
/**
* @deprecated local types are not modeled
*/
@Deprecated
public final boolean isLocalType() {
return false;
}
/**
* Tests if this type is contained within another type.
*
* @return true if this type has an enclosing type, false if this type is a
* top-level type
*/
public abstract boolean isMemberType();
public abstract JParameterizedType isParameterized();
public abstract JPrimitiveType isPrimitive();
public abstract boolean isPrivate();
public abstract boolean isProtected();
public abstract boolean isPublic();
// TODO: Rename this to isRaw
public abstract JRawType isRawType();
public abstract boolean isStatic();
public JTypeParameter isTypeParameter() {
return null;
}
public abstract JWildcardType isWildcard();
/**
* Indicates that the type may be enhanced on the server to contain extra
* fields that are unknown to client code.
*
* TODO(rice): find a better way to do this.
*/
public void setEnhanced() {
this.isEnhanced = true;
}
@Override
public String toString() {
return this.getQualifiedSourceName();
}
protected abstract void acceptSubtype(JClassType me);
protected abstract void getInheritableMethodsOnSuperclassesAndThisClass(
Map<String, JMethod> methodsBySignature);
/**
* Gets the methods declared in interfaces that this type extends. If this
* type is a class, its own methods are not added. If this type is an
* interface, its own methods are added. Used internally by
* {@link #getOverridableMethods()}.
*
* @param methodsBySignature
*/
protected abstract void getInheritableMethodsOnSuperinterfacesAndMaybeThisInterface(
Map<String, JMethod> methodsBySignature);
protected abstract int getModifierBits();
protected JMaybeParameterizedType isMaybeParameterizedType() {
return null;
}
/**
* Tells this type's superclasses and superinterfaces about it.
*/
protected abstract void notifySuperTypesOf(JClassType me);
protected abstract void removeSubtype(JClassType me);
abstract void addConstructor(JConstructor ctor);
abstract void addField(JField field);
abstract void addImplementedInterface(JClassType intf);
abstract void addMethod(JMethod method);
abstract void addModifierBits(int bits);
abstract void addNestedType(JClassType type);
abstract JClassType findNestedTypeImpl(String[] typeName, int index);
/**
* Returns either the substitution of this type based on the parameterized
* type or this instance.
*
* @param parameterizedType
* @return either the substitution of this type based on the parameterized
* type or this instance
*/
abstract JClassType getSubstitutedType(JParameterizedType parameterizedType);
abstract void notifySuperTypes();
/**
* Removes references to this instance from all of its super types.
*/
abstract void removeFromSupertypes();
abstract void setSuperclass(JClassType type);
}