blob: f6b98ebf2f01fd0d1b207f4f05d1070a0d3f5ce1 [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.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
import com.google.gwt.dev.util.collect.IdentityHashMap;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Maps;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a parameterized type in a declaration.
*/
public class JParameterizedType extends JMaybeParameterizedType implements
com.google.gwt.core.ext.typeinfo.JParameterizedType {
/**
* Create a parameterized type along with any necessary enclosing
* parameterized types. Enclosing parameterized types are necessary when the
* base type is a non-static member and the enclosing type is also generic.
*/
private static JParameterizedType createParameterizedTypeRecursive(
JGenericType baseType, Map<JTypeParameter, JClassType> substitutionMap) {
JClassType enclosingType = baseType.getEnclosingType();
if (baseType.isMemberType() && !baseType.isStatic()) {
// This base type is a non-static generic type so we build the necessary
// enclosing parameterized type and update the enclosing type to be
// a parameterized type.
JGenericType isGenericEnclosingType = enclosingType.isGenericType();
if (isGenericEnclosingType != null) {
enclosingType = createParameterizedTypeRecursive(
isGenericEnclosingType, substitutionMap);
}
}
JTypeParameter[] typeParameters = baseType.getTypeParameters();
JClassType[] newTypeArgs = new JClassType[typeParameters.length];
TypeOracle oracle = baseType.getOracle();
for (int i = 0; i < newTypeArgs.length; ++i) {
JClassType newTypeArg = substitutionMap.get(typeParameters[i]);
if (newTypeArg == null) {
newTypeArg = oracle.getWildcardType(BoundType.EXTENDS,
typeParameters[i].getFirstBound());
}
newTypeArgs[i] = newTypeArg;
}
JParameterizedType parameterizedType = oracle.getParameterizedType(
baseType, enclosingType, newTypeArgs);
return parameterizedType;
}
private final JClassType enclosingType;
private List<JClassType> interfaces;
/**
* This map records the JClassType that should be used in place of a given
* {@link JTypeParameter}.
*/
private Map<JTypeParameter, JClassType> lazySubstitutionMap;
private JClassType lazySuperclass;
private final AbstractMembers members;
private final List<JClassType> typeArgs;
JParameterizedType(JGenericType baseType, JClassType enclosingType,
JClassType[] typeArgs) {
super.setBaseType(baseType);
this.enclosingType = enclosingType;
// NOTE: this instance is not considered a nested type of the enclosing type
final JParameterizedType parameterizedType = this;
members = new DelegateMembers(this, baseType, new Substitution() {
public JClassType getSubstitution(JClassType type) {
return type.getSubstitutedType(parameterizedType);
}
});
this.typeArgs = Lists.create(typeArgs);
assert (this.typeArgs.indexOf(null) == -1) : "Unresolved typeArg creating JParameterizedType from "
+ baseType;
// NOTE: Can't perform substitutions until we are done building
}
@Override
public JConstructor findConstructor(JType[] paramTypes) {
return members.findConstructor(paramTypes);
}
@Override
public JField findField(String name) {
return members.findField(name);
}
@Override
public JMethod findMethod(String name, JType[] paramTypes) {
return members.findMethod(name, paramTypes);
}
@Override
public JClassType findNestedType(String typeName) {
return members.findNestedType(typeName);
}
@Override
public JConstructor getConstructor(JType[] paramTypes)
throws NotFoundException {
return members.getConstructor(paramTypes);
}
@Override
public JConstructor[] getConstructors() {
return members.getConstructors();
}
@Override
public JClassType getEnclosingType() {
return enclosingType;
}
@Override
public JField getField(String name) {
return members.getField(name);
}
@Override
public JField[] getFields() {
return members.getFields();
}
@Override
public JClassType[] getImplementedInterfaces() {
if (interfaces == null) {
interfaces = new ArrayList<JClassType>();
JClassType[] intfs = getBaseType().getImplementedInterfaces();
for (JClassType intf : intfs) {
JClassType newIntf = intf.getSubstitutedType(this);
interfaces.add(newIntf);
}
interfaces = Lists.normalize(interfaces);
}
return interfaces.toArray(TypeOracle.NO_JCLASSES);
}
@Override
public JMethod[] getInheritableMethods() {
return members.getInheritableMethods();
}
@Override
public JMethod getMethod(String name, JType[] paramTypes)
throws NotFoundException {
return members.getMethod(name, paramTypes);
}
@Override
public JMethod[] getMethods() {
return members.getMethods();
}
@Override
public JClassType getNestedType(String typeName) throws NotFoundException {
return members.getNestedType(typeName);
}
@Override
public JClassType[] getNestedTypes() {
return members.getNestedTypes();
}
/**
* @deprecated See {@link #getQualifiedSourceName()}
*/
@Deprecated
public String getNonParameterizedQualifiedSourceName() {
return getQualifiedSourceName();
}
@Override
public JMethod[] getOverloads(String name) {
return members.getOverloads(name);
}
@Override
public JMethod[] getOverridableMethods() {
return members.getOverridableMethods();
}
@Override
public String getParameterizedQualifiedSourceName() {
StringBuffer sb = new StringBuffer();
if (getEnclosingType() != null) {
sb.append(getEnclosingType().getParameterizedQualifiedSourceName());
sb.append(".");
sb.append(getSimpleSourceName());
} else {
sb.append(getQualifiedSourceName());
}
if (typeArgs.size() > 0) {
sb.append('<');
boolean needComma = false;
for (JType typeArg : typeArgs) {
if (needComma) {
sb.append(", ");
} else {
needComma = true;
}
sb.append(typeArg.getParameterizedQualifiedSourceName());
}
sb.append('>');
} else {
/*
* Non-static, inner classes of generic types are modeled as generic, even
* if they do not declare type parameters or reference the type parameters
* of their enclosing generic type.
*/
}
return sb.toString();
}
@Override
public String getQualifiedBinaryName() {
return getBaseType().getQualifiedBinaryName();
}
/**
* Everything is fully qualified and includes the &lt; and &gt; in the
* signature.
*/
@Override
public String getQualifiedSourceName() {
return getBaseType().getQualifiedSourceName();
}
public JClassType getRawType() {
return getBaseType().getRawType();
}
/**
* In this case, the raw type name.
*/
@Override
public String getSimpleSourceName() {
return getBaseType().getSimpleSourceName();
}
/*
* Goal: Return a list of possible subtypes of this parameterized type. In the
* event that we have generic subtypes and we cannot resolve the all of the
* type arguments, we need to wildcard types in place of the arguments that we
* cannot resolve.
*
* Algorithm: - Ask generic type for its subtypes - Filter subtypes of the
* generic which cannot be our subtype.
*/
@Override
public JClassType[] getSubtypes() {
List<JClassType> subtypeList = new ArrayList<JClassType>();
// Parameterized types are not tracked in the subtype hierarchy; ask base
// type
JClassType[] genericSubtypes = getBaseType().getSubtypes();
for (JClassType subtype : genericSubtypes) {
// Could be a subtype depending on how it is substituted
Map<JTypeParameter, JClassType> substitutions = findSubtypeSubstitution(subtype);
if (substitutions != null) {
JGenericType genericType = subtype.isGenericType();
if (genericType != null) {
subtype = createParameterizedTypeRecursive(genericType, substitutions);
} else {
// If this is not a generic type then there should not be any
// substitution.
assert (substitutions.isEmpty());
}
subtypeList.add(subtype);
}
}
return subtypeList.toArray(TypeOracle.NO_JCLASSES);
}
@Override
public JClassType getSuperclass() {
if (isInterface() != null) {
return null;
}
if (lazySuperclass == null) {
JGenericType baseType = getBaseType();
JClassType superclass = baseType.getSuperclass();
assert (superclass != null);
lazySuperclass = superclass.getSubstitutedType(this);
}
return lazySuperclass;
}
public JClassType[] getTypeArgs() {
return typeArgs.toArray(TypeOracle.NO_JCLASSES);
}
@Override
public JGenericType isGenericType() {
return null;
}
@Override
public JParameterizedType isParameterized() {
return this;
}
@Override
public JRawType isRawType() {
return null;
}
@Override
public JWildcardType isWildcard() {
return null;
}
@Override
public String toString() {
if (isInterface() != null) {
return "interface " + getParameterizedQualifiedSourceName();
}
return "class " + getParameterizedQualifiedSourceName();
}
@Override
protected JClassType findNestedTypeImpl(String[] typeName, int index) {
return members.findNestedTypeImpl(typeName, index);
}
@Override
protected void getInheritableMethodsOnSuperclassesAndThisClass(
Map<String, JMethod> methodsBySignature) {
members.getInheritableMethodsOnSuperclassesAndThisClass(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
*/
@Override
protected void getInheritableMethodsOnSuperinterfacesAndMaybeThisInterface(
Map<String, JMethod> methodsBySignature) {
members.getInheritableMethodsOnSuperinterfacesAndMaybeThisInterface(methodsBySignature);
}
@Override
JClassType getSubstitutedType(JParameterizedType parameterizedType) {
maybeInitializeTypeParameterSubstitutionMap();
if (this == parameterizedType) {
return this;
}
JClassType[] newTypeArgs = new JClassType[typeArgs.size()];
for (int i = 0; i < newTypeArgs.length; ++i) {
newTypeArgs[i] = typeArgs.get(i).getSubstitutedType(parameterizedType);
}
return getOracle().getParameterizedType(getBaseType(), getEnclosingType(),
newTypeArgs);
}
/**
* Returns the {@link JClassType} that is a substitute for the given
* {@link JTypeParameter}. If there is no substitution, the original
* {@link JTypeParameter} is returned.
*/
JClassType getTypeParameterSubstitution(JTypeParameter typeParameter) {
maybeInitializeTypeParameterSubstitutionMap();
JClassType substitute = lazySubstitutionMap.get(typeParameter);
if (substitute != null) {
return substitute;
}
return typeParameter;
}
boolean hasTypeArgs(JClassType[] otherArgTypes) {
if (otherArgTypes.length != typeArgs.size()) {
return false;
}
for (int i = 0; i < otherArgTypes.length; ++i) {
// Identity tests are ok since identity is durable within an oracle.
//
if (otherArgTypes[i] != typeArgs.get(i)) {
return false;
}
}
return true;
}
/**
* Initialize a map of substitutions for {@link JTypeParameter}s to
* corresponding {@link JClassType}s. This can only be initialized after the
* {@link com.google.gwt.dev.javac.TypeOracleMediator TypeOracleMediator} has
* fully resolved all of the {@link JClassType}s.
*/
void maybeInitializeTypeParameterSubstitutionMap() {
if (lazySubstitutionMap != null) {
return;
}
lazySubstitutionMap = new IdentityHashMap<JTypeParameter, JClassType>();
JParameterizedType currentParameterizedType = this;
while (currentParameterizedType != null) {
JGenericType genericType = currentParameterizedType.getBaseType();
JTypeParameter[] typeParameters = genericType.getTypeParameters();
JClassType[] typeArguments = currentParameterizedType.getTypeArgs();
for (JTypeParameter typeParameter : typeParameters) {
lazySubstitutionMap.put(typeParameter,
typeArguments[typeParameter.getOrdinal()]);
}
if (currentParameterizedType.isStatic()) {
break;
}
JClassType maybeParameterizedType = currentParameterizedType.getEnclosingType();
if (maybeParameterizedType == null
|| maybeParameterizedType.isParameterized() == null) {
break;
}
currentParameterizedType = maybeParameterizedType.isParameterized();
}
lazySubstitutionMap = Maps.normalize(lazySubstitutionMap);
}
void setTypeArguments(JClassType[] typeArgs) {
this.typeArgs.addAll(Arrays.asList(typeArgs));
}
/**
* Returns a map of substitutions that will make the subtype a proper subtype
* of this parameterized type. The map maybe empty in the case that it is
* already an exact subtype.
*/
private Map<JTypeParameter, JClassType> findSubtypeSubstitution(
JClassType subtype) {
Map<JTypeParameter, JClassType> substitutions = new IdentityHashMap<JTypeParameter, JClassType>();
// Get the supertype hierarchy. If this JParameterizedType exists
// exactly in this set we are done.
Set<JClassType> supertypeHierarchy = getFlattenedSuperTypeHierarchy(subtype);
if (supertypeHierarchy.contains(this)) {
return substitutions;
}
/*
* Try to find a parameterized supertype whose base type is the same as our
* own. Because that parameterized supertype might be made into ourself via
* substitution.
*/
for (JClassType candidate : supertypeHierarchy) {
JParameterizedType parameterizedCandidate = candidate.isParameterized();
if (parameterizedCandidate == null) {
// If not parameterized then there is no substitution possible.
continue;
}
if (parameterizedCandidate.getBaseType() != getBaseType()) {
// This candidate be parameterized to us.
continue;
}
/*
* We have a parameterization of our base type. Now we need to see if it
* is possible to parameterize subtype such that candidate becomes
* equivalent to us.
*/
JClassType[] candidateTypeArgs = parameterizedCandidate.getTypeArgs();
JClassType[] myTypeArgs = getTypeArgs();
for (int i = 0; i < myTypeArgs.length; ++i) {
JClassType otherTypeArg = candidateTypeArgs[i];
JClassType myTypeArg = myTypeArgs[i];
if (myTypeArg == otherTypeArg) {
// There are identical so there is no substitution that is needed.
continue;
}
JTypeParameter otherTypeParameter = otherTypeArg.isTypeParameter();
if (otherTypeParameter == null) {
// Not a type parameter and not equal so no substitution can make it
// equal.
return null;
}
if (!otherTypeParameter.isAssignableFrom(myTypeArg)) {
// Make sure that my type argument can be substituted for this type
// parameter.
return null;
}
substitutions.put(otherTypeParameter, myTypeArg);
}
}
// Legal substitution can be made and is record in substitutions.
return substitutions;
}
}