public ArrayType createArrayType(TType elementType, int dimensions) {
    Assert.isTrue(!elementType.isArrayType());
    Assert.isTrue(!elementType.isAnonymous());
    Assert.isTrue(dimensions > 0);

    int index = dimensions - 1;
    Map arrayTypes = getArrayTypesMap(index);
    ArrayType result = (ArrayType) arrayTypes.get(elementType);
    if (result != null) return result;
    result =
        new ArrayType(
            this, BindingKey.createArrayTypeBindingKey(elementType.getBindingKey(), dimensions));
    arrayTypes.put(elementType, result);
    result.initialize(elementType, dimensions);
    return result;
  }
public class TypeEnvironment {

  private static class ProjectKeyPair {
    private final IJavaScriptProject fProject;
    private final String fBindingKey;

    public ProjectKeyPair(IJavaScriptProject project, String bindingKey) {
      fProject = project;
      fBindingKey = bindingKey;
    }

    public boolean equals(Object other) {
      if (this == other) return true;
      if (!(other instanceof ProjectKeyPair)) return false;
      ProjectKeyPair otherPair = (ProjectKeyPair) other;
      return fProject.equals(otherPair.fProject) && fBindingKey.equals(otherPair.fBindingKey);
    }

    public int hashCode() {
      return fProject.hashCode() + fBindingKey.hashCode();
    }
  }

  /** Type code for the primitive type "int". */
  public final PrimitiveType INT =
      new PrimitiveType(
          this, PrimitiveType.INT, BindingKey.createTypeBindingKey("int")); // $NON-NLS-1$
  /** Type code for the primitive type "char". */
  public final PrimitiveType CHAR =
      new PrimitiveType(
          this, PrimitiveType.CHAR, BindingKey.createTypeBindingKey("char")); // $NON-NLS-1$
  /** Type code for the primitive type "boolean". */
  public final PrimitiveType BOOLEAN =
      new PrimitiveType(
          this, PrimitiveType.BOOLEAN, BindingKey.createTypeBindingKey("boolean")); // $NON-NLS-1$
  /** Type code for the primitive type "short". */
  public final PrimitiveType SHORT =
      new PrimitiveType(
          this, PrimitiveType.SHORT, BindingKey.createTypeBindingKey("short")); // $NON-NLS-1$
  /** Type code for the primitive type "long". */
  public final PrimitiveType LONG =
      new PrimitiveType(
          this, PrimitiveType.LONG, BindingKey.createTypeBindingKey("long")); // $NON-NLS-1$
  /** Type code for the primitive type "float". */
  public final PrimitiveType FLOAT =
      new PrimitiveType(
          this, PrimitiveType.FLOAT, BindingKey.createTypeBindingKey("float")); // $NON-NLS-1$
  /** Type code for the primitive type "double". */
  public final PrimitiveType DOUBLE =
      new PrimitiveType(
          this, PrimitiveType.DOUBLE, BindingKey.createTypeBindingKey("double")); // $NON-NLS-1$
  /** Type code for the primitive type "byte". */
  public final PrimitiveType BYTE =
      new PrimitiveType(
          this, PrimitiveType.BYTE, BindingKey.createTypeBindingKey("byte")); // $NON-NLS-1$

  /** Type code for the primitive type "null". */
  public final NullType NULL = new NullType(this);

  public final VoidType VOID = new VoidType(this);

  final PrimitiveType[] PRIMITIVE_TYPES = {INT, CHAR, BOOLEAN, SHORT, LONG, FLOAT, DOUBLE, BYTE};

  private static final String[] BOXED_PRIMITIVE_NAMES =
      new String[] {
        "java.lang.Integer", //$NON-NLS-1$
        "java.lang.Character", //$NON-NLS-1$
        "java.lang.Boolean", //$NON-NLS-1$
        "java.lang.Short", //$NON-NLS-1$
        "java.lang.Long", //$NON-NLS-1$
        "java.lang.Float", //$NON-NLS-1$
        "java.lang.Double", //$NON-NLS-1$
        "java.lang.Byte"
      }; //$NON-NLS-1$

  private TType OBJECT_TYPE = null;

  private Map /*<TType, ArrayType>*/[] fArrayTypes = new Map[] {new HashMap()};
  private Map /*<IJavaScriptElement, StandardType>*/ fStandardTypes = new HashMap();
  private Map /*<IJavaScriptElement, GenericType>*/ fGenericTypes = new HashMap();
  private Map /*<ProjectKeyPair, ParameterizedType>*/ fParameterizedTypes = new HashMap();
  private Map /*<IJavaScriptElement, RawType>*/ fRawTypes = new HashMap();
  private Map /*<ProjectKeyPair, CaptureType>*/ fCaptureTypes = new HashMap();
  private UnboundWildcardType fUnboundWildcardType = null;

