/*
 * 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
 */
/*
 * Created on Aug 22, 2005
 */
package com.ibm.wala.cast.java.loader;

import com.ibm.wala.cast.ir.ssa.AssignInstruction;
import com.ibm.wala.cast.ir.ssa.AstAssertInstruction;
import com.ibm.wala.cast.ir.ssa.AstEchoInstruction;
import com.ibm.wala.cast.ir.ssa.AstGlobalRead;
import com.ibm.wala.cast.ir.ssa.AstGlobalWrite;
import com.ibm.wala.cast.ir.ssa.AstIsDefinedInstruction;
import com.ibm.wala.cast.ir.ssa.AstLexicalAccess.Access;
import com.ibm.wala.cast.ir.ssa.AstLexicalRead;
import com.ibm.wala.cast.ir.ssa.AstLexicalWrite;
import com.ibm.wala.cast.ir.ssa.AstPropertyRead;
import com.ibm.wala.cast.ir.ssa.AstPropertyWrite;
import com.ibm.wala.cast.ir.ssa.AstYieldInstruction;
import com.ibm.wala.cast.ir.translator.AstTranslator;
import com.ibm.wala.cast.ir.translator.AstTranslator.AstLexicalInformation;
import com.ibm.wala.cast.java.ssa.AstJavaInstructionFactory;
import com.ibm.wala.cast.java.ssa.AstJavaInvokeInstruction;
import com.ibm.wala.cast.java.ssa.AstJavaNewEnclosingInstruction;
import com.ibm.wala.cast.java.ssa.EnclosingObjectReference;
import com.ibm.wala.cast.java.translator.SourceModuleTranslator;
import com.ibm.wala.cast.loader.AstClass;
import com.ibm.wala.cast.loader.AstField;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.loader.AstMethod.DebuggingInformation;
import com.ibm.wala.cast.tree.CAstAnnotation;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstQualifier;
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
import com.ibm.wala.cast.tree.CAstType;
import com.ibm.wala.cast.tree.CAstType.Function;
import com.ibm.wala.cfg.AbstractCFG;
import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.ClassLoaderImpl;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory;
import com.ibm.wala.classLoader.Language;
import com.ibm.wala.classLoader.Module;
import com.ibm.wala.classLoader.ModuleEntry;
import com.ibm.wala.classLoader.NewSiteReference;
import com.ibm.wala.core.util.strings.Atom;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.shrike.shrikeCT.AnnotationsReader.ConstantElementValue;
import com.ibm.wala.shrike.shrikeCT.AnnotationsReader.ElementValue;
import com.ibm.wala.shrike.shrikeCT.ClassConstants;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.Descriptor;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.Selector;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.types.annotations.Annotation;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** A {@link ClassLoaderImpl} that processes source file entities in the compile-time classpath. */
public abstract class JavaSourceLoaderImpl extends ClassLoaderImpl {
  public Map<CAstEntity, IClass> fTypeMap = HashMapFactory.make();
  /* BEGIN Custom change: Common superclass is optional */
  private final boolean
      existsCommonSuperclass; // extension to deal with X10 that has no common superclass
  /* END Custom change: Common superclass is optional */

  /**
   * WALA representation of a Java class residing in a source file
   *
   * @author rfuhrer
   */
  public class JavaClass extends AstClass {
    protected final IClass enclosingClass;

    protected final Collection<TypeName> superTypeNames;

    private final Collection<Annotation> annotations;

    public JavaClass(
        String typeName,
        Collection<TypeName> superTypeNames,
        CAstSourcePositionMap.Position position,
        Collection<CAstQualifier> qualifiers,
        JavaSourceLoaderImpl loader,
        IClass enclosingClass,
        Collection<Annotation> annotations) {
      super(
          position,
          TypeName.string2TypeName(typeName),
          loader,
          (short) mapToInt(qualifiers),
          new HashMap<>(),
          new HashMap<>());
      this.superTypeNames = superTypeNames;
      this.enclosingClass = enclosingClass;
      this.annotations = annotations;
    }

    @Override
    public Collection<Annotation> getAnnotations() {
      return annotations;
    }

    @Override
    public IClassHierarchy getClassHierarchy() {
      return cha;
    }

