/*
 * 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.ipa.summaries;

import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.Language;
import com.ibm.wala.classLoader.Module;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.config.SetOfClasses;
import com.ibm.wala.util.strings.Atom;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * This class represents class loaders that introduce classes that do not exist in the actual
 * application being analyzed. They may be abstract summaries of unanalyzed library code, wrappers
 * that encode J2EE specialized behavior or other invented classes.
 *
 * <p>The intention is that there be (at most) one such classloader in a given class hierarchy, and
 * that it be referenced using the "Synthetic" classloader reference. Furthermore, it is required
 * that this synthetic loader be a child loader of the Primordial, Extension and Application
 * loaders.
 *
 * <p>This special classloader has some interactions with the hierarchy for, while the classes it
 * loads are normal-seeming IClass objects, unlike the other loaders, its set of classes is not
 * fixed, causing special cases in code that caches hierarchy data. Also note that this causes the
 * getNumberfClasses and iterateAllClasses methods to behave differently for those of other
 * classloaders.
 *
 * <p>Code that wants to introduce synthetic classes uses the registerClass method, giving it an
 * Atom which is the class name, and an IClass which is the class to load. Since the synthetic
 * loader must be a child of the others, it would be very bad to use an existing name for a new
 * synthetic class.
 *
 * <p>Class lookup works just as for any other classloader.
 *
 * @author Julian Dolby (dolby@us.ibm.com)
 */
public class BypassSyntheticClassLoader implements IClassLoader {

  private final ClassLoaderReference me;

  private final IClassLoader parent;

  private final IClassHierarchy cha;

  private final HashMap<TypeName, IClass> syntheticClasses = HashMapFactory.make();

  /**
   * Don't change my signature! ClassLoaderFactoryImpl calls me by reflection! yuck.
   *
   * @param me the name of this class loader
   * @param parent its parent
   * @param exclusions classes to ignore
   * @param cha governing class hierarchy
   */
  public BypassSyntheticClassLoader(
      ClassLoaderReference me, IClassLoader parent, SetOfClasses exclusions, IClassHierarchy cha) {
    if (cha == null) {
      throw new IllegalArgumentException("null cha");
    }
    this.me = me;
    this.cha = cha;
    this.parent = parent;
  }

  @Override
  public String toString() {
    return me.getName().toString();
  }

  @Override
  public IClass lookupClass(TypeName className) {
    IClass pc = parent.lookupClass(className);
    if (pc == null) {
      IClass c = syntheticClasses.get(className);
      return c;
    } else {
      return pc;
    }
  }

  /** Register the existence of a new synthetic class */
  public void registerClass(TypeName className, IClass theClass) {
    cha.addClass(theClass);
    syntheticClasses.put(className, theClass);
  }

  /** Return the ClassLoaderReference for this class loader. */
  @Override
  public ClassLoaderReference getReference() {
    return me;
  }

  /** @return an Iterator of all classes loaded by this loader */
  @Override
  public Iterator<IClass> iterateAllClasses() {
    return syntheticClasses.values().iterator();
  }

  /** @return the number of classes in scope to be loaded by this loader */
  @Override
  public int getNumberOfClasses() {
    return syntheticClasses.size();
  }

  /** @return the unique name that identifies this class loader. */
  @Override
  public Atom getName() {
    return me.getName();
  }

  /**
   * @return the unique name that identifies the programming language from which this class loader
   *     loads code.
   */
  @Override
  public Language getLanguage() {
    return parent.getLanguage();
  }

  /*
   * @see com.ibm.wala.classLoader.IClassLoader#getNumberOfMethods()
   */
  @Override
  public int getNumberOfMethods() {
    // TODO Auto-generated method stub
    return 0;
  }

  /*
   * @see com.ibm.wala.classLoader.IClassLoader#getSourceFileName(com.ibm.wala.classLoader.IClass)
   */
  @Override
  public String getSourceFileName(IClass klass) {
    return null;
  }

  @Override
  public IClassLoader getParent() {
    return parent;
  }

  @Override
  public void init(List<Module> modules) throws IOException {}

  /*
   * @see com.ibm.wala.classLoader.IClassLoader#removeAll(java.util.Collection)
   */
  @Override
  public void removeAll(Collection<IClass> toRemove) {
    if (toRemove == null) {
      throw new IllegalArgumentException("toRemove is null");
    }
    toRemove.stream().map(IClass::getName).forEach(syntheticClasses::remove);
  }

  @Override
  public Reader getSource(IClass klass) {
    return null;
  }

  @Override
  public SSAInstructionFactory getInstructionFactory() {
    return getLanguage().instructionFactory();
  }

  @Override
  public Reader getSource(IMethod method, int offset) {
    return null;
  }

  @Override
  public String getSourceFileName(IMethod method, int offset) {
    return null;
  }
}