  private static final int MAX_ENTRIES = 1024;
  private Map /*<TypeTuple, Boolean>*/ fSubTypeCache =
      new LinkedHashMap(50, 0.75f, true) {
        private static final long serialVersionUID = 1L;

        protected boolean removeEldestEntry(Map.Entry eldest) {
          return size() > MAX_ENTRIES;
        }
      };

  /**
   * Map from TType to its known subtypes, or <code>null</code> iff subtype information was not
   * requested in the constructor.
   */
  private Map /*<TType, List<TType>>*/ fSubTypes;

  public static ITypeBinding[] createTypeBindings(TType[] types, IJavaScriptProject project) {
    final Map mapping = new HashMap();
    List keys = new ArrayList();
    for (int i = 0; i < types.length; i++) {
      TType type = types[i];
      String bindingKey = type.getBindingKey();
      mapping.put(bindingKey, type);
      keys.add(bindingKey);
    }
    ASTParser parser = ASTParser.newParser(AST.JLS3);
    parser.setProject(project);
    parser.setResolveBindings(true);
    parser.createASTs(
        new IJavaScriptUnit[0],
        (String[]) keys.toArray(new String[keys.size()]),
        new ASTRequestor() {
          public void acceptBinding(String bindingKey, IBinding binding) {
            mapping.put(bindingKey, binding);
          }
        },
        null);
    ITypeBinding[] result = new ITypeBinding[types.length];
    for (int i = 0; i < types.length; i++) {
      TType type = types[i];
      String bindingKey = type.getBindingKey();
      Object value = mapping.get(bindingKey);
      if (value instanceof ITypeBinding) {
        result[i] = (ITypeBinding) value;
      }
    }
    return result;
  }

  public TypeEnvironment() {
    this(false);
  }

  public TypeEnvironment(boolean rememberSubtypes) {
    if (rememberSubtypes) {
      fSubTypes = new HashMap();
    }
  }

  Map /*<TypeTuple, Boolean>*/ getSubTypeCache() {
    return fSubTypeCache;
  }

  public TType create(ITypeBinding binding) {
    if (binding.isPrimitive()) {
      return createPrimitiveType(binding);
    } else if (binding.isArray()) {
      return createArrayType(binding);
    }
    if ("null".equals(binding.getName())) // $NON-NLS-1$
    return NULL;
    return createStandardType(binding);
  }

  public TType[] create(ITypeBinding[] bindings) {
    TType[] result = new TType[bindings.length];
    for (int i = 0; i < bindings.length; i++) {
      result[i] = create(bindings[i]);
    }
    return result;
  }

  /**
   * Returns the TType for java.lang.Object.
   *
   * <p>Warning: currently returns <code>null</code> unless this type environment has already
   * created its first hierarchy type.
   *
   * @return the TType for java.lang.Object
   */
  public TType getJavaLangObject() {
    return OBJECT_TYPE;
  }

  void initializeJavaLangObject(ITypeBinding object) {
    if (OBJECT_TYPE != null) return;

    TType objectType = createStandardType(object);
    Assert.isTrue(objectType.isJavaLangObject());
  }

  PrimitiveType createUnBoxed(StandardType type) {
    String name = type.getPlainPrettySignature();
    for (int i = 0; i < BOXED_PRIMITIVE_NAMES.length; i++) {
      if (BOXED_PRIMITIVE_NAMES[i].equals(name)) return PRIMITIVE_TYPES[i];
    }
    return null;
  }

  StandardType createBoxed(PrimitiveType type, IJavaScriptProject focus) {
    String fullyQualifiedName = BOXED_PRIMITIVE_NAMES[type.getId()];
    try {
      IType javaElementType = focus.findType(fullyQualifiedName);
      StandardType result = (StandardType) fStandardTypes.get(javaElementType);
      if (result != null) return result;
      ASTParser parser = ASTParser.newParser(AST.JLS3);
      parser.setProject(focus);
      IBinding[] bindings = parser.createBindings(new IJavaScriptElement[] {javaElementType}, null);
      return createStandardType((ITypeBinding) bindings[0]);
    } catch (JavaScriptModelException e) {
      // fall through
    }
    return null;
  }

  Map /*<TType, List<TType>>*/ getSubTypes() {
    return fSubTypes;
  }

  private void cacheSubType(TType supertype, TType result) {
    if (fSubTypes == null) return;
    if (supertype == null) supertype = OBJECT_TYPE;

    ArrayList subtypes = (ArrayList) fSubTypes.get(supertype);
    if (subtypes == null) {
      subtypes = new ArrayList(5);
      fSubTypes.put(supertype, subtypes);
    } else {
      Assert.isTrue(!subtypes.contains(result));
    }
    subtypes.add(result);
  }