    @Override
    public IClass getSuperclass() {
      boolean excludedSupertype = false;
      for (TypeName name : superTypeNames) {
        IClass domoType = lookupClass(name);
        if (domoType != null && !domoType.isInterface()) {
          return domoType;
        }
        if (domoType == null
            && getClassHierarchy()
                .getScope()
                .getExclusions()
                .contains(name.toString().substring(1))) {
          excludedSupertype = true;
        }
      }

      // The following test allows the root class to reside in source; without
      // it, the assertion requires all classes represented by a JavaClass to
      // have a superclass.
      /* BEGIN Custom change: Common superclass is optional */
      // Is no longer true in new X10 - no common object super class
      if (existsCommonSuperclass
          && !getName().equals(JavaSourceLoaderImpl.this.getLanguage().getRootType().getName())
          && !excludedSupertype) {
        /* END Custom change: Common superclass is optional */
        Assertions.UNREACHABLE("Cannot find super class for " + this + " in " + superTypeNames);
      }

      if (excludedSupertype) {
        System.err.println(
            "Not tracking calls through excluded superclass of "
                + getName()
                + " extends "
                + superTypeNames);
      }

      return null;
    }

    @Override
    public Collection<IClass> getDirectInterfaces() {
      List<IClass> result = new ArrayList<>();
      for (TypeName name : superTypeNames) {
        IClass domoType = lookupClass(name);
        if (domoType != null && domoType.isInterface()) {
          result.add(domoType);
        }
        if (domoType == null
            && !getClassHierarchy()
                .getScope()
                .getExclusions()
                .contains(name.toString().substring(1))) {
          assert false : "Failed to find non-excluded interface: " + name;
        }
      }

      return result;
    }

    protected void addMethod(
        CAstEntity methodEntity,
        IClass owner,
        AbstractCFG<?, ?> cfg,
        SymbolTable symtab,
        boolean hasCatchBlock,
        Map<IBasicBlock<SSAInstruction>, TypeReference[]> caughtTypes,
        boolean hasMonitorOp,
        AstLexicalInformation lexicalInfo,
        DebuggingInformation debugInfo) {
      declaredMethods.put(
          Util.methodEntityToSelector(methodEntity),
          new ConcreteJavaMethod(
              methodEntity,
              owner,
              cfg,
              symtab,
              hasCatchBlock,
              caughtTypes,
              hasMonitorOp,
              lexicalInfo,
              debugInfo));
    }

    protected void addMethod(CAstEntity methodEntity, IClass owner) {
      declaredMethods.put(
          Util.methodEntityToSelector(methodEntity), new AbstractJavaMethod(methodEntity, owner));
    }

    protected void addField(CAstEntity fieldEntity) {
      declaredFields.put(
          Util.fieldEntityToAtom(fieldEntity),
          new JavaField(
              fieldEntity,
              JavaSourceLoaderImpl.this,
              this,
              JavaSourceLoaderImpl.this.getAnnotations(fieldEntity)));
    }

    public IClass getEnclosingClass() {
      return enclosingClass;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder("<src-class: ");
      sb.append(getName().toString());
      if (enclosingClass != null) {
        sb.append(" (within ").append(enclosingClass.getName()).append(')');
      }
      if (annotations != null && !annotations.isEmpty()) {
        for (Annotation a : annotations) {
          sb.append('[').append(a.getType().getName().getClassName()).append(']');
        }
      }
      return sb.toString();
    }
  }

  protected Collection<Annotation> getAnnotations(CAstEntity e) {
    Collection<CAstAnnotation> annotations = e.getAnnotations();
    if (annotations == null || annotations.isEmpty()) {
      return null;
    } else {
      Collection<Annotation> result = HashSetFactory.make();
      for (CAstAnnotation ca : annotations) {
        TypeName walaTypeName = toWALATypeName(ca.getType());
        TypeReference ref = TypeReference.findOrCreate(getReference(), walaTypeName);
        if (ca.getArguments() == null || ca.getArguments().isEmpty()) {
          result.add(Annotation.make(ref));
        } else {
          Map<String, ElementValue> args = HashMapFactory.make();
          for (Map.Entry<String, Object> a : ca.getArguments().entrySet()) {
            args.put(a.getKey(), new ConstantElementValue(a.getValue()));
          }
          result.add(Annotation.makeWithNamed(ref, args));
        }
      }
      return result;
    }
  }

