package soot.jimple.toolkits.pointer.nativemethods;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2003 Feng Qian
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import soot.SootMethod;
import soot.jimple.toolkits.pointer.representations.AbstractObject;
import soot.jimple.toolkits.pointer.representations.Environment;
import soot.jimple.toolkits.pointer.representations.ReferenceVariable;
import soot.jimple.toolkits.pointer.util.NativeHelper;

public class JavaLangClassNative extends NativeMethodClass {
  public JavaLangClassNative(NativeHelper helper) {
    super(helper);
  }

  /**
   * Implements the abstract method simulateMethod. It distributes the request to the corresponding methods by signatures.
   */
  public void simulateMethod(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {

    String subSignature = method.getSubSignature();

    if (subSignature.equals("java.lang.Class forName0(java.lang.String,boolean,java.lang.ClassLoader)")) {
      java_lang_Class_forName0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Object newInstance0()")) {
      java_lang_Class_newInstance0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.String getName()")) {
      java_lang_Class_getName(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.ClassLoader getClassLoader0()")) {
      java_lang_Class_getClassLoader0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Class getSuperclass()")) {
      java_lang_Class_getSuperclass(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Class[] getInterfaces()")) {
      java_lang_Class_getInterfaces(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Class getComponentType()")) {
      java_lang_Class_getComponentType(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Object[] getSigners()")) {
      java_lang_Class_getSigners(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("void setSigners(java.lang.Object[])")) {
      java_lang_Class_setSigners(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Class getDeclaringClass()")) {
      java_lang_Class_getDeclaringClass(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("void setProtectionDomain0(java.security.ProtectionDomain)")) {
      java_lang_Class_setProtectionDomain0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.security.ProtectionDomain getProtectionDomain0()")) {
      java_lang_Class_getProtectionDomain0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Class getPrimitiveClass(java.lang.String)")) {
      java_lang_Class_getPrimitiveClass(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Field[] getFields0(int)")) {
      java_lang_Class_getFields0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Method[] getMethods0(int)")) {
      java_lang_Class_getMethods0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Constructor[] getConstructors0(int)")) {
      java_lang_Class_getConstructors0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Field getField0(java.lang.String,int)")) {
      java_lang_Class_getField0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Method getMethod0(java.lang.String,java.lang.Class[],int)")) {
      java_lang_Class_getMethod0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Constructor getConstructor0(java.lang.Class[],int)")) {
      java_lang_Class_getConstructor0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.Class[] getDeclaredClasses0()")) {
      java_lang_Class_getDeclaredClasses0(method, thisVar, returnVar, params);
      return;

    } else if (subSignature.equals("java.lang.reflect.Constructor[] getDeclaredConstructors0(boolean)")) {
      java_lang_Class_getDeclaredConstructors0(method, thisVar, returnVar, params);
      return;

    } else {
      defaultMethod(method, thisVar, returnVar, params);
      return;

    }
  }

  /****************************** java.lang.Class **********************/
  /*
   * A quick note for simulating java.lang.Class :
   *
   * In theory, the same class may have two or more representations at the runtime. But statically, we can assume that all
   * variables of java.lang.Class type are aliased together. By looking at static class hierarchy, there is only one
   * ReferenceVariable variable for a class in the hierarchy.
   */

  /**
   * NOTE: the semantic of forName0 follows forName method.
   *
   * Returns the Class object associated with the class or interface with the given string name, using the given class
   * loader. Given the fully qualified name for a class or interface (in the same format returned by getName) this method
   * attempts to locate, load, and link the class or interface. The specified class loader is used to load the class or
   * interface. If the parameter loader is null, the class is loaded through the bootstrap class loader. The class is
   * initialized only if the initialize parameter is true and if it has not been initialized earlier.
   *
   * If name denotes a primitive type or void, an attempt will be made to locate a user-defined class in the unnamed package
   * whose name is name. Therefore, this method cannot be used to obtain any of the Class objects representing primitive
   * types or void.
   *
   * If name denotes an array class, the component type of the array class is loaded but not initialized.
   *
   * For example, in an instance method the expression: Class.forName("Foo") is equivalent to: Class.forName("Foo", true,
   * this.getClass().getClassLoader())
   *
   * private static native java.lang.Class forName0(java.lang.String, boolean, java.lang.ClassLoader) throws
   * java.lang.ClassNotFoundException;
   */
  public void java_lang_Class_forName0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getClassObject());
  }

  /**
   * NOTE: creates an object.
   *
   * private native java.lang.Object newInstance0() throws java.lang.InstantiationException, java.lang.IllegalAccessException
   */
  public void java_lang_Class_newInstance0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    ReferenceVariable instanceVar = helper.newInstanceOf(thisVar);
    helper.assign(returnVar, instanceVar);
  }

  /**
   * Returns the class name.
   *
   * public native java.lang.String getName();
   */
  public void java_lang_Class_getName(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getStringObject());
  }

  /**
   * returns the class loader object for this class.
   *
   * it is almost impossible to distinguish the dynamic class loader for classes. a conservative way is to use one static
   * representation for all class loader, which means all class loader variable aliased together.
   *
   * private native java.lang.ClassLoader getClassLoader0();
   */
  public void java_lang_Class_getClassLoader0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getClassLoaderObject());
  }

  /**
   * returns the super class of this class
   *
   * public native java.lang.Class getSuperclass();
   */
  public void java_lang_Class_getSuperclass(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getClassObject());
  }

  /**
   * Determines the interfaces implemented by the class or interface represented by this object.
   *
   * public native java.lang.Class getInterfaces()[];
   */
  public void java_lang_Class_getInterfaces(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    /* currently, we do not distinguish array object and scalar object. */
    helper.assignObjectTo(returnVar, Environment.v().getClassObject());
  }

  /**
   * Returns the Class representing the component type of an array. If this class does not represent an array class this
   * method returns null.
   *
   * public native java.lang.Class getComponentType();
   */
  public void java_lang_Class_getComponentType(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getClassObject());
  }

  /**
   * Sets the signers of a class. This should be called after defining a class. Parameters: c - the Class object signers -
   * the signers for the class
   *
   * native void setSigners(java.lang.Object[]);
   */
  public void java_lang_Class_setSigners(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    ReferenceVariable tempFld = helper.tempField("<java.lang.Class signers>");
    helper.assign(tempFld, params[0]);
  }

  /**
   * Gets the signers of this class. We need an artificial field variable to connect setSigners and getSigners.
   *
   * public native java.lang.Object getSigners()[];
   */
  public void java_lang_Class_getSigners(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    ReferenceVariable tempFld = helper.tempField("<java.lang.Class signers>");
    helper.assign(returnVar, tempFld);
  }

  /**
   * If the class or interface represented by this Class object is a member of another class, returns the Class object
   * representing the class in which it was declared. This method returns null if this class or interface is not a member of
   * any other class. If this Class object represents an array class, a primitive type, or void,then this method returns
   * null.
   *
   * Returns: the declaring class for this class
   *
   * public native java.lang.Class getDeclaringClass();
   */
  public void java_lang_Class_getDeclaringClass(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getClassObject());
  }

