Package com.google.gwt.dev.javac

Source Code of com.google.gwt.dev.javac.TypeOracleMediator

/*
* 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;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.HasTypeParameters;
import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
import com.google.gwt.core.ext.typeinfo.JAnnotationMethod;
import com.google.gwt.core.ext.typeinfo.JAnnotationType;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JEnumConstant;
import com.google.gwt.core.ext.typeinfo.JEnumType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
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.TypeOracle;
import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
import com.google.gwt.dev.javac.CompilationUnit.State;
import com.google.gwt.dev.javac.impl.Shared;
import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.dev.util.collect.HashSet;
import com.google.gwt.dev.util.collect.IdentityHashMap;
import com.google.gwt.dev.util.collect.Maps;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.Clinit;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Builds or rebuilds a {@link com.google.gwt.core.ext.typeinfo.TypeOracle} from
* a set of compilation units.
*/
public class TypeOracleMediator {

  private static final JClassType[] NO_JCLASSES = new JClassType[0];

  /**
   * Returns the binary name of a type. This is the same name that would be
   * returned by {@link Class#getName()} for this type.
   */
  public static String computeBinaryClassName(JType type) {
    JPrimitiveType primitiveType = type.isPrimitive();
    if (primitiveType != null) {
      return primitiveType.getJNISignature();
    }

    JArrayType arrayType = type.isArray();
    if (arrayType != null) {
      JType component = arrayType.getComponentType();
      if (component.isClassOrInterface() != null) {
        return "[L" + computeBinaryClassName(arrayType.getComponentType())
            + ";";
      } else {
        return "[" + computeBinaryClassName(arrayType.getComponentType());
      }
    }

    JParameterizedType parameterizedType = type.isParameterized();
    if (parameterizedType != null) {
      return computeBinaryClassName(parameterizedType.getBaseType());
    }

    JClassType classType = type.isClassOrInterface();
    assert (classType != null);

    JClassType enclosingType = classType.getEnclosingType();
    if (enclosingType != null) {
      return computeBinaryClassName(enclosingType) + "$"
          + classType.getSimpleSourceName();
    }

    return classType.getQualifiedSourceName();
  }

  /**
   * Returns the value associated with a JDT constant.
   */
  private static Object getConstantValue(Constant constant) {
    switch (constant.typeID()) {
      case TypeIds.T_char:
        return constant.charValue();
      case TypeIds.T_byte:
        return constant.byteValue();
      case TypeIds.T_short:
        return constant.shortValue();
      case TypeIds.T_boolean:
        return constant.booleanValue();
      case TypeIds.T_long:
        return constant.longValue();
      case TypeIds.T_double:
        return constant.doubleValue();
      case TypeIds.T_float:
        return constant.floatValue();
      case TypeIds.T_int:
        return constant.intValue();
      case TypeIds.T_JavaLangString:
        return constant.stringValue();
      case TypeIds.T_null:
        return null;
      default:
        break;
    }

    assert false : "Unknown constant type";
    return null;
  }

  private static String getMethodName(JClassType enclosingType,
      AbstractMethodDeclaration jmethod) {
    if (jmethod.isConstructor()) {
      return String.valueOf(enclosingType.getSimpleSourceName());
    } else {
      return String.valueOf(jmethod.binding.selector);
    }
  }

  private static RetentionPolicy getRetentionPolicy(
      Class<? extends java.lang.annotation.Annotation> clazz) {
    // Default retention policy is CLASS, see @Retention.
    RetentionPolicy retentionPolicy = RetentionPolicy.CLASS;
    Retention retentionAnnotation = clazz.getAnnotation(Retention.class);
    if (retentionAnnotation != null) {
      retentionPolicy = retentionAnnotation.value();
    }
    return retentionPolicy;
  }

  /**
   * Returns <code>true</code> if this name is the special package-info type
   * name.
   *
   * @return <code>true</code> if this name is the special package-info type
   *         name
   */
  private static boolean isPackageInfoTypeName(String qname) {
    return "package-info".equals(qname);
  }

  private static boolean maybeGeneric(TypeDeclaration typeDecl,
      JClassType enclosingType) {

    if (typeDecl.typeParameters != null) {
      // Definitely generic since it has type parameters.
      return true;
    }

    if (enclosingType != null && enclosingType.isGenericType() != null) {
      if (!typeDecl.binding.isStatic()) {
        /*
         * This non-static, inner class can reference the type parameters of its
         * enclosing generic type, so we will treat it as a generic type.
         */
        return true;
      }
    }

    if (typeDecl.binding.isLocalType()) {
      LocalTypeBinding localTypeBinding = (LocalTypeBinding) typeDecl.binding;
      MethodBinding enclosingMethod = localTypeBinding.enclosingMethod;
      if (enclosingMethod != null) {
        if (enclosingMethod.typeVariables != null
            && enclosingMethod.typeVariables.length != 0) {
          /*
           * This local type can reference the type parameters of its enclosing
           * method, so we will treat is as a generic type.
           */
          return true;
        }
      }
    }

    return false;
  }

  // TODO: move into the Annotations class or create an AnnotationsUtil class?
  private static HashMap<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> newAnnotationMap() {
    return new HashMap<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation>();
  }

  private final Map<String, JRealClassType> binaryMapper = new HashMap<String, JRealClassType>();