  /**
   * WALA representation of a field on a Java type that resides in a source file
   *
   * @author rfuhrer
   */
  protected static class JavaField extends AstField {
    protected JavaField(
        CAstEntity fieldEntity,
        IClassLoader loader,
        IClass declaringClass,
        Collection<Annotation> annotations) {
      super(
          FieldReference.findOrCreate(
              declaringClass.getReference(),
              Atom.findOrCreateUnicodeAtom(fieldEntity.getName()),
              TypeReference.findOrCreate(
                  loader.getReference(),
                  TypeName.string2TypeName(fieldEntity.getType().getName()))),
          fieldEntity.getQualifiers(),
          declaringClass,
          declaringClass.getClassHierarchy(),
          annotations);
    }
  }

  /**
   * Generic DOMO representation of a method on a Java type that resides in a source file
   *
   * @author rfuhrer
   */
  protected abstract class JavaEntityMethod extends AstMethod {
    private final TypeReference[] parameterTypes;

    private final TypeReference[] exceptionTypes;

    public JavaEntityMethod(
        CAstEntity methodEntity,
        IClass owner,
        AbstractCFG<?, ?> cfg,
        SymbolTable symtab,
        boolean hasCatchBlock,
        Map<IBasicBlock<SSAInstruction>, TypeReference[]> caughtTypes,
        boolean hasMonitorOp,
        AstLexicalInformation lexicalInfo,
        DebuggingInformation debugInfo) {
      super(
          owner,
          methodEntity.getQualifiers(),
          cfg,
          symtab,
          MethodReference.findOrCreate(
              owner.getReference(), Util.methodEntityToSelector(methodEntity)),
          hasCatchBlock,
          caughtTypes,
          hasMonitorOp,
          lexicalInfo,
          debugInfo,
          JavaSourceLoaderImpl.this.getAnnotations(methodEntity));
      this.parameterTypes = computeParameterTypes(methodEntity);
      this.exceptionTypes = computeExceptionTypes(methodEntity);
    }

    public JavaEntityMethod(CAstEntity methodEntity, IClass owner) {
      super(
          owner,
          methodEntity.getQualifiers(),
          MethodReference.findOrCreate(
              owner.getReference(), Util.methodEntityToSelector(methodEntity)),
          JavaSourceLoaderImpl.this.getAnnotations(methodEntity));
      this.parameterTypes = computeParameterTypes(methodEntity);
      this.exceptionTypes = computeExceptionTypes(methodEntity);
    }

    public int getMaxLocals() {
      Assertions.UNREACHABLE("AbstractJavaMethod.getMaxLocals() called");
      return 0;
    }

    public int getMaxStackHeight() {
      Assertions.UNREACHABLE("AbstractJavaMethod.getMaxStackHeight() called");
      return 0;
    }

    @Override
    public TypeReference getParameterType(int i) {
      return parameterTypes[i];
    }

    protected TypeReference[] computeParameterTypes(CAstEntity methodEntity) {
      TypeReference[] types;
      CAstType.Function type = (Function) methodEntity.getType();
      int argCount = type.getArgumentTypes().size();
      if (isStatic()) {
        types = new TypeReference[argCount];
        for (int i = 0; i < argCount; i++) {
          types[i] =
              TypeReference.findOrCreate(
                  JavaSourceLoaderImpl.this.getReference(),
                  type.getArgumentTypes().get(i).getName());
        }
      } else {
        types = new TypeReference[argCount + 1];
        types[0] = cls.getReference();
        for (int i = 0; i < argCount; i++) {
          types[i + 1] =
              TypeReference.findOrCreate(
                  JavaSourceLoaderImpl.this.getReference(),
                  type.getArgumentTypes().get(i).getName());
        }
      }

      return types;
    }

    @Override
    public TypeReference[] getDeclaredExceptions() {
      return exceptionTypes;
    }

    protected TypeReference[] computeExceptionTypes(CAstEntity methodEntity) {
      CAstType.Function fType = (Function) methodEntity.getType();
      Collection<CAstType> exceptionTypes = fType.getExceptionTypes();

      TypeReference[] result = new TypeReference[exceptionTypes.size()];
      int i = 0;
      for (CAstType type : exceptionTypes) {
        result[i] =
            TypeReference.findOrCreate(JavaSourceLoaderImpl.this.getReference(), type.getName());
        ++i;
      }

      return result;
    }

    @Override
    public String toString() {
      return "<src-method: " + this.getReference() + '>';
    }
  }

