/*
 * 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.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.dev.util.collect.HashSet;

import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Type used to represent any non-primitive type.
 */
public abstract class JClassType implements
    com.google.gwt.core.ext.typeinfo.JClassType {

  /**
   * Returns all of the superclasses and superinterfaces for a given type
   * including the type itself. The returned set maintains an internal
   * breadth-first ordering of the type, followed by its interfaces (and their
   * super-interfaces), then the supertype and its interfaces, and so on.
   */
  protected static Set<JClassType> getFlattenedSuperTypeHierarchy(
      JClassType type) {
    Set<JClassType> flattened = type.flattenedSupertypes;
    if (flattened == null) {
      flattened = new LinkedHashSet<JClassType>();
      getFlattenedSuperTypeHierarchyRecursive(type, flattened);
      // flattened.size() > 1 for all types other than Object
      type.flattenedSupertypes = Collections.unmodifiableSet(flattened);
    }
    return flattened;
  }

  private static void getFlattenedSuperTypeHierarchyRecursive(JClassType type,
      Set<JClassType> typesSeen) {
    if (typesSeen.contains(type)) {
      return;
    }
    typesSeen.add(type);

    // Check the interfaces
    JClassType[] intfs = type.getImplementedInterfaces();
    for (JClassType intf : intfs) {
      typesSeen.addAll(getFlattenedSuperTypeHierarchy(intf));
    }

    // Superclass
    JClassType superclass = type.getSuperclass();
    if (superclass != null) {
      typesSeen.addAll(getFlattenedSuperTypeHierarchy(superclass));
    }
  }

  /**
   * Cached set of supertypes for this type (including itself). If null, the set
   * has not been calculated yet.
   */
  private Set<JClassType> flattenedSupertypes;

  /**
   * True if this type may be enhanced with server-only fields. This property is
   * 'sticky' and may be set but not unset, since we need to generate the
   * relevant RPC code for handling the server fields if there is any chance the
   * class will be enhanced.
   */
  private boolean isEnhanced = false;

  @Override
  public JParameterizedType asParameterizationOf(
      com.google.gwt.core.ext.typeinfo.JGenericType type) {
    Set<JClassType> supertypes = getFlattenedSuperTypeHierarchy(this);
    for (JClassType supertype : supertypes) {
      JParameterizedType isParameterized = supertype.isParameterized();
      if (isParameterized != null && isParameterized.getBaseType() == type) {
        return isParameterized;
      }

      JRawType isRaw = supertype.isRawType();
      if (isRaw != null && isRaw.getBaseType() == type) {
        return isRaw.asParameterizedByWildcards();
      }
    }

    return null;
  }

  /**
   * All types use identity for comparison.
   */
  @Override
  public final boolean equals(Object obj) {
    return super.equals(obj);
  }

  /**
   * Find an annotation on a type or on one of its superclasses or
   * superinterfaces.
   * <p>
   * This provides semantics similar to that of
   * {@link java.lang.annotation.Inherited} except that it checks all types to
   * which this type is assignable. {@code @Inherited} only works on
   * superclasses, not superinterfaces.
   * <p>
   * Annotations present on the superclass chain will be returned preferentially
   * over those found in the superinterface hierarchy. Note that the annotation
   * does not need to be tagged with {@code @Inherited} in order to be returned
   * from the superclass chain.
   *
   * @param annotationType the type of the annotation to look for
   * @return the desired annotation or <code>null</code> if the annotation is
   *         not present in the type's type hierarchy
   */
  @Override
  public <T extends Annotation> T findAnnotationInTypeHierarchy(
      Class<T> annotationType) {

    // Remember what we've seen to avoid loops
    Set<JClassType> seen = new HashSet<JClassType>();

    // Work queue
    List<JClassType> searchTypes = new LinkedList<JClassType>();
    searchTypes.add(this);

    T toReturn = null;

    while (!searchTypes.isEmpty()) {
      JClassType current = searchTypes.remove(0);

      if (!seen.add(current)) {
        continue;
      }

      toReturn = current.getAnnotation(annotationType);
      if (toReturn != null) {
        /*
         * First one wins. It might be desirable at some point to have a
         * variation that can return more than one instance of the annotation if
         * it is present on multiple supertypes.
         */
        break;
      }

      if (current.getSuperclass() != null) {
        // Add the superclass at the front of the list
        searchTypes.add(0, current.getSuperclass());
      }

      // Superinterfaces
      Collections.addAll(searchTypes, current.getImplementedInterfaces());
    }

    return toReturn;
  }

  @Override
  public abstract JConstructor findConstructor(JType[] paramTypes);

  @Override
  public abstract JField findField(String name);

  @Override
  public abstract JMethod findMethod(String name, JType[] paramTypes);

  @Override
  public abstract JClassType findNestedType(String typeName);

  @Override
  public abstract <T extends Annotation> T getAnnotation(
      Class<T> annotationClass);

  @Override
  public abstract Annotation[] getAnnotations();

  @Override
  public abstract JConstructor getConstructor(JType[] paramTypes)
      throws NotFoundException;

  @Override
  public abstract JConstructor[] getConstructors();

  @Override
  public abstract Annotation[] getDeclaredAnnotations();

  @Override
  public abstract JClassType getEnclosingType();

  @Override
  public abstract JClassType getErasedType();

  @Override
  public abstract JField getField(String name);

  @Override
  public abstract JField[] getFields();

  /**
   * Returns all of the superclasses and superinterfaces for a given type
   * including the type itself. The returned set maintains an internal
   * breadth-first ordering of the type, followed by its interfaces (and their
   * super-interfaces), then the supertype and its interfaces, and so on.
   */
  @Override
  public Set<JClassType> getFlattenedSupertypeHierarchy() {
    // Retuns an immutable set
    return getFlattenedSuperTypeHierarchy(this);
  }

  @Override
  public abstract JClassType[] getImplementedInterfaces();

  /**
   * Iterates over the most-derived declaration of each unique inheritable
   * method available in the type hierarchy of the specified type, including
   * those found in superclasses and superinterfaces. A method is inheritable if
   * its accessibility is <code>public</code>, <code>protected</code>, or
   * package protected.
   *
   * This method offers a convenient way for Generators to find candidate
   * methods to call from a subclass.
   *
   * @return an array of {@link JMethod} objects representing inheritable
   *         methods
   */
  @Override
  public abstract JMethod[] getInheritableMethods();

  @Override
  public abstract String getJNISignature();

  @Override
  public JType getLeafType() {
    return this;
  }

  @Override
  public abstract JMethod getMethod(String name, JType[] paramTypes)
      throws NotFoundException;

  /*
   * Returns the declared methods of this class (not any superclasses or
   * superinterfaces).
   */
  @Override
  public abstract JMethod[] getMethods();

  @Override
  public abstract String getName();

  @Override
  public abstract JClassType getNestedType(String typeName)
      throws NotFoundException;

  @Override
  public abstract JClassType[] getNestedTypes();

  @Override
  public abstract TypeOracle getOracle();

  @Override
  public abstract JMethod[] getOverloads(String name);

  /**
   * Iterates over the most-derived declaration of each unique overridable
   * method available in the type hierarchy of the specified type, including
   * those found in superclasses and superinterfaces. A method is overridable if
   * it is not <code>final</code> and its accessibility is <code>public</code>,
   * <code>protected</code>, or package protected.
   *
   * Deferred binding generators often need to generate method implementations;
   * this method offers a convenient way to find candidate methods to implement.
   *
   * Note that the behavior does not match
   * {@link Class#getMethod(String, Class[])}, which does not return the most
   * derived method in some cases.
   *
   * @return an array of {@link JMethod} objects representing overridable
   *         methods
   */
  @Override
  public abstract JMethod[] getOverridableMethods();

  @Override
  public abstract JPackage getPackage();

  @Override
  public String getParameterizedQualifiedSourceName() {
    return getQualifiedSourceName();
  }

  /**
   * TODO(scottb): remove if we can resolve param names differently.
   */
  @Override
  public abstract String getQualifiedBinaryName();

  @Override
  public abstract String getQualifiedSourceName();

  @Override
  public abstract String getSimpleSourceName();

  /**
   * Returns all subtypes for this type.  This means various things:
   * <ol>
   *   <li>Array: subtypes are those array types with the same number of dimensions as this type,
   *   and whose base element type is a subtype of this type's element type.</li>
   *   <li>Wildcards: if ? extends X, subtypes are subtypes of X.  If ? super X: no subtypes.
   *   <li>Named type parameter: subtypes are (1) those subtypes of the "base" type that are
   *   assignable to this, plus (2) the first bound of the parameter, if the bound is both
   *   assignable to this and not an interface.  The "base" is the type itself, except for
   *   delegating types, where the base is the result of <code>getBaseType()</code>.</li>
   *   <li>Real class: subtypes are those real classes that are subtypes of this class in the
   *   class hierarchy.  Interfaces aren't subtypes of {@code Object}.</li>
   *   <li>Parameterized type: subtypes are those subtypes of the generic type of this type which
   *   can be parameterized with the right types so that they have this type as a supertype.
   *   The subtypes are parameterized as described, with minimal parameterization.</li>
   *   <li>Raw type: subtypes are the subtypes of the generic type of this type, as raw types if
   *   they themselves are generic.</li>
   * </ol>
   */
  @Override
  public abstract JClassType[] getSubtypes();

  @Override
  public abstract JClassType getSuperclass();

  /**
   * All types use identity for comparison.
   */
  @Override
  public final int hashCode() {
    return super.hashCode();
  }

  @Override
  public abstract boolean isAbstract();

  /**
   * Returns this instance if it is a annotation or <code>null</code> if it is
   * not.
   *
   * @return this instance if it is a annotation or <code>null</code> if it is
   *         not
   */
  @Override
  public JAnnotationType isAnnotation() {
    return null;
  }

  @Override
  public abstract boolean isAnnotationPresent(
      Class<? extends Annotation> annotationClass);

  @Override
  public abstract JArrayType isArray();

  /**
   * Returns <code>true</code> if this {@link JClassType} is assignable from the
   * specified {@link JClassType} parameter.
   *
   * @param possibleSubtype possible subtype of this {@link JClassType}
   * @return <code>true</code> if this {@link JClassType} is assignable from the
   *         specified {@link JClassType} parameter
   *
   * @throws NullPointerException if <code>possibleSubtype</code> is
   *           <code>null</code>
   */
  @Override
  public boolean isAssignableFrom(
      com.google.gwt.core.ext.typeinfo.JClassType possibleSubtype) {
    if (possibleSubtype == null) {
      throw new NullPointerException("possibleSubtype");
    }

    return new AssignabilityChecker().isAssignable((JClassType) possibleSubtype, this);
  }

  /**
   * Returns <code>true</code> if this {@link JClassType} is assignable to the
   * specified {@link JClassType} parameter.
   *
   * @param possibleSupertype possible supertype of this {@link JClassType}
   * @return <code>true</code> if this {@link JClassType} is assignable to the
   *         specified {@link JClassType} parameter
   *
   * @throws NullPointerException if <code>possibleSupertype</code> is
   *           <code>null</code>
   */
  @Override
  public boolean isAssignableTo(
      com.google.gwt.core.ext.typeinfo.JClassType possibleSupertype) {
    if (possibleSupertype == null) {
      throw new NullPointerException("possibleSupertype");
    }

    return new AssignabilityChecker().isAssignable(this, (JClassType) possibleSupertype);
  }

  @Override
  public abstract JClassType isClass();

  @Override
  public JClassType isClassOrInterface() {
    JClassType type = isClass();
    if (type != null) {
      return type;
    }
    return isInterface();
  }

  /**
   * Determines if the class can be constructed using a simple <code>new</code>
   * operation. Specifically, the class must
   * <ul>
   * <li>be a class rather than an interface,</li>
   * <li>have either no constructors or a parameterless constructor, and</li>
   * <li>be a top-level class or a static nested class.</li>
   * </ul>
   *
   * @return <code>true</code> if the type is default instantiable, or
   *         <code>false</code> otherwise
   */
  @Override
  public abstract boolean isDefaultInstantiable();

  /**
   * Returns true if the type may be enhanced on the server to contain extra
   * fields that are unknown to client code.
   *
   * @return <code>true</code> if the type might be enhanced on the server
   */
  @Override
  public final boolean isEnhanced() {
    return isEnhanced;
  }

  /**
   * Returns this instance if it is an enumeration or <code>null</code> if it is
   * not.
   *
   * @return this instance if it is an enumeration or <code>null</code> if it is
   *         not
   */
  @Override
  public abstract JEnumType isEnum();

  @Override
  public abstract boolean isFinal();

  @Override
  public abstract JGenericType isGenericType();

  @Override
  public abstract JClassType isInterface();

  /**
   * Tests if this type is contained within another type.
   *
   * @return true if this type has an enclosing type, false if this type is a
   *         top-level type
   */
  @Override
  public abstract boolean isMemberType();

  @Override
  public abstract JParameterizedType isParameterized();

  @Override
  public abstract JPrimitiveType isPrimitive();

  @Override
  public abstract boolean isPrivate();

  @Override
  public abstract boolean isProtected();

  @Override
  public abstract boolean isPublic();

  @Override
  public boolean isPackageProtected() {
    return !isPrivate() && !isPublic() && !isProtected();
  }

  // TODO: Rename this to isRaw
  @Override
  public abstract JRawType isRawType();

  @Override
  public abstract boolean isStatic();

  @Override
  public JTypeParameter isTypeParameter() {
    return null;
  }

  @Override
  public abstract JWildcardType isWildcard();

  /**
   * Indicates that the type may be enhanced on the server to contain extra
   * fields that are unknown to client code.
   *
   * TODO(rice): find a better way to do this.
   */
  @Override
  public void setEnhanced() {
    this.isEnhanced = true;
  }

  @Override
  public String toString() {
    return this.getQualifiedSourceName();
  }

  protected abstract void acceptSubtype(JClassType me);

  protected abstract void getInheritableMethodsOnSuperclassesAndThisClass(
      Map<String, JMethod> methodsBySignature);

  /**
   * Gets the methods declared in interfaces that this type extends. If this
   * type is a class, its own methods are not added. If this type is an
   * interface, its own methods are added. Used internally by
   * {@link #getOverridableMethods()}.
   *
   * @param methodsBySignature
   */
  protected abstract void getInheritableMethodsOnSuperinterfacesAndMaybeThisInterface(
      Map<String, JMethod> methodsBySignature);

  protected abstract int getModifierBits();

  protected JMaybeParameterizedType isMaybeParameterizedType() {
    return null;
  }

  /**
   * Tells this type's superclasses and superinterfaces about it.
   */
  protected abstract void notifySuperTypesOf(JClassType me);

  protected abstract void removeSubtype(JClassType me);

  abstract void addConstructor(JConstructor ctor);

  abstract void addField(JField field);

  abstract void addImplementedInterface(JClassType intf);

  abstract void addMethod(JMethod method);

  abstract void addModifierBits(int bits);

  abstract void addNestedType(JClassType type);

  abstract JClassType findNestedTypeImpl(String[] typeName, int index);

  /**
   * Returns either the substitution of this type based on the parameterized
   * type or this instance.
   *
   * @param parameterizedType
   * @return either the substitution of this type based on the parameterized
   *         type or this instance
   */
  abstract JClassType getSubstitutedType(JParameterizedType parameterizedType);

  abstract void notifySuperTypes();

  /**
   * Removes references to this instance from all of its super types.
   */
  abstract void removeFromSupertypes();

  abstract void setSuperclass(JClassType type);
}
