blob: 334fcd0d705aedee12c6cab48a6fe199ddc429e5 [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.BadTypeArgsException;
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.core.ext.typeinfo.ParseException;
import com.google.gwt.core.ext.typeinfo.TypeOracleException;
import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
import com.google.gwt.dev.javac.JavaSourceParser;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.dev.util.collect.IdentityHashMap;
import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceIdentityMap;
import org.apache.commons.collections.map.ReferenceMap;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides type-related information about a set of types.
* <p>
* All type objects exposed, such as
* {@link com.google.gwt.core.ext.typeinfo.JClassType} and others, have a stable
* identity relative to this type oracle instance. Consequently, you can
* reliably compare object identity of any objects this type oracle produces.
* For example, the following code relies on this stable identity guarantee:
*
* <pre>
* JClassType o = typeOracle.getJavaLangObject();
* JClassType s1 = typeOracle.getType(&quot;java.lang.String&quot;);
* JClassType s2 = typeOracle.getType(&quot;java.lang.String&quot;);
* assert (s1 == s2);
* assert (o == s1.getSuperclass());
* JParameterizedType ls = typeOracle.parse(&quot;java.util.List&lt;java.lang.String&gt;&quot;);
* assert (ls.getTypeArgs()[0] == s1);
* </pre>
*
* </p>
*
*/
public class TypeOracle extends com.google.gwt.core.ext.typeinfo.TypeOracle {
private static class ParameterizedTypeKey {
private final JClassType enclosingType;
private final JGenericType genericType;
private final com.google.gwt.core.ext.typeinfo.JClassType[] typeArgs;
public ParameterizedTypeKey(JGenericType genericType, JClassType enclosingType,
com.google.gwt.core.ext.typeinfo.JClassType[] typeArgs) {
this.genericType = genericType;
this.enclosingType = enclosingType;
this.typeArgs = typeArgs;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ParameterizedTypeKey)) {
return false;
}
ParameterizedTypeKey other = (ParameterizedTypeKey) obj;
return genericType == other.genericType && enclosingType == other.enclosingType
&& Arrays.equals(typeArgs, other.typeArgs);
}
@Override
public int hashCode() {
return 29 * genericType.hashCode() + 17
* ((enclosingType == null) ? 0 : enclosingType.hashCode()) + Arrays.hashCode(typeArgs);
}
}
private static class WildCardKey {
private final BoundType boundType;
private final JClassType typeBound;
public WildCardKey(BoundType boundType, JClassType typeBound) {
this.boundType = boundType;
this.typeBound = typeBound;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof WildCardKey)) {
return false;
}
WildCardKey other = (WildCardKey) obj;
return boundType == other.boundType && typeBound == other.typeBound;
}
@Override
public int hashCode() {
return 29 * typeBound.hashCode() + boundType.hashCode();
}
}
static final int MOD_ABSTRACT = 0x00000001;
static final int MOD_FINAL = 0x00000002;
static final int MOD_NATIVE = 0x00000004;
static final int MOD_PRIVATE = 0x00000008;
static final int MOD_PROTECTED = 0x00000010;
static final int MOD_PUBLIC = 0x00000020;
static final int MOD_STATIC = 0x00000040;
static final int MOD_TRANSIENT = 0x00000080;
static final int MOD_VOLATILE = 0x00000100;
static final Annotation[] NO_ANNOTATIONS = new Annotation[0];
static final JClassType[] NO_JCLASSES = new JClassType[0];
static final JConstructor[] NO_JCTORS = new JConstructor[0];
static final JField[] NO_JFIELDS = new JField[0];
static final JMethod[] NO_JMETHODS = new JMethod[0];
static final JPackage[] NO_JPACKAGES = new JPackage[0];
static final JParameter[] NO_JPARAMS = new JParameter[0];
static final JType[] NO_JTYPES = new JType[0];
static final String[][] NO_STRING_ARR_ARR = new String[0][];
static final String[] NO_STRINGS = new String[0];
private static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject";
/**
* Convenience method to sort class types in a consistent way. Note that the
* order is subject to change and is intended to generate an "aesthetically
* pleasing" order rather than a computationally reliable order.
*/
public static void sort(JClassType[] types) {
Arrays.sort(types, new Comparator<JClassType>() {
public int compare(JClassType type1, JClassType type2) {
String name1 = type1.getQualifiedSourceName();
String name2 = type2.getQualifiedSourceName();
return name1.compareTo(name2);
}
});
}
/**
* Convenience method to sort constructors in a consistent way. Note that the
* order is subject to change and is intended to generate an "aesthetically
* pleasing" order rather than a computationally reliable order.
*/
public static void sort(JConstructor[] ctors) {
Arrays.sort(ctors, new Comparator<JConstructor>() {
public int compare(JConstructor o1, JConstructor o2) {
// Nothing for now; could enhance to sort based on parameter list
return 0;
}
});
}
/**
* Convenience method to sort fields in a consistent way. Note that the order
* is subject to change and is intended to generate an "aesthetically
* pleasing" order rather than a computationally reliable order.
*/
public static void sort(JField[] fields) {
Arrays.sort(fields, new Comparator<JField>() {
public int compare(JField f1, JField f2) {
String name1 = f1.getName();
String name2 = f2.getName();
return name1.compareTo(name2);
}
});
}
/**
* Convenience method to sort methods in a consistent way. Note that the order
* is subject to change and is intended to generate an "aesthetically
* pleasing" order rather than a computationally reliable order.
*/
public static void sort(JMethod[] methods) {
Arrays.sort(methods, new Comparator<JMethod>() {
public int compare(JMethod m1, JMethod m2) {
String name1 = m1.getName();
String name2 = m2.getName();
return name1.compareTo(name2);
}
});
}
static String[] modifierBitsToNamesForField(int bits) {
List<String> strings = modifierBitsToNamesForMethodsAndFields(bits);
if (0 != (bits & MOD_VOLATILE)) {
strings.add("volatile");
}
if (0 != (bits & MOD_TRANSIENT)) {
strings.add("transient");
}
return strings.toArray(NO_STRINGS);
}
static String[] modifierBitsToNamesForMethod(int bits) {
List<String> strings = modifierBitsToNamesForMethodsAndFields(bits);
if (0 != (bits & MOD_ABSTRACT)) {
strings.add("abstract");
}
if (0 != (bits & MOD_NATIVE)) {
strings.add("native");
}
return strings.toArray(NO_STRINGS);
}
private static JClassType[] cast(com.google.gwt.core.ext.typeinfo.JClassType[] extTypeArgs) {
JClassType[] result = new JClassType[extTypeArgs.length];
System.arraycopy(extTypeArgs, 0, result, 0, extTypeArgs.length);
return result;
}
/**
* Converts modifier bits, which are common to fields and methods, to
* readable names.
*
* @see TypeOracle#modifierBitsToNamesForField(int) modifierBitsToNamesForField
* @see TypeOracle#modifierBitsToNamesForMethod(int) modifierBitsToNamesForMethod
*/
private static List<String> modifierBitsToNamesForMethodsAndFields(int bits) {
List<String> strings = new ArrayList<String>();
// The order is based on the order in which we want them to appear.
//
if (0 != (bits & MOD_PUBLIC)) {
strings.add("public");
}
if (0 != (bits & MOD_PRIVATE)) {
strings.add("private");
}
if (0 != (bits & MOD_PROTECTED)) {
strings.add("protected");
}
if (0 != (bits & MOD_STATIC)) {
strings.add("static");
}
if (0 != (bits & MOD_FINAL)) {
strings.add("final");
}
return strings;
}
/**
* A map of fully-qualify source names (ie, use "." rather than "$" for nested
* classes) to JRealClassTypes.
*/
private final Map<String, JRealClassType> allTypes = new HashMap<String, JRealClassType>();
/**
* Cached types that represent Arrays of other types. These types are created
* as needed.
*/
@SuppressWarnings("unchecked")
private final Map<JType, JArrayType> arrayTypes = new ReferenceIdentityMap(
AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true);
/**
* Cached singleton type representing <code>java.lang.Object</code>.
*/
private JClassType javaLangObject;
private final JavaSourceParser javaSourceParser = new JavaSourceParser();
/**
* Maps SingleJsoImpl interfaces to the implementing JSO subtype.
*/
private final Map<JClassType, JClassType> jsoSingleImpls =
new IdentityHashMap<JClassType, JClassType>();
/**
* Cached map of all packages thus far encountered.
*/
private final Map<String, JPackage> packages = new HashMap<String, JPackage>();
/**
* Subclasses of generic types that have type parameters filled in. These
* types are created as needed.
*/
@SuppressWarnings("unchecked")
private final Map<ParameterizedTypeKey, JParameterizedType> parameterizedTypes =
new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK, true);
/**
* A list of recently-added types that will be fully initialized on the next
* call to {@link #finish}.
*/
private final List<JRealClassType> recentTypes = new ArrayList<JRealClassType>();
private JWildcardType unboundWildCardType;
@SuppressWarnings("unchecked")
private final Map<WildCardKey, JWildcardType> wildcardTypes = new ReferenceMap(
AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK, true);
public TypeOracle() {
// Always create the default package.
//
getOrCreatePackage("");
}
/**
* Attempts to find a package by name. All requests for the same package
* return the same package object.
*
* @return <code>null</code> if the package could not be found
*/
@Override
public JPackage findPackage(String pkgName) {
return packages.get(pkgName);
}
/**
* Finds a class or interface given its fully-qualified name.
*
* @param name fully-qualified class/interface name - for nested classes, use
* its source name rather than its binary name (that is, use a "."
* rather than a "$")
*
* @return <code>null</code> if the type is not found
*/
@Override
public JClassType findType(String name) {
assert Name.isSourceName(name);
return allTypes.get(name);
}
/**
* Finds a type given its package-relative name. For nested classes, use its
* source name rather than its binary name (that is, use a "." rather than a
* "$").
*
* @return <code>null</code> if the type is not found
*/
@Override
public JClassType findType(String pkgName, String typeName) {
assert Name.isSourceName(typeName);
JPackage pkg = findPackage(pkgName);
if (pkg != null) {
JClassType type = pkg.findType(typeName);
if (type != null) {
return type;
}
}
return null;
}
/**
* Gets the type object that represents an array of the specified type. The
* returned type always has a stable identity so as to guarantee that all
* calls to this method with the same argument return the same object.
*
* @param componentType the component type of the array, which can itself be
* an array type
* @return a type object representing an array of the component type
*/
@Override
public JArrayType getArrayType(JType componentType) {
JArrayType arrayType = arrayTypes.get(componentType);
if (arrayType == null) {
arrayType = new JArrayType(componentType, this);
arrayTypes.put(componentType, arrayType);
}
return arrayType;
}
/**
* Gets a reference to the type object representing
* <code>java.lang.Object</code>.
*/
@Override
public JClassType getJavaLangObject() {
if (javaLangObject == null) {
javaLangObject = findType("java.lang.Object");
assert javaLangObject != null;
}
return javaLangObject;
}
/**
* Ensure that a package with the specified name exists as well as its parent
* packages.
*/
@Override
public JPackage getOrCreatePackage(String name) {
int i = name.lastIndexOf('.');
if (i != -1) {
// Ensure the parent package is also created.
//
getOrCreatePackage(name.substring(0, i));
}
JPackage pkg = packages.get(name);
if (pkg == null) {
pkg = new JPackage(name);
packages.put(name, pkg);
}
return pkg;
}
/**
* Gets a package by name. All requests for the same package return the same
* package object.
*
* @return the package object associated with the specified name
*/
@Override
public JPackage getPackage(String pkgName) throws NotFoundException {
JPackage result = findPackage(pkgName);
if (result == null) {
throw new NotFoundException(pkgName);
}
return result;
}
/**
* Gets an array of all packages known to this type oracle.
*
* @return an array of packages, possibly of zero-length
*/
@Override
public JPackage[] getPackages() {
return packages.values().toArray(NO_JPACKAGES);
}
/**
* Gets the parameterized type object that represents the combination of a
* specified raw type and a set of type arguments. The returned type always
* has a stable identity so as to guarantee that all calls to this method with
* the same arguments return the same object.
*
* @param extGenericType a generic base class
* @param extEnclosingType
* @param extTypeArgs the type arguments bound to the specified generic type
* @return a type object representing this particular binding of type
* arguments to the specified generic
* @throws IllegalArgumentException if the parameterization of a non-static
* member type does not specify an enclosing type or if not enough
* arguments were specified to parameterize the generic type
* @throws NullPointerException if genericType is <code>null</code>
*/
@Override
public JParameterizedType getParameterizedType(
com.google.gwt.core.ext.typeinfo.JGenericType extGenericType,
com.google.gwt.core.ext.typeinfo.JClassType extEnclosingType,
com.google.gwt.core.ext.typeinfo.JClassType[] extTypeArgs) {
JGenericType genericType = (JGenericType) extGenericType;
JClassType enclosingType = (JClassType) extEnclosingType;
JClassType[] typeArgs = cast(extTypeArgs);
ParameterizedTypeKey key = new ParameterizedTypeKey(genericType, enclosingType, typeArgs);
JParameterizedType result = parameterizedTypes.get(key);
if (result != null) {
return result;
}
if (genericType.isMemberType() && !genericType.isStatic()) {
if (genericType.getEnclosingType().isGenericType() != null
&& enclosingType.isParameterized() == null && enclosingType.isRawType() == null) {
/*
* If the generic type is a non-static member type enclosed by a generic
* type then the enclosing type for this parameterized type should be
* raw or parameterized.
*/
throw new IllegalArgumentException("Generic type '"
+ genericType.getParameterizedQualifiedSourceName()
+ "' is a non-static member type, but the enclosing type '"
+ enclosingType.getQualifiedSourceName() + "' is not a parameterized or raw type");
}
}
JTypeParameter[] typeParameters = genericType.getTypeParameters();
if (typeArgs.length < typeParameters.length) {
throw new IllegalArgumentException(
"Not enough type arguments were specified to parameterize '"
+ genericType.getParameterizedQualifiedSourceName() + "'");
} else {
/*
* TODO: Should WARN if we specify too many type arguments but we have no
* logger.
*/
}
// TODO: validate that the type arguments satisfy the generic type parameter
// bounds if any were specified
result = new JParameterizedType(genericType, enclosingType, typeArgs);
parameterizedTypes.put(key, result);
return result;
}
/**
* Gets the parameterized type object that represents the combination of a
* specified raw type and a set of type arguments. The returned type always
* has a stable identity so as to guarantee that all calls to this method with
* the same arguments return the same object.
*
* @param genericType a generic base class
* @param typeArgs the type arguments bound to the specified generic type
* @return a type object representing this particular binding of type
* arguments to the specified generic
* @throws IllegalArgumentException if the generic type is a non-static member
* type or if not enough arguments were specified to parameterize
* the generic type
* @throws NullPointerException if genericType is <code>null</code>
*/
@Override
public JParameterizedType getParameterizedType(
com.google.gwt.core.ext.typeinfo.JGenericType genericType,
com.google.gwt.core.ext.typeinfo.JClassType[] typeArgs) {
return getParameterizedType(genericType, null, typeArgs);
}
/**
* @deprecated This method will always return 0 because a TypeOracle never
* gets reloaded anymore. Callers should not rely on this value to
* manage static state.
*/
@Deprecated
@Override
public long getReloadCount() {
return 0;
}
/**
* Returns the single implementation type for an interface returned via
* {@link #getSingleJsoImplInterfaces()} or <code>null</code> if no JSO
* implementation is defined.
*/
@Override
public JClassType getSingleJsoImpl(com.google.gwt.core.ext.typeinfo.JClassType intf) {
assert intf.isInterface() == intf;
return jsoSingleImpls.get(intf);
}
/**
* Returns an unmodifiable, live view of all interface types that are
* implemented by exactly one JSO subtype.
*/
@Override
public Set<? extends com.google.gwt.core.ext.typeinfo.JClassType> getSingleJsoImplInterfaces() {
return Collections.unmodifiableSet(jsoSingleImpls.keySet());
}
/**
* Finds a type given its fully qualified name. For nested classes, use its
* source name rather than its binary name (that is, use a "." rather than a
* "$").
*
* @return the specified type
*/
@Override
public JClassType getType(String name) throws NotFoundException {
assert Name.isSourceName(name);
JClassType type = findType(name);
if (type == null) {
throw new NotFoundException(name);
}
return type;
}
/**
* Finds a type given its package-relative name. For nested classes, use its
* source name rather than its binary name (that is, use a "." rather than a
* "$").
*
* @return the specified type
*/
@Override
public JClassType getType(String pkgName, String topLevelTypeSimpleName) throws NotFoundException {
assert Name.isSourceName(topLevelTypeSimpleName);
JClassType type = findType(pkgName, topLevelTypeSimpleName);
if (type == null) {
throw new NotFoundException(pkgName + "." + topLevelTypeSimpleName);
}
return type;
}
/**
* Gets all types, both top-level and nested.
*
* @return an array of types, possibly of zero length
*/
@Override
public JClassType[] getTypes() {
Collection<JRealClassType> values = allTypes.values();
JClassType[] result = values.toArray(new JClassType[values.size()]);
Arrays.sort(result, new Comparator<JClassType>() {
public int compare(JClassType o1, JClassType o2) {
return o1.getQualifiedSourceName().compareTo(o2.getQualifiedSourceName());
}
});
return result;
}
@Override
public JWildcardType getWildcardType(
com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType boundType,
com.google.gwt.core.ext.typeinfo.JClassType extTypeBound) {
// Special fast case for <? extends Object>
// TODO(amitmanjhi): make sure this actually does speed things up!
JClassType typeBound = (JClassType) extTypeBound;
if (typeBound == getJavaLangObject() && boundType == BoundType.UNBOUND) {
if (unboundWildCardType == null) {
unboundWildCardType = new JWildcardType(boundType, typeBound);
}
return unboundWildCardType;
}
// End special case / todo.
WildCardKey key = new WildCardKey(boundType, typeBound);
JWildcardType result = wildcardTypes.get(key);
if (result != null) {
return result;
}
result = new JWildcardType(boundType, typeBound);
wildcardTypes.put(key, result);
return result;
}
/**
* Parses the string form of a type to produce the corresponding type object.
* The types that can be parsed include primitives, class and interface names,
* simple parameterized types (those without wildcards or bounds), and arrays
* of the preceding.
* <p>
* Examples of types that can be parsed by this method.
* <ul>
* <li><code>int</code></li>
* <li><code>java.lang.Object</code></li>
* <li><code>java.lang.String[]</code></li>
* <li><code>char[][]</code></li>
* <li><code>void</code></li>
* <li><code>List&lt;Shape&gt;</code></li>
* <li><code>List&lt;List&lt;Shape&gt;&gt;</code></li>
* </ul>
* </p>
*
* @param type a type signature to be parsed
* @return the type object corresponding to the parse type
*/
@Override
public JType parse(String type) throws TypeOracleException {
// Remove all internal and external whitespace.
//
type = type.replaceAll("\\\\s", "");
// Recursively parse.
//
return parseImpl(type);
}
void addNewType(JRealClassType newType) {
String fqcn = newType.getQualifiedSourceName();
allTypes.put(fqcn, newType);
recentTypes.add(newType);
}
/**
* Called to add a source reference for a top-level class type.
*/
void addSourceReference(JRealClassType type, Resource sourceFile) {
javaSourceParser.addSourceForType(type, sourceFile);
}
/**
* Called after a block of new types are added.
*/
void finish() {
JClassType[] newTypes = recentTypes.toArray(new JClassType[recentTypes.size()]);
computeHierarchyRelationships(newTypes);
computeSingleJsoImplData(newTypes);
recentTypes.clear();
}
JavaSourceParser getJavaSourceParser() {
return javaSourceParser;
}
private List<JClassType> classChain(JClassType cls) {
LinkedList<JClassType> chain = new LinkedList<JClassType>();
while (cls != null) {
chain.addFirst(cls);
cls = cls.getSuperclass();
}
return chain;
}
/**
* Determines whether the given class fully implements an interface (either
* directly or via inherited methods).
*/
private boolean classFullyImplements(JClassType cls, JClassType intf) {
// If the interface has at least 1 method, then the class must at
// least nominally implement the interface.
if ((intf.getMethods().length > 0) && !intf.isAssignableFrom(cls)) {
return false;
}
// Check to see whether it implements all the interfaces methods.
for (JMethod meth : intf.getInheritableMethods()) {
if (!classImplementsMethod(cls, meth)) {
return false;
}
}
return true;
}
private boolean classImplementsMethod(JClassType cls, JMethod meth) {
while (cls != null) {
JMethod found = cls.findMethod(meth.getName(), meth.getParameterTypes());
if ((found != null) && !found.isAbstract()) {
return true;
}
cls = cls.getSuperclass();
}
return false;
}
private void computeHierarchyRelationships(JClassType[] types) {
// For each type, walk up its hierarchy chain and tell each supertype
// about its subtype.
for (JClassType type : types) {
type.notifySuperTypes();
}
}
/**
* Updates the list of jsoSingleImpl types from recently-added types.
*/
private void computeSingleJsoImplData(JClassType... newTypes) {
JClassType jsoType = findType(JSO_CLASS);
if (jsoType == null) {
return;
}
for (JClassType type : newTypes) {
if (!jsoType.isAssignableFrom(type)) {
continue;
}
for (JClassType intf : JClassType.getFlattenedSuperTypeHierarchy(type)) {
// If intf refers to a JParameterizedType, we need to use its generic
// base type instead.
if (intf instanceof JParameterizedType) {
intf = ((JParameterizedType) intf).getBaseType();
}
if (intf.isInterface() == null) {
// Not an interface
continue;
}
if (intf.getOverridableMethods().length == 0) {
/*
* Record a tag interface as being implemented by JSO, since they
* don't actually have any methods and we want to avoid spurious
* messages about multiple JSO types implementing a common interface.
*/
jsoSingleImpls.put(intf, jsoType);
continue;
}
/*
* If the previously-registered implementation type for a SingleJsoImpl
* interface is a subtype of the type we're currently looking at, we
* want to choose the least-derived class.
*/
JClassType previousType = jsoSingleImpls.get(intf);
if (previousType == null) {
jsoSingleImpls.put(intf, type);
} else if (type.isAssignableFrom(previousType)) {
jsoSingleImpls.put(intf, type);
} else if (type.isAssignableTo(previousType)) {
// Do nothing
} else {
// Special case: If two JSOs implement the same interface, but they
// share a common base class that fully implements that interface,
// then choose that base class.
JClassType impl = findFullyImplementingBase(intf, type, previousType);
if (impl != null) {
jsoSingleImpls.put(intf, impl);
} else {
throw new InternalCompilerException("Already seen an implementing JSO subtype ("
+ previousType.getName() + ") for interface (" + intf.getName()
+ ") while examining newly-added type (" + type.getName() + "). This is a bug in "
+ "JSORestrictionsChecker.");
}
}
}
}
}
/**
* Determines whether both classes A and B share a common superclass which
* fully implements the given interface.
*/
private JClassType findFullyImplementingBase(JClassType intf, JClassType a, JClassType b) {
JClassType common = findNearestCommonBase(a, b);
if (classFullyImplements(common, intf)) {
return common;
}
return null;
}
/**
* Finds the nearest common base class of the given classes.
*/
private JClassType findNearestCommonBase(JClassType a, JClassType b) {
List<JClassType> as = classChain(a);
List<JClassType> bs = classChain(b);
JClassType match = null;
Iterator<JClassType> ait = as.iterator();
Iterator<JClassType> bit = bs.iterator();
while (ait.hasNext() && bit.hasNext()) {
a = ait.next();
b = bit.next();
if (a.equals(b)) {
match = a;
} else {
break;
}
}
return match;
}
private JType parseImpl(String type) throws NotFoundException, ParseException,
BadTypeArgsException {
if (type.endsWith("[]")) {
String remainder = type.substring(0, type.length() - 2);
JType componentType = parseImpl(remainder);
return getArrayType(componentType);
}
if (type.endsWith(">")) {
int bracket = type.indexOf('<');
if (bracket == -1) {
throw new ParseException("Mismatched brackets; expected '<' to match subsequent '>'");
}
// Resolve the raw type.
//
String rawTypeName = type.substring(0, bracket);
JType rawType = parseImpl(rawTypeName);
if (rawType.isParameterized() != null) {
// The raw type cannot itself be parameterized.
//
throw new BadTypeArgsException(
"Only non-parameterized classes and interface can be parameterized");
} else if (rawType.isClassOrInterface() == null) {
// The raw type must be a class or interface
// (not an array or primitive).
//
throw new BadTypeArgsException("Only classes and interface can be parameterized, so "
+ rawType.getQualifiedSourceName() + " cannot be used in this context");
} else if (rawType.isGenericType() == null) {
throw new BadTypeArgsException("'" + rawType.getQualifiedSourceName()
+ "' is not a generic type; only generic types can be parameterized");
}
// Resolve each type argument.
//
String typeArgContents = type.substring(bracket + 1, type.length() - 1);
JClassType[] typeArgs = parseTypeArgContents(typeArgContents);
// Intern this type.
//
return getParameterizedType(rawType.isGenericType(), typeArgs);
}
JType result = JPrimitiveType.parse(type);
if (result != null) {
return result;
}
result = findType(type);
if (result != null) {
return result;
}
throw new NotFoundException("Unable to recognize '" + type
+ "' as a type name (is it fully qualified?)");
}
private void parseTypeArgComponent(List<JClassType> typeArgList, String typeArgComponent)
throws NotFoundException, ParseException, BadTypeArgsException {
JType typeArg = parseImpl(typeArgComponent);
if (typeArg.isPrimitive() != null) {
// Cannot be primitive.
//
throw new BadTypeArgsException("Type arguments cannot be primitives, so '"
+ typeArg.getQualifiedSourceName() + "' cannot be used in this context");
}
typeArgList.add((JClassType) typeArg);
}
/**
* Returns an array of types specified inside of a gwt.typeArgs javadoc
* annotation.
*/
private JClassType[] parseTypeArgContents(String typeArgContents) throws ParseException,
NotFoundException, BadTypeArgsException {
List<JClassType> typeArgList = new ArrayList<JClassType>();
int start = 0;
for (int offset = 0, length = typeArgContents.length(); offset < length; ++offset) {
char ch = typeArgContents.charAt(offset);
switch (ch) {
case '<':
// scan for closing '>' while ignoring commas
for (int depth = 1; depth > 0;) {
if (++offset == length) {
throw new ParseException("Mismatched brackets; expected '<' to match subsequent '>'");
}
char ich = typeArgContents.charAt(offset);
if (ich == '<') {
++depth;
} else if (ich == '>') {
--depth;
}
}
break;
case '>':
throw new ParseException("No matching '<' for '>'");
case ',':
String typeArgComponent = typeArgContents.substring(start, offset);
parseTypeArgComponent(typeArgList, typeArgComponent);
start = offset + 1;
break;
default:
break;
}
}
String typeArgComponent = typeArgContents.substring(start);
parseTypeArgComponent(typeArgList, typeArgComponent);
JClassType[] typeArgs = typeArgList.toArray(new JClassType[typeArgList.size()]);
return typeArgs;
}
}