  /**
   * DOMO representation of an abstract (body-less) method on a Java type that resides in a source
   * file
   *
   * @author rfuhrer
   */
  protected class AbstractJavaMethod extends JavaEntityMethod {
    public AbstractJavaMethod(CAstEntity methodEntity, IClass owner) {
      super(methodEntity, owner);
    }

    @Override
    public String getLocalVariableName(int bcIndex, int localNumber) {
      Assertions.UNREACHABLE("AbstractJavaMethod.getLocalVariableName() called");
      return null;
    }

    @Override
    public boolean hasLocalVariableTable() {
      Assertions.UNREACHABLE("AbstractJavaMethod.hasLocalVariableTable() called");
      return false;
    }

    @Override
    public LexicalParent[] getParents() {
      return new LexicalParent[0];
    }

    @Override
    public IClassHierarchy getClassHierarchy() {
      return cha;
    }
  }

  /**
   * DOMO representation of a concrete method (which has a body) on a Java type that resides in a
   * source file
   *
   * @author rfuhrer
   */
  public class ConcreteJavaMethod extends JavaEntityMethod {
    public ConcreteJavaMethod(
        CAstEntity methodEntity,
        IClass owner,
        AbstractCFG<?, ?> cfg,
        SymbolTable symtab,
        boolean hasCatchBlock,
        Map<IBasicBlock<SSAInstruction>, TypeReference[]> caughtTypes,
        boolean hasMonitorOp,
        AstLexicalInformation lexicalInfo,
        DebuggingInformation debugInfo) {
      super(
          methodEntity,
          owner,
          cfg,
          symtab,
          hasCatchBlock,
          caughtTypes,
          hasMonitorOp,
          lexicalInfo,
          debugInfo);
    }

    @Override
    public IClassHierarchy getClassHierarchy() {
      return cha;
    }

    @Override
    public String getLocalVariableName(int bcIndex, int localNumber) {
      return null;
    }

    @Override
    public boolean hasLocalVariableTable() {
      return false;
    }

    @Override
    public LexicalParent[] getParents() {
      if (AstTranslator.DEBUG_LEXICAL) {
        System.err.println(("resolving parents of " + this));
      }

      if (lexicalInfo() == null) {
        if (AstTranslator.DEBUG_LEXICAL) System.err.println("no info");
        return new LexicalParent[0];
      }

      final String[] parents = lexicalInfo().getScopingParents();

      if (parents == null) {
        if (AstTranslator.DEBUG_LEXICAL) System.err.println("no parents");
        return new LexicalParent[0];
      }

      LexicalParent result[] = new LexicalParent[parents.length];

      for (int i = 0; i < parents.length; i++) {
        int lastLeftParen = parents[i].lastIndexOf('(');
        int lastQ = parents[i].lastIndexOf('/', lastLeftParen);
        String typeName = parents[i].substring(0, lastQ);
        final IClass cls = lookupClass(TypeName.string2TypeName(typeName));

        String sig = parents[i].substring(lastQ);
        int nameEnd = sig.indexOf('(');
        String nameStr = sig.substring(1, nameEnd);
        Atom name = Atom.findOrCreateUnicodeAtom(nameStr);

        String descStr = sig.substring(nameEnd);
        Descriptor desc = Descriptor.findOrCreateUTF8(Language.JAVA, descStr);

        final Selector sel = new Selector(name, desc);

        if (AstTranslator.DEBUG_LEXICAL)
          System.err.println(("get " + typeName + ", " + nameStr + ", " + descStr));

        final int hack = i;
        result[i] =
            new LexicalParent() {
              @Override
              public String getName() {
                return parents[hack];
              }

              @Override
              public AstMethod getMethod() {
                return (AstMethod) cls.getMethod(sel);
              }
            };

        if (AstTranslator.DEBUG_LEXICAL)
          System.err.println(("parent " + result[i].getName() + " is " + result[i].getMethod()));
      }

      return result;
    }
  }