  /**
   * Mapping of source type bindings; transient because freshly-compiled units
   * are processed in chunks, so only references between types in the same chunk
   * are source bindings; the rest are going to be binary type bindings.
   */
  private final transient Map<SourceTypeBinding, JRealClassType> sourceMapper = new IdentityHashMap<SourceTypeBinding, JRealClassType>();

  /**
   * Mapping of type variable bindings; transient because compilation units are
   * processed monolithically, and a tv binding is only valid within a single
   * unit.
   */
  private final transient Map<TypeVariableBinding, JTypeParameter> tvMapper = new IdentityHashMap<TypeVariableBinding, JTypeParameter>();

  private final TypeOracle typeOracle = new TypeOracle();

  /**
   * Set of unresolved types to process, generated from the first phase of type
   * oracle resolution. Transient because they all get resolved in the second
   * phase.
   */
  private final transient Set<JRealClassType> unresolvedTypes = new HashSet<JRealClassType>();

  /**
   * Adds new units to an existing TypeOracle.
   */
  public void addNewUnits(TreeLogger logger, Collection<CompilationUnit> units) {
    // Perform a shallow pass to establish identity for new and old types.
    for (CompilationUnit unit : units) {
      switch (unit.getState()) {
        case GRAVEYARD:
          for (CompiledClass compiledClass : unit.getCompiledClasses()) {
            JRealClassType type = compiledClass.getRealClassType();
            assert (type != null);
            type.resurrect();
            binaryMapper.put(compiledClass.getInternalName(), type);
          }
          break;
        case COMPILED:
          for (CompiledClass compiledClass : unit.getCompiledClasses()) {
            JRealClassType type = createType(compiledClass);
            binaryMapper.put(compiledClass.getInternalName(), type);
          }
          break;
        case CHECKED:
          for (CompiledClass compiledClass : unit.getCompiledClasses()) {
            JRealClassType type = compiledClass.getRealClassType();
            assert (type != null);
            binaryMapper.put(compiledClass.getInternalName(), type);
          }
          break;
      }
    }

    // Perform a deep pass to resolve all new types in terms of our types.
    for (CompilationUnit unit : units) {
      if (unit.getState() != State.COMPILED) {
        continue;
      }
      TreeLogger cudLogger = logger.branch(TreeLogger.SPAM,
          "Processing types in compilation unit: " + unit.getDisplayLocation());
      Set<CompiledClass> compiledClasses = unit.getCompiledClasses();
      for (CompiledClass compiledClass : compiledClasses) {
        if (unresolvedTypes.remove(compiledClass.getRealClassType())) {
          TypeDeclaration typeDeclaration = compiledClass.getTypeDeclaration();
          if (!resolveTypeDeclaration(cudLogger, typeDeclaration)) {
            logger.log(TreeLogger.WARN,
                "Unexpectedly unable to fully resolve type "
                    + compiledClass.getSourceName());
          }
        }
      }
    }
    // Clean transient state.
    assert unresolvedTypes.size() == 0;
    sourceMapper.clear();
    tvMapper.clear();

    typeOracle.finish();
  }

  public TypeOracle getTypeOracle() {
    return typeOracle;
  }

  /**
   * Full refresh based on new units.
   */
  public void refresh(TreeLogger logger, Collection<CompilationUnit> units) {
    logger = logger.branch(TreeLogger.DEBUG, "Refreshing TypeOracle");
    binaryMapper.clear();
    typeOracle.reset();
    addNewUnits(logger, units);
  }

  private Object createAnnotationInstance(TreeLogger logger,
      Expression expression) {
    Annotation annotation = (Annotation) expression;

    // Determine the annotation class
    TypeBinding resolvedType = annotation.resolvedType;
    Class<?> classLiteral = getClassLiteral(logger, resolvedType);
    if (classLiteral == null) {
      return null;
    }

    Class<? extends java.lang.annotation.Annotation> clazz = classLiteral.asSubclass(java.lang.annotation.Annotation.class);

    // Build the map of identifiers to values.
    Map<String, Object> identifierToValue = new HashMap<String, Object>();
    for (MemberValuePair mvp : annotation.memberValuePairs()) {
      // Method name
      String identifier = String.valueOf(mvp.name);

      // Value
      Expression expressionValue = mvp.value;
      TypeBinding expectedElementValueType = mvp.binding.returnType;
      Object elementValue = getAnnotationElementValue(logger,
          expectedElementValueType, expressionValue);
      if (elementValue == null) {
        return null;
      }

      /*
       * If the expected value is supposed to be an array then the element value
       * had better be an array.
       */
      assert (expectedElementValueType.isArrayType() == false || expectedElementValueType.isArrayType()
          && elementValue.getClass().isArray());

      identifierToValue.put(identifier, elementValue);
    }

    return AnnotationProxyFactory.create(clazz,
        Maps.normalize(identifierToValue));
  }

  private JRealClassType createType(CompiledClass compiledClass) {
    JRealClassType realClassType = compiledClass.getRealClassType();
    if (realClassType == null) {
      JRealClassType enclosingType = null;
      CompiledClass enclosingClass = compiledClass.getEnclosingClass();
      if (enclosingClass != null) {
        enclosingType = enclosingClass.getRealClassType();
        if (enclosingType == null) {
          enclosingType = createType(enclosingClass);
        }
      }
      realClassType = createType(compiledClass, enclosingType);
      if (realClassType != null) {
        unresolvedTypes.add(realClassType);
        sourceMapper.put(compiledClass.getTypeDeclaration().binding,
            realClassType);
        compiledClass.setRealClassType(realClassType);
      }
    }
    return realClassType;
  }