  /**
   * Sets or returns the ProtectionDomain of this class, called by getProtectiondomain.
   *
   * We need an artificial field variable to handle this.
   *
   * native void setProtectionDomain0(java.security.ProtectionDomain);
   */
  public void java_lang_Class_setProtectionDomain0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    ReferenceVariable protdmn = helper.tempField("<java.lang.Class ProtDmn>");
    helper.assign(protdmn, params[0]);
  }

  /**
   * private native java.security.ProtectionDomain getProtectionDomain0();
   */
  public void java_lang_Class_getProtectionDomain0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    ReferenceVariable protdmn = helper.tempField("<java.lang.Class ProtDmn>");
    helper.assign(returnVar, protdmn);
  }

  /**
   * Undocumented. It is supposed to return a class object for primitive type named by @param0.
   *
   * static native java.lang.Class getPrimitiveClass(java.lang.String);
   */
  public void java_lang_Class_getPrimitiveClass(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getClassObject());
  }

  /**
   * Returns an array containing Field objects reflecting all the accessible public fields of the class or interface
   * represented by this Class object.
   *
   * private native java.lang.reflect.Field getFields0(int)[];
   */
  public void java_lang_Class_getFields0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getArrayFields());
  }

  /**
   * Returns an array containing Method objects reflecting all the public member methods of the class or interface
   * represented by this Class object, including those declared by the class or interface and and those inherited from
   * superclasses and superinterfaces.
   *
   * private native java.lang.reflect.Method getMethods0(int)[];
   */
  public void java_lang_Class_getMethods0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getArrayMethods());
  }

  /**
   * Returns a Constructor object that reflects the specified public constructor of the class represented by this Class
   * object. The parameterTypes parameter is an array of Class objects that identify the constructor's formal parameter
   * types, in declared order.
   *
   * private native java.lang.reflect.Constructor getConstructors0(int)[];
   */
  public void java_lang_Class_getConstructors0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getArrayConstructor());
  }

  /**
   * Returns a Field object that reflects the specified public member field of the class or interface represented by this
   * Class object.
   *
   * Called by getField(String)
   *
   * NOTE: getField0(String name), since the name can be dynamically constructed, it may be not able to know exact field name
   * in static analysis. Uses a C.F to represent the class field.
   *
   * private native java.lang.reflect.Field getField0(java.lang.String, int);
   */
  public void java_lang_Class_getField0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getArrayFields());
  }

  /**
   * Returns a Method object that reflects the specified public member method of the class or interface represented by this
   * Class object.
   *
   * Called by getMethod()
   *
   * private native java.lang.reflect.Method getMethod0(java.lang.String, java.lang.Class[], int);
   */
  public void java_lang_Class_getMethod0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getMethodObject());
  }

  /**
   * Returns a constructor of a class
   *
   * private native java.lang.reflect.Constructor getConstructor0(java.lang.Class[], int);
   */
  public void java_lang_Class_getConstructor0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getConstructorObject());
  }

  /**
   * Returns an array of Class objects reflecting all the classes and interfaces declared as members of the class represented
   * by this Class object.
   *
   * private native java.lang.Class[] getDeclaredClasses0();
   */
  public void java_lang_Class_getDeclaredClasses0(SootMethod method, ReferenceVariable thisVar, ReferenceVariable returnVar,
      ReferenceVariable params[]) {
    helper.assignObjectTo(returnVar, Environment.v().getArrayClasses());
  }

  /**
   * Returns an array of Constructor objects reflecting all the classes and interfaces declared as members of the class
   * represented by this Class object.
   *
   * private native java.lang.reflect.Constructor[] getDeclaredConstructors0(boolean);
   */
  public void java_lang_Class_getDeclaredConstructors0(SootMethod method, ReferenceVariable thisVar,
      ReferenceVariable returnVar, ReferenceVariable params[]) {
    AbstractObject array = Environment.v().getArrayConstructor();
    AbstractObject cons = Environment.v().getConstructorObject();
    helper.assignObjectTo(returnVar, array);
    helper.assignObjectTo(helper.arrayElementOf(returnVar), cons);
  }

  /**
   * Following methods have NO side effects.
   *
   * private static native void registerNatives(); public native boolean isInstance(java.lang.Object); public native boolean
   * isAssignableFrom(java.lang.Class); public native boolean isInterface(); public native boolean isArray(); public native
   * boolean isPrimitive(); public native int getModifiers();
   */

}
