/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.classloader;

import org.jikesrvm.VM;
import org.vmmagic.pragma.Uninterruptible;

/**
 * A class to represent the reference in a class file to a method of
 * that class or interface.
 */
public final class MethodReference extends MemberReference {

  /**
   * type of return value
   */
  private final TypeReference returnType;

  /**
   * types of parameters (not including "this", if virtual)
   */
  private final TypeReference[] parameterTypes;

  /**
   * The RVMMethod that this method reference resolved to (null if not yet resolved).
   */
  private RVMMethod resolvedMember;

  /**
   * Find or create a method reference
   * @see MemberReference#findOrCreate(TypeReference, Atom, Atom)
   */
  public static MethodReference findOrCreate(TypeReference tRef, Atom mn, Atom md) {
    return MemberReference.findOrCreate(tRef, mn, md).asMethodReference();
  }

  /**
   * @param tr a type reference to the defining class in which this method
   * appears. (e.g., "Ljava/lang/String;")
   * @param mn the name of this method (e.g., "equals")
   * @param d the method descriptor (e.g., "(Ljava/lang/Object;)Z")
   * @param id the new ID of the member were a new member required
   */
  MethodReference(TypeReference tr, Atom mn, Atom d, int id) {
    super(tr, mn, d, id);
    ClassLoader cl = tr.getClassLoader();
    returnType = d.parseForReturnType(cl);
    parameterTypes = d.parseForParameterTypes(cl);
  }

  /**
   * @return return type of the method
   */
  @Uninterruptible
  public TypeReference getReturnType() {
    return returnType;
  }

  /**
   * @return parameter types of the method
   */
  @Uninterruptible
  public TypeReference[] getParameterTypes() {
    return parameterTypes;
  }

  /**
   * Space required by method for its parameters, in words.
   * Note: does *not* include implicit "this" parameter, if any.
   */
  @Uninterruptible
  public int getParameterWords() {
    int pw = 0;
    for (TypeReference parameterType : parameterTypes) pw += parameterType.getStackWords();
    return pw;
  }

  /**
   * Do this and that definitely refer to the different methods?
   */
  public boolean definitelyDifferent(MethodReference that) {
    if (this == that) return false;
    if (name != that.name || descriptor != that.descriptor) return true;
    RVMMethod mine = peekResolvedMethod();
    RVMMethod theirs = that.peekResolvedMethod();
    if (mine == null || theirs == null) return false;
    return mine != theirs;
  }

  /**
   * Do this and that definitely refer to the same method?
   */
  public boolean definitelySame(MethodReference that) {
    if (this == that) return true;
    if (name != that.name || descriptor != that.descriptor) return false;
    RVMMethod mine = peekResolvedMethod();
    RVMMethod theirs = that.peekResolvedMethod();
    if (mine == null || theirs == null) return false;
    return mine == theirs;
  }

  /**
   * Has the method reference already been resolved into a target method?
   */
  public boolean isResolved() {
    return resolvedMember != null;
  }

  /**
   * Get the member this reference has been resolved to, if
   * it has already been resolved. Does NOT force resolution.
   */
  @Uninterruptible
  public RVMMethod getResolvedMember() {
    return resolvedMember;
  }

  /**
   * For use by RVMMethod constructor
   */
  void setResolvedMember(RVMMethod it) {
    if (VM.VerifyAssertions) VM._assert(resolvedMember == null || resolvedMember == it);
    resolvedMember = it;
  }

  /**
   * Resolve the method reference for an invoke special into a target
   * method, return null if the method cannot be resolved without classloading.
   */
  public synchronized RVMMethod resolveInvokeSpecial() {
    RVMClass thisClass = (RVMClass) type.peekType();
    if (thisClass == null && name != RVMClassLoader.StandardObjectInitializerMethodName) {
      thisClass = (RVMClass) type.resolve();
      /* Can't fail to resolve thisClass; we're at compile time doing
         resolution of an invocation to a private method or super call.  We
         must have loaded the class already */
    }
    if (thisClass == null) {
      return null; // can't be found now.
    }
    RVMMethod sought = resolveInternal(thisClass);

    if (sought.isObjectInitializer()) {
      return sought;   // <init>
    }

    RVMClass cls = sought.getDeclaringClass();
    if (!cls.isSpecial()) {
      return sought;   // old-style invokespecial semantics
    }

    for (; cls != null; cls = cls.getSuperClass()) {
      RVMMethod found = cls.findDeclaredMethod(sought.getName(), sought.getDescriptor());
      if (found != null) {
        return found; // new-style invokespecial semantics
      }
    }
    return null; // cannot be found
  }