  /**
   * Maps a TypeDeclaration into a JRealClassType. If the TypeDeclaration has
   * TypeParameters (i.e, it is a generic type or method), then the
   * TypeParameters are mapped into JTypeParameters.
   */
  private JRealClassType createType(CompiledClass compiledClass,
      JRealClassType enclosingType) {
    TypeDeclaration typeDecl = compiledClass.getTypeDeclaration();
    SourceTypeBinding binding = typeDecl.binding;
    assert (binding.constantPoolName() != null);

    String qname = compiledClass.getSourceName();
    String className = Shared.getShortName(qname);
    String jpkgName = compiledClass.getPackageName();
    JPackage pkg = typeOracle.getOrCreatePackage(jpkgName);
    boolean isLocalType = binding instanceof LocalTypeBinding;
    boolean isIntf = TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.INTERFACE_DECL;
    boolean isAnnotation = TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.ANNOTATION_TYPE_DECL;

    JRealClassType resultType;
    if (isAnnotation) {
      resultType = new JAnnotationType(typeOracle, pkg, enclosingType,
          isLocalType, className, isIntf);
    } else if (maybeGeneric(typeDecl, enclosingType)) {
      // Go through and create declarations for each of the type parameters on
      // the generic class or method
      JTypeParameter[] jtypeParameters = declareTypeParameters(typeDecl.typeParameters);

      JGenericType jgenericType = new JGenericType(typeOracle, pkg,
          enclosingType, isLocalType, className, isIntf, jtypeParameters);

      resultType = jgenericType;
    } else if (binding.isEnum()) {
      resultType = new JEnumType(typeOracle, pkg, enclosingType, isLocalType,
          className, isIntf);
    } else {
      resultType = new JRealClassType(typeOracle, pkg, enclosingType,
          isLocalType, className, isIntf);
    }

    /*
     * Declare type parameters for all methods; we must do this during the first
     * pass.
     */
    if (typeDecl.methods != null) {
      for (AbstractMethodDeclaration method : typeDecl.methods) {
        declareTypeParameters(method.typeParameters());
      }
    }

    /*
     * Add modifiers since these are needed for
     * TypeOracle.getParameterizedType's error checking code.
     */
    resultType.addModifierBits(Shared.bindingToModifierBits(binding));
    return resultType;
  }

  private JClassType[] createTypeParameterBounds(TreeLogger logger,
      TypeVariableBinding tvBinding) {
    TypeBinding firstBound = tvBinding.firstBound;
    if (firstBound == null) {
      // No bounds were specified, so we default to Object. We assume that the
      // superclass field of a TypeVariableBinding object is a Binding
      // for a java.lang.Object, and we use this binding to find the
      // JClassType for java.lang.Object. To be sure that our assumption
      // about the superclass field is valid, we perform a runtime check
      // against the name of the resolved type.

      // You may wonder why we have to go this roundabout way to find a
      // JClassType for java.lang.Object. The reason is that the TypeOracle
      // has not been constructed as yet, so we cannot simply call
      // TypeOracle.getJavaLangObject().
      JClassType jimplicitUpperBound = (JClassType) resolveType(logger,
          tvBinding.superclass);
      if (jimplicitUpperBound != null) {
        assert (Object.class.getName().equals(jimplicitUpperBound.getQualifiedSourceName()));
        return new JClassType[] {jimplicitUpperBound};
      }

      // Failed to resolve the implicit upper bound.
      return null;
    }

    List<JClassType> bounds = new ArrayList<JClassType>();
    JClassType jfirstBound = (JClassType) resolveType(logger, firstBound);
    if (jfirstBound == null) {
      return null;
    }

    bounds.add(jfirstBound);

    ReferenceBinding[] superInterfaces = tvBinding.superInterfaces();
    for (ReferenceBinding superInterface : superInterfaces) {
      if (superInterface != firstBound) {
        JClassType jsuperInterface = (JClassType) resolveType(logger,
            superInterface);
        if (jsuperInterface == null) {
          return null;
        }
        bounds.add(jsuperInterface);
      } else {
        /*
         * If the first bound was an interface JDT will still include it in the
         * set of superinterfaces. So, we ignore it since we have already added
         * it to the bounds.
         */
      }
    }

    return bounds.toArray(NO_JCLASSES);
  }

  /**
   * Declares TypeParameters declared on a JGenericType or a JAbstractMethod by
   * mapping the TypeParameters into JTypeParameters. <p/> This mapping has to
   * be done on the first pass through the AST in order to handle declarations
   * of the form: <<C exends GenericClass<T>, T extends SimpleClass> <p/> JDT
   * already knows that GenericClass<T> is a parameterized type with a type
   * argument of <T extends SimpleClass>. Therefore, in order to resolve
   * GenericClass<T>, we must have knowledge of <T extends SimpleClass>. <p/>
   * By calling this method on the first pass through the AST, a JTypeParameter
   * for <T extends SimpleClass> will be created.
   */
  private JTypeParameter[] declareTypeParameters(TypeParameter[] typeParameters) {
    if (typeParameters == null || typeParameters.length == 0) {
      return null;
    }

    JTypeParameter[] jtypeParamArray = new JTypeParameter[typeParameters.length];
    for (int i = 0; i < typeParameters.length; ++i) {
      TypeParameter typeParam = typeParameters[i];
      jtypeParamArray[i] = new JTypeParameter(String.valueOf(typeParam.name), i);
      tvMapper.put(typeParam.binding, jtypeParamArray[i]);
    }

    return jtypeParamArray;
  }