  private void cacheSubTypes(TType[] interfaces, TType result) {
    for (int i = 0; i < interfaces.length; i++) {
      cacheSubType(interfaces[i], result);
    }
  }

  private TType createPrimitiveType(ITypeBinding binding) {
    String name = binding.getName();
    String[] names = PrimitiveType.NAMES;
    for (int i = 0; i < names.length; i++) {
      if (name.equals(names[i])) {
        return PRIMITIVE_TYPES[i];
      }
    }
    Assert.isTrue(false, "Primitive type " + name + "unkown"); // $NON-NLS-1$//$NON-NLS-2$
    return null;
  }

  private ArrayType createArrayType(ITypeBinding binding) {
    int index = binding.getDimensions() - 1;
    TType elementType = create(binding.getElementType());
    Map /*<TType, ArrayType>*/ arrayTypes = getArrayTypesMap(index);
    ArrayType result = (ArrayType) arrayTypes.get(elementType);
    if (result != null) return result;
    result = new ArrayType(this);
    arrayTypes.put(elementType, result);
    result.initialize(binding, elementType);
    return result;
  }

  public ArrayType createArrayType(TType elementType, int dimensions) {
    Assert.isTrue(!elementType.isArrayType());
    Assert.isTrue(!elementType.isAnonymous());
    Assert.isTrue(dimensions > 0);

    int index = dimensions - 1;
    Map arrayTypes = getArrayTypesMap(index);
    ArrayType result = (ArrayType) arrayTypes.get(elementType);
    if (result != null) return result;
    result =
        new ArrayType(
            this, BindingKey.createArrayTypeBindingKey(elementType.getBindingKey(), dimensions));
    arrayTypes.put(elementType, result);
    result.initialize(elementType, dimensions);
    return result;
  }

  private Map /*<TType, ArrayType>*/ getArrayTypesMap(int index) {
    int oldLength = fArrayTypes.length;
    if (index >= oldLength) {
      Map[] newArray = new Map[index + 1];
      System.arraycopy(fArrayTypes, 0, newArray, 0, oldLength);
      fArrayTypes = newArray;
    }
    Map arrayTypes = fArrayTypes[index];
    if (arrayTypes == null) {
      arrayTypes = new HashMap();
      fArrayTypes[index] = arrayTypes;
    }
    return arrayTypes;
  }

  private StandardType createStandardType(ITypeBinding binding) {
    IJavaScriptElement javaElement = binding.getJavaElement();
    StandardType result = (StandardType) fStandardTypes.get(javaElement);
    if (result != null) return result;
    result = new StandardType(this);
    fStandardTypes.put(javaElement, result);
    result.initialize(binding, (IType) javaElement);
    if (OBJECT_TYPE == null && result.isJavaLangObject()) OBJECT_TYPE = result;
    return result;
  }

  private GenericType createGenericType(ITypeBinding binding) {
    IJavaScriptElement javaElement = binding.getJavaElement();
    GenericType result = (GenericType) fGenericTypes.get(javaElement);
    if (result != null) return result;
    result = new GenericType(this);
    fGenericTypes.put(javaElement, result);
    result.initialize(binding, (IType) javaElement);
    cacheSubType(result.getSuperclass(), result);
    cacheSubTypes(result.getInterfaces(), result);
    return result;
  }

  private RawType createRawType(ITypeBinding binding) {
    IJavaScriptElement javaElement = binding.getJavaElement();
    RawType result = (RawType) fRawTypes.get(javaElement);
    if (result != null) return result;
    result = new RawType(this);
    fRawTypes.put(javaElement, result);
    result.initialize(binding, (IType) javaElement);
    cacheSubType(result.getSuperclass(), result);
    cacheSubTypes(result.getInterfaces(), result);
    return result;
  }

  private TType createUnboundWildcardType(ITypeBinding binding) {
    if (fUnboundWildcardType == null) {
      fUnboundWildcardType = new UnboundWildcardType(this);
      fUnboundWildcardType.initialize(binding);
    }
    return fUnboundWildcardType;
  }

  private CaptureType createCaptureType(ITypeBinding binding) {
    IJavaScriptProject javaProject =
        binding.getDeclaringClass().getJavaElement().getJavaScriptProject();
    String bindingKey = binding.getKey();
    ProjectKeyPair pair = new ProjectKeyPair(javaProject, bindingKey);
    CaptureType result = (CaptureType) fCaptureTypes.get(pair);
    if (result != null) return result;
    result = new CaptureType(this);
    fCaptureTypes.put(pair, result);
    result.initialize(binding, javaProject);
    return result;
  }
}