| /* |
| * Copyright 2009 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.asm; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.typeinfo.JPrimitiveType; |
| import com.google.gwt.core.ext.typeinfo.JType; |
| import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType; |
| import com.google.gwt.core.ext.typeinfo.NotFoundException; |
| import com.google.gwt.dev.javac.Resolver; |
| import com.google.gwt.dev.javac.TypeParameterLookup; |
| import com.google.gwt.dev.javac.typemodel.JClassType; |
| import com.google.gwt.dev.javac.typemodel.JGenericType; |
| import com.google.gwt.dev.javac.typemodel.JParameterizedType; |
| import com.google.gwt.dev.javac.typemodel.JRealClassType; |
| import com.google.gwt.dev.javac.typemodel.JTypeParameter; |
| import com.google.gwt.dev.javac.typemodel.JWildcardType; |
| import com.google.gwt.dev.util.Name; |
| |
| import org.objectweb.asm.signature.SignatureVisitor; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Resolve a single parameterized type. |
| */ |
| public class ResolveTypeSignature extends EmptySignatureVisitor { |
| |
| private final Resolver resolver; |
| private final TreeLogger logger; |
| private final JType[] returnTypeRef; |
| private final TypeParameterLookup lookup; |
| private final char wildcardMatch; |
| private final JClassType enclosingClass; |
| |
| private JClassType outerClass; |
| private final List<JType[]> args = new ArrayList<JType[]>(); |
| private int arrayDepth = 0; |
| |
| /** |
| * Resolve a parameterized type. |
| * |
| * @param resolver |
| * @param logger |
| * @param returnTypeRef "pointer" to return location, ie. 1-element array |
| * @param lookup |
| * @param enclosingClass |
| */ |
| public ResolveTypeSignature(Resolver resolver, TreeLogger logger, |
| JType[] returnTypeRef, TypeParameterLookup lookup, JClassType enclosingClass) { |
| this(resolver, logger, returnTypeRef, lookup, enclosingClass, '='); |
| } |
| |
| public ResolveTypeSignature(Resolver resovler, TreeLogger logger, |
| JType[] returnTypeRef, TypeParameterLookup lookup, JClassType enclosingClass, |
| char wildcardMatch) { |
| this.resolver = resovler; |
| this.logger = logger; |
| this.returnTypeRef = returnTypeRef; |
| this.lookup = lookup; |
| this.enclosingClass = enclosingClass; |
| this.wildcardMatch = wildcardMatch; |
| } |
| |
| @Override |
| public SignatureVisitor visitArrayType() { |
| ++arrayDepth; |
| return this; |
| } |
| |
| @Override |
| public void visitBaseType(char descriptor) { |
| switch (descriptor) { |
| case 'V': |
| returnTypeRef[0] = JPrimitiveType.VOID; |
| break; |
| case 'B': |
| returnTypeRef[0] = JPrimitiveType.BYTE; |
| break; |
| case 'J': |
| returnTypeRef[0] = JPrimitiveType.LONG; |
| break; |
| case 'Z': |
| returnTypeRef[0] = JPrimitiveType.BOOLEAN; |
| break; |
| case 'I': |
| returnTypeRef[0] = JPrimitiveType.INT; |
| break; |
| case 'S': |
| returnTypeRef[0] = JPrimitiveType.SHORT; |
| break; |
| case 'C': |
| returnTypeRef[0] = JPrimitiveType.CHAR; |
| break; |
| case 'F': |
| returnTypeRef[0] = JPrimitiveType.FLOAT; |
| break; |
| case 'D': |
| returnTypeRef[0] = JPrimitiveType.DOUBLE; |
| break; |
| default: |
| throw new IllegalStateException("Unrecognized base type " + descriptor); |
| } |
| // this is the last visitor called on this visitor |
| visitEnd(); |
| } |
| |
| @Override |
| public void visitClassType(String internalName) { |
| assert Name.isInternalName(internalName); |
| outerClass = enclosingClass; |
| JRealClassType classType = resolver.findByInternalName(internalName); |
| if (classType == null) { |
| logger.log(TreeLogger.ERROR, "Unable to find class " + internalName); |
| // Replace bound with Object if we can't find the class. |
| returnTypeRef[0] = resolver.getTypeOracle().getJavaLangObject(); |
| return; |
| } |
| if (!resolver.resolveClass(logger, classType)) { |
| // already logged why it failed. |
| // Ignores the return value to be consistent with the behavior of |
| // CompilationUnitTypeOracleUpdater. |
| } |
| returnTypeRef[0] = classType; |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (returnTypeRef[0] == null) { |
| return; |
| } |
| resolveGenerics(); |
| } |
| |
| @Override |
| public void visitInnerClassType(String innerName) { |
| // Called after visitClass has already been called, and we will |
| // successively refine the class by going into its inner classes. |
| assert returnTypeRef[0] != null; |
| resolveGenerics(); |
| outerClass = (JClassType) returnTypeRef[0]; |
| JClassType searchClass = outerClass; |
| try { |
| JParameterizedType pt = searchClass.isParameterized(); |
| if (pt != null) { |
| searchClass = pt.getBaseType(); |
| } |
| returnTypeRef[0] = searchClass.getNestedType(innerName); |
| } catch (NotFoundException e) { |
| logger.log(TreeLogger.ERROR, "Unable to resolve inner class " + innerName |
| + " in " + searchClass, e); |
| } |
| } |
| |
| @Override |
| public void visitTypeArgument() { |
| JType[] arg = new JType[1]; // This could be int[] for example |
| arg[0] = resolver.getTypeOracle().getWildcardType( |
| JWildcardType.BoundType.UNBOUND, |
| resolver.getTypeOracle().getJavaLangObject()); |
| args.add(arg); |
| } |
| |
| @Override |
| public SignatureVisitor visitTypeArgument(char wildcard) { |
| JType[] arg = new JType[1]; |
| args.add(arg); |
| // TODO(jat): should we pass enclosingClass here instead of null? |
| // not sure what the enclosing class of a type argument means, but |
| // I haven't found a case where it is actually used while processing |
| // the type argument. |
| return new ResolveTypeSignature(resolver, logger, arg, lookup, null, wildcard); |
| } |
| |
| @Override |
| public void visitTypeVariable(String name) { |
| returnTypeRef[0] = lookup.lookup(name); |
| // this is the last visitor called on this visitor |
| visitEnd(); |
| } |
| |
| /** |
| * Merge the bounds from the declared type parameters into the type arguments |
| * for this type if necessary. |
| * |
| * <pre> |
| * Example: |
| * class Foo<T extends Bar> ... |
| * |
| * Foo<?> foo |
| * |
| * foo needs to have bounds ? extends Bar. |
| * </pre> |
| * |
| * <p> |
| * Currently we only deal with unbound wildcards as above, which matches |
| * existing TypeOracleUpdater behavior. However, this may need to be |
| * extended. |
| * |
| * @param typeParams |
| * @param typeArgs |
| */ |
| private void mergeTypeParamBounds(JTypeParameter[] typeParams, |
| JClassType[] typeArgs) { |
| int n = typeArgs.length; |
| for (int i = 0; i < n; ++i) { |
| JWildcardType wildcard = typeArgs[i] == null ? null : typeArgs[i].isWildcard(); |
| // right now we only replace Foo<?> with the constraints defined on the |
| // definition (which appears to match the existing TypeOracleUpdater) |
| // but other cases may need to be handled. |
| if (wildcard != null |
| && wildcard.getBoundType() == BoundType.UNBOUND |
| && wildcard.getBaseType() == resolver.getTypeOracle().getJavaLangObject() |
| && typeParams[i].getBaseType() != null) { |
| typeArgs[i] = resolver.getTypeOracle().getWildcardType( |
| BoundType.UNBOUND, typeParams[i].getBaseType()); |
| } |
| } |
| } |
| |
| private JType resolveGeneric(JType type, JClassType outer, |
| JClassType[] typeArgs) { |
| JGenericType genericType = (JGenericType) type.isGenericType(); |
| if (genericType != null) { |
| int actual = typeArgs.length; |
| JTypeParameter[] typeParams = genericType.getTypeParameters(); |
| int expected = typeParams.length; |
| if (actual == 0 && expected > 0) { |
| // If no type parameters were supplied, this is a raw type usage. |
| type = genericType.getRawType(); |
| } else { |
| if (actual != expected) { |
| throw new IllegalStateException("Incorrect # of type parameters to " |
| + genericType.getQualifiedBinaryName() + ": expected " + expected |
| + ", actual=" + actual); |
| } |
| JClassType genericEnc = genericType.getEnclosingType(); |
| if (outer == null && genericEnc != null) { |
| // Sometimes the signature is like Foo$Bar<H> even if Foo is a |
| // generic class. The cases I have seen are where Foo's type |
| // parameter is also named H and has the same bounds. That |
| // manifests itself as getting visitClassType("Foo$Bar") and |
| // then VisitTypeArgument/etc, rather than the usual |
| // visitClassType("Foo"), visitTypeArgument/etc, |
| // visitInnerClass("Bar"), visitTypeArgument/etc. |
| // |
| // So, in this case we have to build our own chain of enclosing |
| // classes here, properly parameterizing any generics along the |
| // way. |
| // TODO(jat): more testing to validate this assumption |
| JClassType[] outerArgs = null; |
| JGenericType genericEncGeneric = genericEnc.isGenericType(); |
| if (genericEncGeneric != null) { |
| JTypeParameter[] encTypeParams = genericEncGeneric.getTypeParameters(); |
| int n = encTypeParams.length; |
| outerArgs = new JClassType[n]; |
| for (int i = 0; i < n; ++i) { |
| outerArgs[i] = lookup.lookup(encTypeParams[i].getName()); |
| if (outerArgs[i] == null) { |
| // check to see if our current type has a parameter of the same |
| // name, and use it if so. |
| for (int j = 0; j < expected; ++j) { |
| if (typeParams[j].getName().equals(encTypeParams[j].getName())) { |
| outerArgs[i] = typeArgs[j]; |
| break; |
| } |
| } |
| } |
| assert outerArgs[i] != null : "Unable to resolve type parameter " |
| + encTypeParams[i].getName() + " in enclosing type " |
| + genericEnc + " of type " + genericType; |
| } |
| } |
| outer = (JClassType) resolveGeneric(genericEnc, null, outerArgs); |
| } |
| try { |
| mergeTypeParamBounds(typeParams, typeArgs); |
| type = resolver.getTypeOracle().getParameterizedType(genericType, |
| outer, typeArgs); |
| } catch (IllegalArgumentException e) { |
| // Can't use toString on typeArgs as they aren't completely built |
| // yet, so we have to roll our own. |
| StringBuilder buf = new StringBuilder(); |
| buf.append("Unable to build parameterized type "); |
| buf.append(genericType); |
| String prefix = " with args <"; |
| for (JClassType typeArg : typeArgs) { |
| buf.append(prefix).append(typeArg.getName()); |
| prefix = ", "; |
| } |
| if (", ".equals(prefix)) { |
| buf.append('>'); |
| } |
| logger.log(TreeLogger.ERROR, buf.toString(), e); |
| type = genericType.getRawType(); |
| } |
| } |
| } |
| return type; |
| } |
| |
| private void resolveGenerics() { |
| JGenericType genericType = (JGenericType) returnTypeRef[0].isGenericType(); |
| if (genericType != null) { |
| int actual = args.size(); |
| JClassType[] typeArgs = new JClassType[actual]; |
| for (int i = 0; i < actual; ++i) { |
| JType type = args.get(i)[0]; |
| if (!(type instanceof JClassType)) { |
| logger.log(TreeLogger.ERROR, "Parameterized type argument is " + type |
| + ", expected reference type"); |
| } else { |
| typeArgs[i] = (JClassType) type; |
| } |
| } |
| returnTypeRef[0] = resolveGeneric(genericType, outerClass, typeArgs); |
| args.clear(); |
| } |
| for (int i = 0; i < arrayDepth; ++i) { |
| returnTypeRef[0] = resolver.getTypeOracle().getArrayType(returnTypeRef[0]); |
| } |
| switch (wildcardMatch) { |
| case '=': |
| // nothing to do for an exact match |
| break; |
| case '*': |
| returnTypeRef[0] = resolver.getTypeOracle().getWildcardType( |
| JWildcardType.BoundType.UNBOUND, (JClassType) returnTypeRef[0]); |
| break; |
| case '+': |
| // ? extends T |
| returnTypeRef[0] = resolver.getTypeOracle().getWildcardType( |
| JWildcardType.BoundType.EXTENDS, (JClassType) returnTypeRef[0]); |
| break; |
| case '-': |
| // ? super T |
| returnTypeRef[0] = resolver.getTypeOracle().getWildcardType( |
| JWildcardType.BoundType.SUPER, (JClassType) returnTypeRef[0]); |
| break; |
| } |
| if (returnTypeRef[0] instanceof JClassType) { |
| // Only JClassTypes can be an outer class |
| outerClass = (JClassType) returnTypeRef[0]; |
| } |
| } |
| } |