  /**
   * Returns an annotation element value as defined in JLS 3.0 section 9.7.
   *
   * @param logger
   * @param expectedElementValueType the expected element value type
   * @param elementValueExpression the expression that defines the element value
   *
   * @return annotation element value as defined in JLS 3.0 section 9.7
   */
  @SuppressWarnings("unchecked")
  private Object getAnnotationElementValue(TreeLogger logger,
      TypeBinding expectedElementValueType, Expression elementValueExpression) {

    Object elementValue = null;

    if (elementValueExpression.constant != null
        && elementValueExpression.constant != Constant.NotAConstant) {
      /*
       * Rely on JDT's computed constant value to deal with an
       * AnnotationElementValue expression whose resolved type is a primitive or
       * a string.
       */
      Constant constant = elementValueExpression.constant;
      int expectedTypeId = expectedElementValueType.id;

      if (expectedElementValueType.isArrayType()) {
        /*
         * This can happen when an element value is an array with a single
         * element. In this case JLS 3.0 section 9.7 allows for the
         * ArrayInitializer expression to be implicit. Since, element values can
         * only be single dimensional arrays, we take the leaf type of the
         * expected array type as our resultant element value type.
         */
        assert (!elementValueExpression.resolvedType.isArrayType() && expectedElementValueType.dimensions() == 1);

        expectedTypeId = expectedElementValueType.leafComponentType().id;
      }

      if (elementValueExpression.resolvedType.id != expectedTypeId) {
        /*
         * Narrowing and widening conversions are handled by the following
         * Constant.castTo call. JDT wants the upper four bits of this mask to
         * be the target type id and the lower four bits to be the source type
         * id. See Constant.castTo for more details.
         */
        constant = constant.castTo((expectedTypeId << 4)
            + elementValueExpression.resolvedType.id);
      }

      elementValue = getConstantValue(constant);
    } else if (elementValueExpression instanceof ClassLiteralAccess) {
      ClassLiteralAccess classLiteral = (ClassLiteralAccess) elementValueExpression;
      elementValue = getClassLiteral(logger, classLiteral.targetType);
    } else if (elementValueExpression instanceof ArrayInitializer) {
      elementValue = getAnnotationElementValueArray(logger,
          (ArrayInitializer) elementValueExpression);
    } else if (elementValueExpression instanceof NameReference) {
      /*
       * Any primitive types, conditionals, strings, arrays and name references
       * to constant fields will have all been handled by the constant
       * expression block above. This name reference can only be for an
       * enumerated type.
       */
      NameReference nameRef = (NameReference) elementValueExpression;

      assert (nameRef.constant == null || nameRef.constant == Constant.NotAConstant);
      assert (nameRef.actualReceiverType.isEnum());

      Class<?> clazz = getClassLiteral(logger, nameRef.actualReceiverType);
      Class<? extends Enum> enumClass = clazz.asSubclass(Enum.class);

      String enumName = String.valueOf(nameRef.fieldBinding().name);
      elementValue = Enum.valueOf(enumClass, enumName);
    } else if (elementValueExpression instanceof Annotation) {
      elementValue = createAnnotationInstance(logger, elementValueExpression);
    } else {
      assert (false);
      return null;
    }

    assert (elementValue != null);

    if (expectedElementValueType.isArrayType()
        && !elementValue.getClass().isArray()) {
      /*
       * Handle single element arrays where no explicit array initializer was
       * given.
       */
      Object array = Array.newInstance(elementValue.getClass(), 1);
      Array.set(array, 0, elementValue);
      elementValue = array;
    }

    return elementValue;
  }

  /**
   * Returns an annotation element value array. These arrays can only have a
   * single dimension.
   */
  private Object getAnnotationElementValueArray(TreeLogger logger,
      ArrayInitializer arrayInitializer) {
    assert (arrayInitializer.binding.dimensions == 1);

    Class<?> leafComponentClass = getClassLiteral(logger,
        arrayInitializer.binding.leafComponentType);
    if (leafComponentClass == null) {
      return null;
    }

    Expression[] initExpressions = arrayInitializer.expressions;
    int arrayLength = initExpressions != null ? initExpressions.length : 0;

    Object array = Array.newInstance(leafComponentClass, arrayLength);
    boolean failed = false;
    for (int i = 0; i < arrayLength; ++i) {
      Expression arrayInitExp = initExpressions[i];
      Object value = getAnnotationElementValue(logger,
          arrayInitializer.binding.leafComponentType, arrayInitExp);
      if (value != null) {
        Array.set(array, i, value);
      } else {
        failed = true;
        break;
      }
    }

    if (!failed) {
      return array;
    }

    return null;
  }

  private Class<?> getClassLiteral(TreeLogger logger, TypeBinding resolvedType) {
    if (resolvedType instanceof BaseTypeBinding) {
      return getClassLiteralForPrimitive((BaseTypeBinding) resolvedType);
    } else {
      try {
        String className = String.valueOf(resolvedType.constantPoolName());
        className = className.replace('/', '.');
        return Class.forName(className, false,
            Thread.currentThread().getContextClassLoader());
      } catch (ClassNotFoundException e) {
        logger.log(TreeLogger.ERROR, "", e);
        return null;
      }
    }
  }