  /**
   * Find the RVMMethod that this method reference refers to using
   * the search order specified in JVM spec 5.4.3.3.
   * @return the RVMMethod that this method ref resolved to or null if it cannot be resolved.
   */
  public RVMMethod peekResolvedMethod() {
    if (resolvedMember != null) return resolvedMember;

    // Hasn't been resolved yet. Try to do it now without triggering class loading.
    RVMClass declaringClass = (RVMClass) type.peekType();
    if (declaringClass == null) return null;
    return resolveInternal(declaringClass);
  }

  /**
   * Find the RVMMethod that this field reference refers to using
   * the search order specified in JVM spec 5.4.3.3.
   * @return the RVMMethod that this method ref resolved to.
   */
  public synchronized RVMMethod resolve() {
    if (resolvedMember != null) return resolvedMember;

    // Hasn't been resolved yet. Do it now triggering class loading if necessary.
    return resolveInternal((RVMClass) type.resolve());
  }

  static final boolean DBG = false;

  /**
   * Return true iff this member reference refers to a method which
   * is declared as part of an abstract class but actually is an
   * interface method, known formally as a "miranda method".
   *
   * This method is necessary to handle the special case where an
   * invokevirtual is defined on an abstract class, where the
   * method invocation points to a method inherited from an
   * interface.
   *
   * @return boolean    true iff this member method reference is a miranda method
   */
  public boolean isMiranda() {

    // Hasn't been resolved yet. Try to do it now without triggering class loading.
    RVMClass declaringClass = (RVMClass) type.peekType();
    if (declaringClass == null) { return false; }

    if (!declaringClass.isResolved()) {
      declaringClass.resolve();
    }

    // See if method is explicitly declared in any superclass
    for (RVMClass c = declaringClass; c != null; c = c.getSuperClass()) {

      if (c.findDeclaredMethod(name, descriptor) != null) {
        // Method declared in superclass => not interface method
        return false;
      }
    }

    // Not declared in any superclass; now check to see if it is coming from an interface somewhere
    for (RVMClass c = declaringClass; c != null; c = c.getSuperClass()) {
      // See if method is in any interfaces of c
      for (RVMClass intf : c.getDeclaredInterfaces()) {
        if (searchInterfaceMethods(intf) != null) {
          // Found method in interface or superinterface
          return true;
        }
      }
    }

    // neither in superclass or interface => not interface method
    return false;
  }

  /**
   * Is the method reference to a magic method? NB. In the case of
   * SysCall annotated methods we don't know until they are resolved.
   */
  public boolean isMagic() {
    return getType().isMagicType() || ((resolvedMember != null) && (resolvedMember.isSysCall() || resolvedMember.isSpecializedInvoke()));
  }

  /**
   * Is the method reference to a specialized invoke? NB. we don't know until they are resolved.
   */
  public boolean isSpecializedInvoke() {
    return (resolvedMember != null) && (resolvedMember.isSpecializedInvoke());
  }

  /**
   * Is the method reference to a magic method? NB. In the case of
   * SysCall annotated methods we don't know until they are resolved.
   */
  public boolean isSysCall() {
    return (getType() == TypeReference.SysCall) || ((resolvedMember != null) && (resolvedMember.isSysCall()));
  }

