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

import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.ContextItem;
import com.ibm.wala.shrikeBT.BytecodeConstants;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.util.debug.Assertions;

/**
 * Simple object that represents a static call site (ie., an invoke instruction in the bytecode)
 * 
 * Note that the identity of a call site reference depends on two things: the program counter, and the containing IR. Thus, it
 * suffices to define equals() and hashCode() from {@link ProgramCounter}, since this class does not maintain a pointer to the
 * containing {@link IR} (or {@link CGNode}) anyway. If using a hashtable of CallSiteReference from different IRs, you probably want
 * to use a wrapper which also holds a pointer to the governing CGNode.
 */
public abstract class CallSiteReference extends ProgramCounter implements BytecodeConstants, ContextItem {

  final private MethodReference declaredTarget;

  /**
   * @param programCounter Index into bytecode describing this instruction
   * @param declaredTarget The method target as declared at the call site
   */
  protected CallSiteReference(int programCounter, MethodReference declaredTarget) {
    super(programCounter);
    this.declaredTarget = declaredTarget;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + ((declaredTarget == null) ? 0 : declaredTarget.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (!super.equals(obj))
      return false;
    if (getClass() != obj.getClass())
      return false;
    CallSiteReference other = (CallSiteReference) obj;
    if (declaredTarget == null) {
      if (other.declaredTarget != null)
        return false;
    } else if (!declaredTarget.equals(other.declaredTarget))
      return false;
    return true;
  }

  // the following atrocities are needed to save a word of space by
  // declaring these classes static, so they don't keep a pointer
  // to the enclosing environment
  // Java makes you type!
  static class StaticCall extends CallSiteReference {
    StaticCall(int programCounter, MethodReference declaredTarget) {
      super(programCounter, declaredTarget);
    }

    @Override
    public IInvokeInstruction.IDispatch getInvocationCode() {
      return IInvokeInstruction.Dispatch.STATIC;
    }
  }

  static class SpecialCall extends CallSiteReference {
    SpecialCall(int programCounter, MethodReference declaredTarget) {
      super(programCounter, declaredTarget);
    }

    @Override
    public IInvokeInstruction.IDispatch getInvocationCode() {
      return IInvokeInstruction.Dispatch.SPECIAL;
    }
  }

  static class VirtualCall extends CallSiteReference {
    VirtualCall(int programCounter, MethodReference declaredTarget) {
      super(programCounter, declaredTarget);
    }

    @Override
    public IInvokeInstruction.IDispatch getInvocationCode() {
      return IInvokeInstruction.Dispatch.VIRTUAL;
    }
  }

  static class InterfaceCall extends CallSiteReference {
    InterfaceCall(int programCounter, MethodReference declaredTarget) {
      super(programCounter, declaredTarget);
    }

    @Override
    public IInvokeInstruction.IDispatch getInvocationCode() {
      return IInvokeInstruction.Dispatch.INTERFACE;
    }
  }

  /**
   * This factory method plays a little game to avoid storing the invocation code in the object; this saves a byte (probably
   * actually a whole word) in each created object.
   * 
   * TODO: Consider canonicalization?
   */
  public static CallSiteReference make(int programCounter, MethodReference declaredTarget,
      IInvokeInstruction.IDispatch invocationCode) {

    if (invocationCode == IInvokeInstruction.Dispatch.SPECIAL)
      return new SpecialCall(programCounter, declaredTarget);
    if (invocationCode == IInvokeInstruction.Dispatch.VIRTUAL)
      return new VirtualCall(programCounter, declaredTarget);
    if (invocationCode == IInvokeInstruction.Dispatch.INTERFACE)
      return new InterfaceCall(programCounter, declaredTarget);
    if (invocationCode == IInvokeInstruction.Dispatch.STATIC)
      return new StaticCall(programCounter, declaredTarget);

    throw new IllegalArgumentException("unsupported code: " + invocationCode);
  }

  /**
   * Return the Method that this call site calls. This represents the method declared in the invoke instruction only.
   */
  public MethodReference getDeclaredTarget() {
    return declaredTarget;
  }

  /**
   * Return one of INVOKESPECIAL, INVOKESTATIC, INVOKEVIRTUAL, or INVOKEINTERFACE
   */
  abstract public IInvokeInstruction.IDispatch getInvocationCode();

  @Override
  public String toString() {
    return "invoke" + getInvocationString(getInvocationCode()) + ' ' + declaredTarget + '@' + getProgramCounter();
  }

  protected String getInvocationString(IInvokeInstruction.IDispatch invocationCode) {
    if (invocationCode == IInvokeInstruction.Dispatch.STATIC)
      return "static";
    if (invocationCode == IInvokeInstruction.Dispatch.SPECIAL)
      return "special";
    if (invocationCode == IInvokeInstruction.Dispatch.VIRTUAL)
      return "virtual";
    if (invocationCode == IInvokeInstruction.Dispatch.INTERFACE)
      return "interface";

    Assertions.UNREACHABLE();
    return null;
  }

  public String getInvocationString() {
    return getInvocationString(getInvocationCode());
  }

  /**
   * Is this an invokeinterface call site?
   */
  public final boolean isInterface() {
    return (getInvocationCode() == IInvokeInstruction.Dispatch.INTERFACE);
  }

  /**
   * Is this an invokevirtual call site?
   */
  public final boolean isVirtual() {
    return (getInvocationCode() == IInvokeInstruction.Dispatch.VIRTUAL);
  }

  /**
   * Is this an invokespecial call site?
   */
  public final boolean isSpecial() {
    return (getInvocationCode() == IInvokeInstruction.Dispatch.SPECIAL);
  }

  /**
   * Is this an invokestatic call site?
   */
  public boolean isStatic() {
    return (getInvocationCode() == IInvokeInstruction.Dispatch.STATIC);
  }

  public boolean isFixed() {
    return isStatic() || isSpecial();
  }

  public boolean isDispatch() {
    return isVirtual() || isInterface();
  }

}