  private Class<?> getClassLiteralForPrimitive(BaseTypeBinding type) {
    switch (type.id) {
      case TypeIds.T_boolean:
        return Boolean.TYPE;
      case TypeIds.T_byte:
        return Byte.TYPE;
      case TypeIds.T_char:
        return Character.TYPE;
      case TypeIds.T_short:
        return Short.TYPE;
      case TypeIds.T_int:
        return Integer.TYPE;
      case TypeIds.T_long:
        return Long.TYPE;
      case TypeIds.T_float:
        return Float.TYPE;
      case TypeIds.T_double:
        return Double.TYPE;
      case TypeIds.T_void:
        return Void.TYPE;
      default:
        assert false : "Unexpected base type id " + type.id;
        return null;
    }
  }

  /**
   * Returns the qualified name of the binding, excluding any type parameter
   * information.
   */
  private String getQualifiedName(ReferenceBinding binding) {
    String qualifiedName = CharOperation.toString(binding.compoundName);
    if (binding instanceof LocalTypeBinding) {
      // The real name of a local type is its constant pool name.
      qualifiedName = CharOperation.charToString(binding.constantPoolName());
      qualifiedName = qualifiedName.replace('/', '.');
    } else {
      /*
       * All other types have their fully qualified name as part of its compound
       * name.
       */
      qualifiedName = CharOperation.toString(binding.compoundName);
    }

    qualifiedName = qualifiedName.replace('$', '.');
    return qualifiedName;
  }

  private boolean resolveAnnotation(
      TreeLogger logger,
      Annotation jannotation,
      Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations) {

    logger = logger.branch(TreeLogger.SPAM, "Resolving annotation '"
        + jannotation.printExpression(0, new StringBuffer()).toString() + "'",
        null);

    // Determine the annotation class
    TypeBinding resolvedType = jannotation.resolvedType;
    Class<?> classLiteral = getClassLiteral(logger, resolvedType);
    if (classLiteral == null) {
      return false;
    }

    java.lang.annotation.Annotation annotation = (java.lang.annotation.Annotation) createAnnotationInstance(
        logger, jannotation);
    if (annotation == null) {
      return false;
    }

    Class<? extends java.lang.annotation.Annotation> clazz = classLiteral.asSubclass(java.lang.annotation.Annotation.class);
    // Do not reflect source-only annotations.
    if (getRetentionPolicy(clazz) != RetentionPolicy.SOURCE) {
      declaredAnnotations.put(clazz, annotation);
    }
    return true;
  }

  private boolean resolveAnnotations(
      TreeLogger logger,
      Annotation[] annotations,
      Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations) {
    boolean succeeded = true;
    if (annotations != null) {
      for (Annotation annotation : annotations) {
        succeeded &= resolveAnnotation(logger, annotation, declaredAnnotations);
      }
    }
    return succeeded;
  }

  private boolean resolveBoundForTypeParameter(TreeLogger logger,
      HasTypeParameters genericElement, TypeParameter typeParameter, int ordinal) {
    JClassType[] jbounds = createTypeParameterBounds(logger,
        typeParameter.binding);
    if (jbounds == null) {
      return false;
    }

    genericElement.getTypeParameters()[ordinal].setBounds(jbounds);
    return true;
  }

  private boolean resolveBoundsForTypeParameters(TreeLogger logger,
      HasTypeParameters genericElement, TypeParameter[] typeParameters) {
    if (typeParameters != null) {
      for (int i = 0; i < typeParameters.length; ++i) {
        if (!resolveBoundForTypeParameter(logger, genericElement,
            typeParameters[i], i)) {
          return false;
        }
      }
    }
    return true;
  }

  private boolean resolveField(TreeLogger logger, JClassType enclosingType,
      FieldDeclaration jfield) {

    if (jfield instanceof Initializer) {
      // Pretend we didn't see this.
      //
      return true;
    }

    // Try to resolve annotations, ignore any that fail.
    Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations = newAnnotationMap();
    resolveAnnotations(logger, jfield.annotations, declaredAnnotations);

    String name = String.valueOf(jfield.name);
    JField field;
    if (jfield.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT) {
      assert (enclosingType.isEnum() != null);
      field = new JEnumConstant(enclosingType, name, declaredAnnotations,
          jfield.binding.original().id);
    } else {
      field = new JField(enclosingType, name, declaredAnnotations);
    }

    // Get modifiers.
    //
    field.addModifierBits(Shared.bindingToModifierBits(jfield.binding));

    // Set the field type.
    //
    TypeBinding jfieldType = jfield.binding.type;

    JType fieldType = resolveType(logger, jfieldType);
    if (fieldType == null) {
      // Unresolved type.
      //
      return false;
    }
    field.setType(fieldType);

    return true;
  }

  private boolean resolveFields(TreeLogger logger, JClassType type,
      FieldDeclaration[] jfields) {
    if (jfields != null) {
      for (int i = 0; i < jfields.length; i++) {
        FieldDeclaration jfield = jfields[i];
        if (!resolveField(logger, type, jfield)) {
          return false;
        }
      }
    }
    return true;
  }

