/*
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.cast.ipa.cha;

import com.ibm.wala.classLoader.ClassLoaderFactory;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.IField;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.Language;
import com.ibm.wala.core.util.strings.Atom;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.Selector;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.ComposedIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * This class hierarchy represents a family of disjoint class hierarchies, one for each of a
 * selection of languages. The implementation creates a separate ClassHierarchy object for each
 * language, and this overall IClassHierarchy implementation delegates to the appropriate language
 * class hierarchy based on the language associated with the class loader of the given TypeReference
 * or IClass object.
 *
 * <p>Note that, because of this delegating structure and representation of multiple languages, the
 * getRootClass API call does not make sense for this hierarchy. In general, any code that wants to
 * use multiple language must deal with the fact that there is no longer a single root type. Each
 * individual language is still expected to have a root type, however.
 *
 * @author Julian Dolby (dolby@us.ibm.com)
 */
public class CrossLanguageClassHierarchy implements IClassHierarchy {

  private final ClassLoaderFactory loaderFactory;

  private final AnalysisScope analysisScope;

  private final Map<Atom, IClassHierarchy> hierarchies;

  @Override
  public ClassLoaderFactory getFactory() {
    return loaderFactory;
  }

  @Override
  public AnalysisScope getScope() {
    return analysisScope;
  }

  private IClassHierarchy getHierarchy(Atom language) {
    return hierarchies.get(language);
  }

  private IClassHierarchy getHierarchy(IClassLoader loader) {
    return getHierarchy(loader.getLanguage().getName());
  }

  private IClassHierarchy getHierarchy(ClassLoaderReference loader) {
    return getHierarchy(loader.getLanguage());
  }

  private IClassHierarchy getHierarchy(IClass cls) {
    return getHierarchy(cls.getClassLoader());
  }

  private IClassHierarchy getHierarchy(TypeReference cls) {
    return getHierarchy(cls.getClassLoader());
  }

  private IClassHierarchy getHierarchy(MethodReference ref) {
    return getHierarchy(ref.getDeclaringClass());
  }

  private IClassHierarchy getHierarchy(FieldReference ref) {
    return getHierarchy(ref.getDeclaringClass());
  }

  @Override
  public IClassLoader[] getLoaders() {
    Set<IClassLoader> loaders = HashSetFactory.make();
    for (ClassLoaderReference loaderReference : analysisScope.getLoaders()) {
      loaders.add(getLoader(loaderReference));
    }

    return loaders.toArray(new IClassLoader[0]);
  }

  @Override
  public IClassLoader getLoader(ClassLoaderReference loaderRef) {
    return getHierarchy(loaderRef).getLoader(loaderRef);
  }

  @Override
  public boolean addClass(IClass klass) {
    return getHierarchy(klass).addClass(klass);
  }

  @Override
  public int getNumberOfClasses() {
    int total = 0;
    for (ClassLoaderReference loaderReference : analysisScope.getLoaders()) {
      total += getLoader(loaderReference).getNumberOfClasses();
    }

    return total;
  }

  @Override
  public boolean isRootClass(IClass c) {
    return getHierarchy(c).isRootClass(c);
  }

  @Override
  public IClass getRootClass() {
    Assertions.UNREACHABLE();
    return null;
  }

  @Override
  public int getNumber(IClass c) {
    return getHierarchy(c).getNumber(c);
  }

  @Override
  public Set<IMethod> getPossibleTargets(MethodReference ref) {
    return getHierarchy(ref).getPossibleTargets(ref);
  }

  @Override
  public Set<IMethod> getPossibleTargets(IClass receiverClass, MethodReference ref) {
    return getHierarchy(ref).getPossibleTargets(receiverClass, ref);
  }

  @Override
  public IMethod resolveMethod(MethodReference m) {
    return getHierarchy(m).resolveMethod(m);
  }

  @Override
  public IField resolveField(FieldReference f) {
    return getHierarchy(f).resolveField(f);
  }