  public static int mapToInt(Collection<CAstQualifier> qualifiers) {
    int result = 0;
    for (CAstQualifier q : qualifiers) {
      if (q == CAstQualifier.PUBLIC) result |= ClassConstants.ACC_PUBLIC;
      if (q == CAstQualifier.PROTECTED) result |= ClassConstants.ACC_PROTECTED;
      if (q == CAstQualifier.PRIVATE) result |= ClassConstants.ACC_PRIVATE;
      if (q == CAstQualifier.STATIC) result |= ClassConstants.ACC_STATIC;
      if (q == CAstQualifier.FINAL) result |= ClassConstants.ACC_FINAL;
      if (q == CAstQualifier.SYNCHRONIZED) result |= ClassConstants.ACC_SYNCHRONIZED;
      if (q == CAstQualifier.TRANSIENT) result |= ClassConstants.ACC_TRANSIENT;
      if (q == CAstQualifier.NATIVE) result |= ClassConstants.ACC_NATIVE;
      if (q == CAstQualifier.INTERFACE) result |= ClassConstants.ACC_INTERFACE;
      if (q == CAstQualifier.ABSTRACT) result |= ClassConstants.ACC_ABSTRACT;
      if (q == CAstQualifier.VOLATILE) result |= ClassConstants.ACC_VOLATILE;
      if (q == CAstQualifier.STRICTFP) result |= ClassConstants.ACC_STRICT;
    }
    return result;
  }

  /* BEGIN Custom change: Common superclass is optional */
  public JavaSourceLoaderImpl(
      boolean existsCommonSuperClass,
      ClassLoaderReference loaderRef,
      IClassLoader parent,
      IClassHierarchy cha) {
    super(
        loaderRef,
        cha.getScope().getArrayClassLoader(),
        parent,
        cha.getScope().getExclusions(),
        cha);
    this.existsCommonSuperclass = existsCommonSuperClass;
  }

  public JavaSourceLoaderImpl(
      ClassLoaderReference loaderRef, IClassLoader parent, IClassHierarchy cha) {
    // standard case: we have a common super class
    this(true, loaderRef, parent, cha);
  }
  /* END Custom change: Common superclass is optional */
  public IClassHierarchy getClassHierarchy() {
    return cha;
  }

  @Override
  protected void loadAllSources(Set<ModuleEntry> modules) {
    getTranslator().loadAllSources(modules);
  }

  protected abstract SourceModuleTranslator getTranslator();
  /* BEGIN Custom change: Optional deletion of fTypeMap */
  public static volatile boolean deleteTypeMapAfterInit = true;
  /* END Custom change: Optional deletion of fTypeMap */
  @Override
  public void init(List<Module> modules) throws IOException {
    super.init(modules);
    /* BEGIN Custom change: Optional deletion of fTypeMap */
    if (deleteTypeMapAfterInit) {
      fTypeMap = null;
    }
    /* END Custom change: Optional deletion of fTypeMap */
  }

  public void defineFunction(
      CAstEntity n,
      IClass owner,
      AbstractCFG<?, ?> cfg,
      SymbolTable symtab,
      boolean hasCatchBlock,
      Map<IBasicBlock<SSAInstruction>, TypeReference[]> caughtTypes,
      boolean hasMonitorOp,
      AstLexicalInformation lexicalInfo,
      DebuggingInformation debugInfo) {
    ((JavaClass) owner)
        .addMethod(
            n,
            owner,
            cfg,
            symtab,
            hasCatchBlock,
            caughtTypes,
            hasMonitorOp,
            lexicalInfo,
            debugInfo);
  }

  public void defineAbstractFunction(CAstEntity n, IClass owner) {
    ((JavaClass) owner).addMethod(n, owner);
  }

  public void defineField(CAstEntity n, IClass owner) {
    ((JavaClass) owner).addField(n);
  }

  protected static TypeName toWALATypeName(CAstType type) {
    return TypeName.string2TypeName(type.getName());
  }

  public IClass defineType(CAstEntity type, String typeName, CAstEntity owner) {
    Collection<TypeName> superTypeNames = new ArrayList<>();
    for (CAstType superType : type.getType().getSupertypes()) {
      superTypeNames.add(toWALATypeName(superType));
    }

    JavaClass javaClass =
        new JavaClass(
            typeName,
            superTypeNames,
            type.getPosition(),
            type.getQualifiers(),
            this,
            (owner != null) ? (JavaClass) fTypeMap.get(owner) : (JavaClass) null,
            getAnnotations(type));

    if (getParent().lookupClass(javaClass.getName()) != null) {
      return null;
    }

    fTypeMap.put(type, javaClass);
    loadedClasses.put(javaClass.getName(), javaClass);
    return javaClass;
  }