  private boolean resolveMethod(TreeLogger logger, JClassType enclosingType,
      AbstractMethodDeclaration jmethod) {

    if (jmethod instanceof Clinit) {
      // Pretend we didn't see this.
      //
      return true;
    }

    String name = getMethodName(enclosingType, jmethod);

    // Try to resolve annotations, ignore any that fail.
    Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations = newAnnotationMap();
    resolveAnnotations(logger, jmethod.annotations, declaredAnnotations);

    JAbstractMethod method;

    // Declare the type parameters. We will pass them into the constructors for
    // JConstructor/JMethod/JAnnotatedMethod. Then, we'll do a second pass to
    // resolve the bounds on each JTypeParameter object.
    JTypeParameter[] jtypeParameters = resolveTypeParameters(jmethod.typeParameters());

    if (jmethod.isConstructor()) {
      method = new JConstructor(enclosingType, name, declaredAnnotations,
          jtypeParameters);
      // Do a second pass to resolve the bounds on each JTypeParameter.
      if (!resolveBoundsForTypeParameters(logger, method,
          jmethod.typeParameters())) {
        return false;
      }
    } else {
      if (jmethod.isAnnotationMethod()) {
        AnnotationMethodDeclaration annotationMethod = (AnnotationMethodDeclaration) jmethod;
        Object defaultValue = null;
        if (annotationMethod.defaultValue != null) {
          defaultValue = getAnnotationElementValue(logger,
              annotationMethod.returnType.resolvedType,
              annotationMethod.defaultValue);
        }
        method = new JAnnotationMethod(enclosingType, name, defaultValue,
            declaredAnnotations);
      } else {
        method = new JMethod(enclosingType, name, declaredAnnotations,
            jtypeParameters);
      }

      // Do a second pass to resolve the bounds on each JTypeParameter.
      // The type parameters must be resolved at this point, because they may
      // be used in the resolution of the method's return type.
      if (!resolveBoundsForTypeParameters(logger, method,
          jmethod.typeParameters())) {
        return false;
      }

      TypeBinding jreturnType = ((MethodDeclaration) jmethod).returnType.resolvedType;
      JType returnType = resolveType(logger, jreturnType);
      if (returnType == null) {
        // Unresolved type.
        //
        return false;
      }
      ((JMethod) method).setReturnType(returnType);
    }

    // Parse modifiers.
    //
    method.addModifierBits(Shared.bindingToModifierBits(jmethod.binding));
    if (enclosingType.isInterface() != null) {
      // Always add implicit modifiers on interface methods.
      //
      method.addModifierBits(Shared.MOD_PUBLIC | Shared.MOD_ABSTRACT);
    }

    // Add the parameters.
    //
    Argument[] jparams = jmethod.arguments;
    if (!resolveParameters(logger, method, jparams)) {
      return false;
    }

    // Add throws.
    //
    TypeReference[] jthrows = jmethod.thrownExceptions;
    if (!resolveThrownTypes(logger, method, jthrows)) {
      return false;
    }

    return true;
  }

  private boolean resolveMethods(TreeLogger logger, JClassType type,
      AbstractMethodDeclaration[] jmethods) {
    if (jmethods != null) {
      for (int i = 0; i < jmethods.length; i++) {
        AbstractMethodDeclaration jmethod = jmethods[i];
        if (!resolveMethod(logger, type, jmethod)) {
          return false;
        }
      }
    }
    return true;
  }

  private boolean resolvePackage(TreeLogger logger, TypeDeclaration jclass) {
    SourceTypeBinding binding = jclass.binding;

    String packageName = String.valueOf(binding.fPackage.readableName());
    JPackage pkg = typeOracle.getOrCreatePackage(packageName);
    assert (pkg != null);

    CompilationUnitScope cus = (CompilationUnitScope) jclass.scope.parent;
    assert (cus != null);

    // Try to resolve annotations, ignore any that fail.
    Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations = newAnnotationMap();
    resolveAnnotations(logger, cus.referenceContext.currentPackage.annotations,
        declaredAnnotations);

    pkg.addAnnotations(declaredAnnotations);
    return true;
  }

  private boolean resolveParameter(TreeLogger logger, JAbstractMethod method,
      Argument jparam) {
    TypeBinding jtype = jparam.binding.type;
    JType type = resolveType(logger, jtype);
    if (type == null) {
      // Unresolved.
      //
      return false;
    }

    // Try to resolve annotations, ignore any that fail.
    Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations = newAnnotationMap();
    resolveAnnotations(logger, jparam.annotations, declaredAnnotations);

    String name = String.valueOf(jparam.name);
    new JParameter(method, type, name, declaredAnnotations);
    if (jparam.isVarArgs()) {
      method.setVarArgs();
    }
    return true;
  }

  private boolean resolveParameters(TreeLogger logger, JAbstractMethod method,
      Argument[] jparams) {
    if (jparams != null) {
      for (int i = 0; i < jparams.length; i++) {
        Argument jparam = jparams[i];
        if (!resolveParameter(logger, method, jparam)) {
          return false;
        }
      }
    }
    return true;
  }

  private boolean resolveThrownType(TreeLogger logger, JAbstractMethod method,
      TypeReference jthrown) {

    JType type = resolveType(logger, jthrown.resolvedType);
    if (type == null) {
      // Not resolved.
      //
      return false;
    }

    method.addThrows(type);

    return true;
  }

  private boolean resolveThrownTypes(TreeLogger logger, JAbstractMethod method,
      TypeReference[] jthrows) {
    if (jthrows != null) {
      for (int i = 0; i < jthrows.length; i++) {
        TypeReference jthrown = jthrows[i];
        if (!resolveThrownType(logger, method, jthrown)) {
          return false;
        }
      }
    }
    return true;
  }