  /**
   * Find the RVMMethod that this member reference refers to using
   * the search order specified in JVM spec 5.4.3.3.
   * @return the RVMMethod that this method ref resolved to.
   */
  private RVMMethod resolveInternal(RVMClass declaringClass) {
    if (!declaringClass.isResolved()) {
      declaringClass.resolve();
    }
    for (RVMClass c = declaringClass; c != null; c = c.getSuperClass()) {
      if (DBG) {
        VM.sysWrite("Checking for <" + name + "," + descriptor + "> in class " + c + "...");
      }

      RVMMethod it = c.findDeclaredMethod(name, descriptor);
      if (it != null) {
        if (DBG) {
          VM.sysWriteln("...found <" + name + "," + descriptor + "> in class " + c);
        }
        resolvedMember = it;
        return resolvedMember;
      }
      if (DBG) {
        VM.sysWriteln("...NOT found <" + name + "," + descriptor + "> in class " + c);
      }
    }
    if (!VM.fullyBooted) {
      VM.sysWrite("MethodReference.resolveInternal():");
      VM.sysWrite(" Unable to find a method named ");
      name.sysWrite();
      VM.sysWrite(" with descriptor ");
      descriptor.sysWrite();
      VM.sysWrite(" in the class ");
      declaringClass.getDescriptor().sysWrite();
      if (VM.runningVM) {
        VM.sysWriteln(", while booting the VM");
        VM.sysFail("MethodReference.resolveInternal(): Unable to resolve a method during VM booting");

      } else {
        VM.sysWriteln(", while writing the boot image");
        Thread.dumpStack();
        throw new Error("MethodReference.resolveInternal(): Unable to resolve a method during boot image writing");
      }
    }
    throw new NoSuchMethodError(this.toString());
  }

  /**
   * Find the RVMMethod that this member reference refers to using
   * the search order specified in JVM spec 5.4.3.4.
   * @return the RVMMethod that this method ref resolved to or null if it cannot be resolved without trigering class loading
   */
  public RVMMethod peekInterfaceMethod() {
    if (resolvedMember != null) return resolvedMember;

    // Hasn't been resolved yet. Try to do it now.
    RVMClass declaringClass = (RVMClass) type.peekType();
    if (declaringClass == null) return null;
    if (!declaringClass.isResolved()) {
      declaringClass.resolve();
    }
    if (!declaringClass.isInterface()) return null;
    return resolveInterfaceMethodInternal(declaringClass);
  }

  /**
   * Find the RVMMethod that this member reference refers to using
   * the search order specified in JVM spec 5.4.3.4.
   * @return the RVMMethod that this method ref resolved to
   */
  public RVMMethod resolveInterfaceMethod() throws IncompatibleClassChangeError, NoSuchMethodError {
    if (resolvedMember != null) return resolvedMember;

    // Hasn't been resolved yet. Do it now.
    RVMClass declaringClass = (RVMClass) type.resolve();
    if (!declaringClass.isResolved()) {
      declaringClass.resolve();
    }

    /* Interface method may be either in interface, or a miranda.
    */
    if (!declaringClass.isInterface() && !isMiranda()) {
      throw new IncompatibleClassChangeError();
    }
    RVMMethod ans = resolveInterfaceMethodInternal(declaringClass);
    if (ans == null) {
      throw new NoSuchMethodError(this.toString());
    }
    return ans;
  }

  /**
   * Find the RVMMethod that this member reference refers to using
   * the search order specified in JVM spec 5.4.3.4.
   * @return the RVMMethod that this method ref resolved to or null for error
   */
  private RVMMethod resolveInterfaceMethodInternal(RVMClass declaringClass) {
    RVMMethod it = declaringClass.findDeclaredMethod(name, descriptor);
    if (it != null) {
      resolvedMember = it;
      return resolvedMember;
    }
    for (RVMClass intf : declaringClass.getDeclaredInterfaces()) {
      it = searchInterfaceMethods(intf);
      if (it != null) {
        resolvedMember = it;
        return resolvedMember;
      }
    }
    return null;
  }

  private RVMMethod searchInterfaceMethods(RVMClass c) {
    if (!c.isResolved()) c.resolve();
    RVMMethod it = c.findDeclaredMethod(name, descriptor);
    if (it != null) return it;
    for (RVMClass intf : c.getDeclaredInterfaces()) {
      it = searchInterfaceMethods(intf);
      if (it != null) return it;
    }
    return null;
  }
}