  @Override
  public IField resolveField(IClass klass, FieldReference f) {
    return getHierarchy(klass).resolveField(klass, f);
  }

  @Override
  public IMethod resolveMethod(IClass receiver, Selector selector) {
    return getHierarchy(receiver).resolveMethod(receiver, selector);
  }

  @Override
  public IClass lookupClass(TypeReference A) {
    return getHierarchy(A).lookupClass(A);
  }

  //  public boolean isSyntheticClass(IClass c) {
  //    return getHierarchy(c).isSyntheticClass(c);
  //  }

  @Override
  public boolean isInterface(TypeReference type) {
    return getHierarchy(type).isInterface(type);
  }

  @Override
  public IClass getLeastCommonSuperclass(IClass A, IClass B) {
    return getHierarchy(A).getLeastCommonSuperclass(A, B);
  }

  @Override
  public TypeReference getLeastCommonSuperclass(TypeReference A, TypeReference B) {
    return getHierarchy(A).getLeastCommonSuperclass(A, B);
  }

  @Override
  public boolean isSubclassOf(IClass c, IClass T) {
    return getHierarchy(c).isSubclassOf(c, T);
  }

  @Override
  public boolean implementsInterface(IClass c, IClass i) {
    return getHierarchy(c).implementsInterface(c, i);
  }

  @Override
  public Collection<IClass> computeSubClasses(TypeReference type) {
    return getHierarchy(type).computeSubClasses(type);
  }

  @Override
  public Collection<TypeReference> getJavaLangRuntimeExceptionTypes() {
    return getHierarchy(TypeReference.JavaLangRuntimeException).getJavaLangErrorTypes();
  }

  @Override
  public Collection<TypeReference> getJavaLangErrorTypes() {
    return getHierarchy(TypeReference.JavaLangError).getJavaLangErrorTypes();
  }

  @Override
  public Set<IClass> getImplementors(TypeReference type) {
    return getHierarchy(type).getImplementors(type);
  }

  @Override
  public int getNumberOfImmediateSubclasses(IClass klass) {
    return getHierarchy(klass).getNumberOfImmediateSubclasses(klass);
  }

  @Override
  public Collection<IClass> getImmediateSubclasses(IClass klass) {
    return getHierarchy(klass).getImmediateSubclasses(klass);
  }

  @Override
  public boolean isAssignableFrom(IClass c1, IClass c2) {
    return getHierarchy(c1).isAssignableFrom(c1, c2);
  }

  @Override
  public Iterator<IClass> iterator() {
    return new ComposedIterator<>(analysisScope.getLoaders().iterator()) {
      @Override
      public Iterator<IClass> makeInner(ClassLoaderReference o) {
        IClassLoader ldr = getLoader(o);
        return ldr.iterateAllClasses();
      }
    };
  }

  private CrossLanguageClassHierarchy(
      AnalysisScope scope, ClassLoaderFactory factory, Map<Atom, IClassHierarchy> hierarchies) {
    this.analysisScope = scope;
    this.loaderFactory = factory;
    this.hierarchies = hierarchies;
  }

  public static CrossLanguageClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory)
      throws ClassHierarchyException {
    Set<Language> languages = scope.getBaseLanguages();
    Map<Atom, IClassHierarchy> hierarchies = HashMapFactory.make();
    for (Language L : languages) {
      Set<Language> ll = HashSetFactory.make(L.getDerivedLanguages());
      ll.add(L);
      hierarchies.put(L.getName(), ClassHierarchyFactory.make(scope, factory, ll));
    }

    return new CrossLanguageClassHierarchy(scope, factory, hierarchies);
  }

  /* BEGIN Custom change: unresolved classes */
  @Override
  public Set<TypeReference> getUnresolvedClasses() {
    return HashSetFactory.make();
  }
  /* END Custom change: unresolved classes */
}