  @Override
  public String toString() {
    return "Java Source Loader (classes " + loadedClasses.values() + ')';
  }

  public static class InstructionFactory extends JavaInstructionFactory
      implements AstJavaInstructionFactory {

    @Override
    public com.ibm.wala.cast.java.ssa.EnclosingObjectReference EnclosingObjectReference(
        int iindex, int lval, TypeReference type) {
      return new EnclosingObjectReference(iindex, lval, type);
    }

    @Override
    public AstJavaNewEnclosingInstruction JavaNewEnclosingInstruction(
        int iindex, int result, NewSiteReference site, int enclosing) {
      return new AstJavaNewEnclosingInstruction(iindex, result, site, enclosing);
    }

    @Override
    public AstJavaInvokeInstruction JavaInvokeInstruction(
        int iindex, int result[], int[] params, int exception, CallSiteReference site) {
      return result == null
          ? new AstJavaInvokeInstruction(iindex, params, exception, site)
          : new AstJavaInvokeInstruction(iindex, result[0], params, exception, site);
    }

    @Override
    public AstAssertInstruction AssertInstruction(
        int iindex, int value, boolean fromSpecification) {
      return new AstAssertInstruction(iindex, value, fromSpecification);
    }

    @Override
    public com.ibm.wala.cast.ir.ssa.AssignInstruction AssignInstruction(
        int iindex, int result, int val) {
      return new AssignInstruction(iindex, result, val);
    }

    @Override
    public com.ibm.wala.cast.ir.ssa.EachElementGetInstruction EachElementGetInstruction(
        int iindex, int value, int objectRef, int propRef) {
      throw new UnsupportedOperationException();
    }

    @Override
    public com.ibm.wala.cast.ir.ssa.EachElementHasNextInstruction EachElementHasNextInstruction(
        int iindex, int value, int objectRef, int propRef) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstEchoInstruction EchoInstruction(int iindex, int[] rvals) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstGlobalRead GlobalRead(int iindex, int lhs, FieldReference global) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstGlobalWrite GlobalWrite(int iindex, FieldReference global, int rhs) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstIsDefinedInstruction IsDefinedInstruction(
        int iindex, int lval, int rval, int fieldVal, FieldReference fieldRef) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstIsDefinedInstruction IsDefinedInstruction(
        int iindex, int lval, int rval, FieldReference fieldRef) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstIsDefinedInstruction IsDefinedInstruction(
        int iindex, int lval, int rval, int fieldVal) {
      return new AstIsDefinedInstruction(iindex, lval, rval, fieldVal);
    }

    @Override
    public AstIsDefinedInstruction IsDefinedInstruction(int iindex, int lval, int rval) {
      throw new UnsupportedOperationException();
    }

    @Override
    public AstLexicalRead LexicalRead(int iindex, Access[] accesses) {
      return new AstLexicalRead(iindex, accesses);
    }

    @Override
    public AstLexicalRead LexicalRead(int iindex, Access access) {
      return new AstLexicalRead(iindex, access);
    }

    @Override
    public AstLexicalRead LexicalRead(
        int iindex, int lhs, String definer, String globalName, TypeReference type) {
      return new AstLexicalRead(iindex, lhs, definer, globalName, type);
    }

    @Override
    public AstLexicalWrite LexicalWrite(int iindex, Access[] accesses) {
      return new AstLexicalWrite(iindex, accesses);
    }

    @Override
    public AstLexicalWrite LexicalWrite(int iindex, Access access) {
      return new AstLexicalWrite(iindex, access);
    }

    @Override
    public AstLexicalWrite LexicalWrite(
        int iindex, String definer, String globalName, TypeReference type, int rhs) {
      return new AstLexicalWrite(iindex, definer, globalName, type, rhs);
    }

    @Override
    public AstYieldInstruction YieldInstruction(int iindex, int[] rvals) {
      return new AstYieldInstruction(iindex, rvals);
    }

    @Override
    public AstPropertyRead PropertyRead(int iindex, int result, int objectRef, int memberRef) {
      assert false;
      return null;
    }

    @Override
    public AstPropertyWrite PropertyWrite(int iindex, int objectRef, int memberRef, int value) {
      assert false;
      return null;
    }
  }

  protected static final InstructionFactory insts = new InstructionFactory();

  @Override
  public InstructionFactory getInstructionFactory() {
    return insts;
  }
}