  private JType resolveType(TreeLogger logger, TypeBinding binding) {
    // Check for primitives.
    if (binding instanceof BaseTypeBinding) {
      switch (binding.id) {
        case TypeIds.T_boolean:
          return JPrimitiveType.BOOLEAN;
        case TypeIds.T_byte:
          return JPrimitiveType.BYTE;
        case TypeIds.T_char:
          return JPrimitiveType.CHAR;
        case TypeIds.T_short:
          return JPrimitiveType.SHORT;
        case TypeIds.T_int:
          return JPrimitiveType.INT;
        case TypeIds.T_long:
          return JPrimitiveType.LONG;
        case TypeIds.T_float:
          return JPrimitiveType.FLOAT;
        case TypeIds.T_double:
          return JPrimitiveType.DOUBLE;
        case TypeIds.T_void:
          return JPrimitiveType.VOID;
        default:
          assert false : "Unexpected base type id " + binding.id;
      }
    }

    /*
     * Check for a user-defined type, which may be either a SourceTypeBinding or
     * a RawTypeBinding. Both of these are subclasses of ReferenceBinding and
     * all the functionality we need is on ReferenceBinding, so we cast it to
     * that and deal with them in common code.
     *
     * TODO: do we need to do anything more with raw types?
     */
    if (binding instanceof SourceTypeBinding
        || binding instanceof RawTypeBinding) {
      ReferenceBinding referenceBinding = (ReferenceBinding) binding;

      // First check the type oracle to prefer type identity with the type
      // oracle we're assimilating into.
      //
      String typeName = getQualifiedName(referenceBinding);
      JType resolvedType = typeOracle.findType(typeName);
      if (resolvedType == null) {
        // Otherwise, it should be something we've mapped during this build.
        resolvedType = sourceMapper.get(referenceBinding);
      }

      if (resolvedType != null) {
        if (binding instanceof RawTypeBinding) {
          // Use the raw type instead of the generic type.
          JGenericType genericType = (JGenericType) resolvedType;
          resolvedType = genericType.getRawType();
        }
        return resolvedType;
      }
    }

    if (binding instanceof BinaryTypeBinding) {
      // Try a binary lookup.
      String binaryName = String.valueOf(binding.constantPoolName());
      JRealClassType realClassType = binaryMapper.get(binaryName);
      if (realClassType != null) {
        return realClassType;
      }
    }

    // Check for an array.
    //
    if (binding instanceof ArrayBinding) {
      ArrayBinding arrayBinding = (ArrayBinding) binding;

      // Start by resolving the leaf type.
      //
      TypeBinding leafBinding = arrayBinding.leafComponentType;
      JType resolvedType = resolveType(logger, leafBinding);
      if (resolvedType != null) {
        int dims = arrayBinding.dimensions;
        for (int i = 0; i < dims; ++i) {
          // By using the oracle to intern, we guarantee correct identity
          // mapping of lazily-created array types.
          //
          resolvedType = typeOracle.getArrayType(resolvedType);
        }
        return resolvedType;
      } else {
        // Fall-through to failure.
        //
      }
    }

    // Check for parameterized.
    if (binding instanceof ParameterizedTypeBinding) {
      ParameterizedTypeBinding ptBinding = (ParameterizedTypeBinding) binding;

      /*
       * NOTE: it is possible for ParameterizedTypeBinding.arguments to be null.
       * This can happen if a generic class has a non-static, non-generic, inner
       * class that references a TypeParameter from its enclosing generic type.
       * You would think that typeVariables() would do the right thing but it
       * does not.
       */
      TypeBinding[] arguments = ptBinding.arguments;
      int nArguments = arguments != null ? arguments.length : 0;
      JClassType[] typeArguments = new JClassType[nArguments];
      boolean failed = false;
      for (int i = 0; i < typeArguments.length; ++i) {
        typeArguments[i] = (JClassType) resolveType(logger, arguments[i]);
        if (typeArguments[i] == null) {
          failed = true;
        }
      }

      JClassType enclosingType = null;
      if (ptBinding.enclosingType() != null) {
        enclosingType = (JClassType) resolveType(logger,
            ptBinding.enclosingType());
        if (enclosingType == null) {
          failed = true;
        }
      }

      /*
       * NOTE: In the case where a generic type has a nested, non-static,
       * non-generic type. The type for the binding will not be a generic type.
       */
      JType resolveType = resolveType(logger, ptBinding.genericType());
      if (resolveType == null) {
        failed = true;
      }

      if (!failed) {
        if (resolveType.isGenericType() != null) {
          return typeOracle.getParameterizedType(resolveType.isGenericType(),
              enclosingType, typeArguments);
        } else {
          /*
           * A static type (enum or class) that does not declare any type
           * parameters that is nested within a generic type might be referenced
           * via a parameterized type by JDT. In this case we just return the
           * type and don't treat it as a parameterized.
           */
          return resolveType;
        }
      } else {
        // Fall-through to failure
      }
    }

    if (binding instanceof TypeVariableBinding) {
      TypeVariableBinding tvBinding = (TypeVariableBinding) binding;
      JTypeParameter typeParameter = tvMapper.get(tvBinding);
      if (typeParameter != null) {
        return typeParameter;
      }

      // Fall-through to failure
    }

    if (binding instanceof WildcardBinding) {
      WildcardBinding wcBinding = (WildcardBinding) binding;

      assert (wcBinding.otherBounds == null);

      BoundType boundType;
      JClassType typeBound;

      switch (wcBinding.boundKind) {
        case Wildcard.EXTENDS: {
          assert (wcBinding.bound != null);
          boundType = BoundType.EXTENDS;
          typeBound = (JClassType) resolveType(logger, wcBinding.bound);
        }
          break;
        case Wildcard.SUPER: {
          assert (wcBinding.bound != null);
          boundType = BoundType.SUPER;
          typeBound = (JClassType) resolveType(logger, wcBinding.bound);
        }
          break;
        case Wildcard.UNBOUND: {
          boundType = BoundType.UNBOUND;
          typeBound = (JClassType) resolveType(logger, wcBinding.erasure());
        }
          break;
        default:
          assert false : "WildcardBinding of unknown boundKind???";
          return null;
      }

      if (boundType != null) {
        return typeOracle.getWildcardType(boundType, typeBound);
      }

      // Fall-through to failure
    }

    // Log other cases we know about that don't make sense.
    //
    String name = String.valueOf(binding.readableName());
    logger = logger.branch(TreeLogger.WARN, "Unable to resolve type: " + name
        + " binding: " + binding.getClass().getCanonicalName(), null);

    if (binding instanceof BinaryTypeBinding) {
      logger.log(TreeLogger.WARN,
          "Source not available for this type, so it cannot be resolved", null);
    }

    return null;
  }

