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

import org.jikesrvm.VM;
import org.jikesrvm.Constants;
import org.jikesrvm.classloader.Atom;
import org.jikesrvm.classloader.BootstrapClassLoader;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMField;
import org.jikesrvm.classloader.RVMMember;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.MethodReference;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.classloader.TypeReference;

/**
 * Helper class for retrieving entrypoints. Entrypoints are fields and
 * methods of the virtual machine that are needed by compiler-generated
 * machine code or C runtime code.
 */
public class EntrypointHelper {
  /**
   * Get description of virtual machine component (field or method).
   * Note: This is method is intended for use only by VM classes that need
   * to address their own fields and methods in the runtime virtual machine
   * image.  It should not be used for general purpose class loading.
   * @param classDescriptor  class  descriptor - something like "Lorg/jikesrvm/RuntimeEntrypoints;"
   * @param memberName       member name       - something like "invokestatic"
   * @param memberDescriptor member descriptor - something like "()V"
   * @return corresponding RVMMember object
   */
  private static RVMMember getMember(String classDescriptor, String memberName, String memberDescriptor) {
    Atom clsDescriptor = Atom.findOrCreateAsciiAtom(classDescriptor);
    Atom memName = Atom.findOrCreateAsciiAtom(memberName);
    Atom memDescriptor = Atom.findOrCreateAsciiAtom(memberDescriptor);
    try {
      TypeReference tRef =
          TypeReference.findOrCreate(BootstrapClassLoader.getBootstrapClassLoader(), clsDescriptor);
      RVMClass cls = (RVMClass) tRef.resolve();
      cls.resolve();

      RVMMember member;
      if ((member = cls.findDeclaredField(memName, memDescriptor)) != null) {
        return member;
      }
      if ((member = cls.findDeclaredMethod(memName, memDescriptor)) != null) {
        return member;
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    // The usual causes for getMember() to fail are:
    //  1. you mispelled the class name, member name, or member signature
    //  2. the class containing the specified member didn't get compiled
    //
    VM.sysWrite("Entrypoints.getMember: can't resolve class=" +
                classDescriptor +
                " member=" +
                memberName +
                " desc=" +
                memberDescriptor +
                "\n");
    VM._assert(Constants.NOT_REACHED);
    return null; // placate jikes
  }

  public static NormalMethod getMethod(String klass, String member, String descriptor, final boolean runtimeServiceMethod) {
    NormalMethod m = (NormalMethod) getMember(klass, member, descriptor);
    m.setRuntimeServiceMethod(runtimeServiceMethod);
    return m;
  }

  public static NormalMethod getMethod(String klass, String member, String descriptor) {
    return getMethod(klass, member, descriptor, true);
  }

  private static String makeDescriptor(Class<?>... argTypes) {
    Class<?> lastClass = null;
    StringBuilder result = new StringBuilder("(");
    for (Class<?> c: argTypes) {
      if (lastClass != null) {
        result.append(TypeReference.findOrCreate(lastClass).getName().toString());
      }
      lastClass = c;
    }
    result.append(")").append(TypeReference.findOrCreate(lastClass).getName().toString());
    return result.toString();
  }

  public static RVMMethod getMethod(Class<?> klass, Atom member, Class<?>... argTypes) {
    if (!VM.runningVM) { // avoid compiling this code into the boot image
      try {
        TypeReference tRef = TypeReference.findOrCreate(klass);
        RVMClass cls = tRef.resolve().asClass();
        cls.resolve();

        Atom descriptor = Atom.findOrCreateAsciiAtom(makeDescriptor(argTypes));

        RVMMethod method = cls.findDeclaredMethod(member, descriptor);
        if (method != null) {
          return method;
        }
      } catch(Throwable t) {
        throw new Error("Entrypoints.getMethod: can't resolve class=" +
            klass + " member=" + member + " desc=" + makeDescriptor(argTypes), t);
      }
    }
    throw new Error("Entrypoints.getMethod: can't resolve class=" +
        klass + " member=" + member + " desc=" + makeDescriptor(argTypes));
  }

  public static MethodReference getMethodReference(Class<?> klass, Atom member, Class<?>... argTypes) {
    if (!VM.runningVM) { // avoid compiling this code into the boot image
      TypeReference tRef = TypeReference.findOrCreate(klass);
      if (tRef.resolve().isClassType()) {
        return getMethod(klass, member, argTypes).getMemberRef().asMethodReference();
      } else { // handle method references to unboxed types
        Atom descriptor = Atom.findOrCreateAsciiAtom(makeDescriptor(argTypes));
        return MethodReference.findOrCreate(tRef, member, descriptor);
      }
    }
    throw new Error("Entrypoints.getMethod: can't resolve class=" +
        klass + " member=" + member + " desc=" + makeDescriptor(argTypes));
  }

  public static RVMField getField(String klass, String member, String descriptor) {
    return (RVMField) getMember(klass, member, descriptor);
  }

  /**
   * Get description of virtual machine field.
   * @param klass class containing field
   * @param member member name - something like "invokestatic"
   * @param type of field
   * @return corresponding RVMField
   */
  public static RVMField getField(Class<?> klass, String member, Class<?> type) {
    if (!VM.runningVM) { // avoid compiling this code into the boot image
      try {
        TypeReference klassTRef = TypeReference.findOrCreate(klass);
        RVMClass cls = klassTRef.resolve().asClass();
        cls.resolve();

        Atom memName = Atom.findOrCreateAsciiAtom(member);
        Atom typeName = TypeReference.findOrCreate(type).getName();

        RVMField field = cls.findDeclaredField(memName, typeName);
        if (field != null) {
          return field;
        }
      } catch(Throwable t) {
        throw new Error("Entrypoints.getField: can't resolve class=" +
            klass + " member=" + member + " desc=" + type, t);
      }
    }
    throw new Error("Entrypoints.getField: can't resolve class=" +
        klass + " member=" + member + " desc=" + type);
  }

  /**
   * Get description of virtual machine field.
   * @param klass class containing field
   * @param member member name - something like "invokestatic"
   * @param type of field
   * @return corresponding RVMField
   */
  static RVMField getField(String klass, String member, Class<?> type) {
    if (!VM.runningVM) { // avoid compiling this code into the boot image
      try {
        TypeReference tRef = TypeReference.findOrCreate(klass);
        RVMClass cls = tRef.resolve().asClass();
        cls.resolve();

        Atom memName = Atom.findOrCreateAsciiAtom(member);
        Atom typeName = TypeReference.findOrCreate(type).getName();

        RVMField field = cls.findDeclaredField(memName, typeName);
        if (field != null) {
          return field;
        }
      } catch(Throwable t) {
        throw new Error("Entrypoints.getField: can't resolve class=" +
            klass + " member=" + member + " desc=" + type, t);
      }
    }
    throw new Error("Entrypoints.getField: can't resolve class=" +
        klass + " member=" + member + " desc=" + type);
  }

  /**
   * Get description of virtual machine method.
   * @param klass class  containing method
   * @param member member name - something like "invokestatic"
   * @param descriptor member descriptor - something like "()V"
   * @return corresponding RVMMethod
   */
  public static NormalMethod getMethod(Class<?> klass, String member, String descriptor) {
    if (!VM.runningVM) { // avoid compiling this code into the boot image
      try {
        TypeReference klassTRef = TypeReference.findOrCreate(klass);
        RVMClass cls = klassTRef.resolve().asClass();
        cls.resolve();

        Atom memName = Atom.findOrCreateAsciiAtom(member);
        Atom memDescriptor = Atom.findOrCreateAsciiAtom(descriptor);

        NormalMethod m = (NormalMethod)cls.findDeclaredMethod(memName, memDescriptor);
        if (m != null) {
          m.setRuntimeServiceMethod(true);
          return m;
        }
      } catch(Throwable t) {
        throw new Error("Entrypoints.getField: can't resolve class=" +
            klass + " member=" + member + " desc=" + descriptor, t);
      }
    }
    throw new Error("Entrypoints.getMethod: can't resolve class=" +
        klass + " method=" + member + " desc=" + descriptor);
  }
}