  private boolean resolveTypeDeclaration(TreeLogger logger,
      TypeDeclaration clazz) {
    SourceTypeBinding binding = clazz.binding;
    assert (binding.constantPoolName() != null);

    String qname = String.valueOf(binding.qualifiedSourceName());
    logger = logger.branch(TreeLogger.SPAM, "Found type '" + qname + "'", null);

    // Handle package-info classes.
    if (isPackageInfoTypeName(qname)) {
      return resolvePackage(logger, clazz);
    }

    // Just resolve the type.
    JRealClassType jtype = (JRealClassType) resolveType(logger, binding);
    if (jtype == null) {
      // Failed to resolve.
      //
      return false;
    }

    /*
     * Modifiers were added during processType since getParameterizedType
     * depends on them being set.
     */

    // Try to resolve annotations, ignore any that fail.
    Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations = newAnnotationMap();
    resolveAnnotations(logger, clazz.annotations, declaredAnnotations);
    jtype.addAnnotations(declaredAnnotations);

    // Resolve bounds for type parameters on generic types. Note that this
    // step does not apply to type parameters on generic methods; that
    // occurs during the method resolution stage.
    JGenericType jGenericType = jtype.isGenericType();
    if (jGenericType != null
        && !resolveBoundsForTypeParameters(logger, jtype.isGenericType(),
            clazz.typeParameters)) {
      // Failed to resolve
      return false;
    }

    // Resolve superclass (for classes only).
    //
    if (jtype.isInterface() == null) {
      ReferenceBinding superclassRef = binding.superclass;
      assert superclassRef != null
          || "java.lang.Object".equals(jtype.getQualifiedSourceName());
      if (superclassRef != null) {
        JClassType jsuperClass = (JClassType) resolveType(logger, superclassRef);
        assert jsuperClass != null;
        jtype.setSuperclass(jsuperClass);
      }
    }

    // Resolve superinterfaces.
    //
    ReferenceBinding[] superintfRefs = binding.superInterfaces;
    for (int i = 0; i < superintfRefs.length; i++) {
      ReferenceBinding superintfRef = superintfRefs[i];
      JClassType jinterface = (JClassType) resolveType(logger, superintfRef);
      if (jinterface == null) {
        // Failed to resolve.
        //
        return false;
      }
      jtype.addImplementedInterface(jinterface);
    }

    // Resolve fields.
    //
    FieldDeclaration[] fields = clazz.fields;
    if (!resolveFields(logger, jtype, fields)) {
      return false;
    }

    // Resolve methods. This also involves the declaration of type
    // variables on generic methods, and the resolution of the bounds
    // on these type variables.

    // One would think that it would be better to perform the declaration
    // of type variables on methods at the time when we are processing
    // all of the classes. Unfortunately, when we are processing classes,
    // we do not have enough information about their methods to analyze
    // their type variables. Hence, the type variable declaration and
    // bounds resolution for generic methods must happen after the resolution
    // of methods is complete.
    AbstractMethodDeclaration[] methods = clazz.methods;
    if (!resolveMethods(logger, jtype, methods)) {
      return false;
    }

    return true;
  }

  /**
   * Declares TypeParameters declared on a JGenericType or a JAbstractMethod by
   * mapping the TypeParameters into JTypeParameters. <p/> This mapping has to
   * be done on the first pass through the AST in order to handle declarations
   * of the form: <<C exends GenericClass<T>, T extends SimpleClass> <p/> JDT
   * already knows that GenericClass<T> is a parameterized type with a type
   * argument of <T extends SimpleClass>. Therefore, in order to resolve
   * GenericClass<T>, we must have knowledge of <T extends SimpleClass>. <p/>
   * By calling this method on the first pass through the AST, a JTypeParameter
   * for <T extends SimpleClass> will be created.
   */
  private JTypeParameter[] resolveTypeParameters(TypeParameter[] typeParameters) {
    if (typeParameters == null || typeParameters.length == 0) {
      return null;
    }

    JTypeParameter[] jtypeParamArray = new JTypeParameter[typeParameters.length];
    for (int i = 0; i < typeParameters.length; ++i) {
      jtypeParamArray[i] = tvMapper.get(typeParameters[i].binding);
      assert jtypeParamArray[i] != null;
    }

    return jtypeParamArray;
  }
}
TOP

Related Classes of com.google.gwt.dev.javac.TypeOracleMediator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.