/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package scouter.javassist.tools.reflect;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import scouter.javassist.tools.reflect.CannotCreateException;
import scouter.javassist.tools.reflect.CannotInvokeException;
import scouter.javassist.tools.reflect.Metaobject;
/**
* A runtime class metaobject.
*
* <p>A <code>ClassMetaobject</code> is created for every
* class of reflective objects. It can be used to hold values
* shared among the reflective objects of the same class.
*
* <p>To obtain a class metaobject, calls <code>_getClass()</code>
* on a reflective object. For example,
*
* <pre>ClassMetaobject cm = ((Metalevel)reflectiveObject)._getClass();
* </pre>
*
* @see scouter.javassist.tools.reflect.Metaobject
* @see scouter.javassist.tools.reflect.Metalevel
*/
public class ClassMetaobject implements Serializable {
/**
* The base-level methods controlled by a metaobject
* are renamed so that they begin with
* <code>methodPrefix "_m_"</code>.
*/
static final String methodPrefix = "_m_";
static final int methodPrefixLen = 3;
private Class javaClass;
private Constructor[] constructors;
private Method[] methods;
/**
* Specifies how a <code>java.lang.Class</code> object is loaded.
*
* <p>If true, it is loaded by:
* <pre>Thread.currentThread().getContextClassLoader().loadClass()</pre>
* <p>If false, it is loaded by <code>Class.forName()</code>.
* The default value is false.
*/
public static boolean useContextClassLoader = false;
/**
* Constructs a <code>ClassMetaobject</code>.
*
* @param params <code>params[0]</code> is the name of the class
* of the reflective objects.
*/
public ClassMetaobject(String[] params)
{
try {
javaClass = getClassObject(params[0]);
}
catch (ClassNotFoundException e) {
throw new RuntimeException("not found: " + params[0]
+ ", useContextClassLoader: "
+ Boolean.toString(useContextClassLoader), e);
}
constructors = javaClass.getConstructors();
methods = null;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(javaClass.getName());
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
javaClass = getClassObject(in.readUTF());
constructors = javaClass.getConstructors();
methods = null;
}
private Class getClassObject(String name) throws ClassNotFoundException {
if (useContextClassLoader)
return Thread.currentThread().getContextClassLoader()
.loadClass(name);
else
return Class.forName(name);
}
/**
* Obtains the <code>java.lang.Class</code> representing this class.
*/
public final Class getJavaClass() {
return javaClass;
}
/**
* Obtains the name of this class.
*/
public final String getName() {
return javaClass.getName();
}
/**
* Returns true if <code>obj</code> is an instance of this class.
*/
public final boolean isInstance(Object obj) {
return javaClass.isInstance(obj);
}
/**
* Creates a new instance of the class.
*
* @param args the arguments passed to the constructor.
*/
public final Object newInstance(Object[] args)
throws CannotCreateException
{
int n = constructors.length;
for (int i = 0; i < n; ++i) {
try {
return constructors[i].newInstance(args);
}
catch (IllegalArgumentException e) {
// try again
}
catch (InstantiationException e) {
throw new CannotCreateException(e);
}
catch (IllegalAccessException e) {
throw new CannotCreateException(e);
}
catch (InvocationTargetException e) {
throw new CannotCreateException(e);
}
}
throw new CannotCreateException("no constructor matches");
}
/**
* Is invoked when <code>static</code> fields of the base-level
* class are read and the runtime system intercepts it.
* This method simply returns the value of the field.
*
* <p>Every subclass of this class should redefine this method.
*/
public Object trapFieldRead(String name) {
Class jc = getJavaClass();
try {
return jc.getField(name).get(null);
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e.toString());
}
catch (IllegalAccessException e) {
throw new RuntimeException(e.toString());
}
}
/**
* Is invoked when <code>static</code> fields of the base-level
* class are modified and the runtime system intercepts it.
* This method simply sets the field to the given value.
*
* <p>Every subclass of this class should redefine this method.
*/
public void trapFieldWrite(String name, Object value) {
Class jc = getJavaClass();
try {
jc.getField(name).set(null, value);
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e.toString());
}
catch (IllegalAccessException e) {
throw new RuntimeException(e.toString());
}
}
/**
* Invokes a method whose name begins with
* <code>methodPrefix "_m_"</code> and the identifier.
*
* @exception CannotInvokeException if the invocation fails.
*/
static public Object invoke(Object target, int identifier, Object[] args)
throws Throwable
{
Method[] allmethods = target.getClass().getMethods();
int n = allmethods.length;
String head = methodPrefix + identifier;
for (int i = 0; i < n; ++i)
if (allmethods[i].getName().startsWith(head)) {
try {
return allmethods[i].invoke(target, args);
} catch (java.lang.reflect.InvocationTargetException e) {
throw e.getTargetException();
} catch (java.lang.IllegalAccessException e) {
throw new CannotInvokeException(e);
}
}
throw new CannotInvokeException("cannot find a method");
}
/**
* Is invoked when <code>static</code> methods of the base-level
* class are called and the runtime system intercepts it.
* This method simply executes the intercepted method invocation
* with the original parameters and returns the resulting value.
*
* <p>Every subclass of this class should redefine this method.
*/
public Object trapMethodcall(int identifier, Object[] args)
throws Throwable
{
try {
Method[] m = getReflectiveMethods();
return m[identifier].invoke(null, args);
}
catch (java.lang.reflect.InvocationTargetException e) {
throw e.getTargetException();
}
catch (java.lang.IllegalAccessException e) {
throw new CannotInvokeException(e);
}
}
/**
* Returns an array of the methods defined on the given reflective
* object. This method is for the internal use only.
*/
public final Method[] getReflectiveMethods() {
if (methods != null)
return methods;
Class baseclass = getJavaClass();
Method[] allmethods = baseclass.getDeclaredMethods();
int n = allmethods.length;
int[] index = new int[n];
int max = 0;
for (int i = 0; i < n; ++i) {
Method m = allmethods[i];
String mname = m.getName();
if (mname.startsWith(methodPrefix)) {
int k = 0;
for (int j = methodPrefixLen;; ++j) {
char c = mname.charAt(j);
if ('0' <= c && c <= '9')
k = k * 10 + c - '0';
else
break;
}
index[i] = ++k;
if (k > max)
max = k;
}
}
methods = new Method[max];
for (int i = 0; i < n; ++i)
if (index[i] > 0)
methods[index[i] - 1] = allmethods[i];
return methods;
}
/**
* Returns the <code>java.lang.reflect.Method</code> object representing
* the method specified by <code>identifier</code>.
*
* <p>Note that the actual method returned will be have an altered,
* reflective name i.e. <code>_m_2_..</code>.
*
* @param identifier the identifier index
* given to <code>trapMethodcall()</code> etc.
* @see Metaobject#trapMethodcall(int,Object[])
* @see #trapMethodcall(int,Object[])
*/
public final Method getMethod(int identifier) {
return getReflectiveMethods()[identifier];
}
/**
* Returns the name of the method specified
* by <code>identifier</code>.
*/
public final String getMethodName(int identifier) {
String mname = getReflectiveMethods()[identifier].getName();
int j = ClassMetaobject.methodPrefixLen;
for (;;) {
char c = mname.charAt(j++);
if (c < '0' || '9' < c)
break;
}
return mname.substring(j);
}
/**
* Returns an array of <code>Class</code> objects representing the
* formal parameter types of the method specified
* by <code>identifier</code>.
*/
public final Class[] getParameterTypes(int identifier) {
return getReflectiveMethods()[identifier].getParameterTypes();
}
/**
* Returns a <code>Class</code> objects representing the
* return type of the method specified by <code>identifier</code>.
*/
public final Class getReturnType(int identifier) {
return getReflectiveMethods()[identifier].getReturnType();
}
/**
* Returns the identifier index of the method, as identified by its
* original name.
*
* <p>This method is useful, in conjuction with
* {@link ClassMetaobject#getMethod(int)}, to obtain a quick reference
* to the original method in the reflected class (i.e. not the proxy
* method), using the original name of the method.
*
* <p>Written by Brett Randall and Shigeru Chiba.
*
* @param originalName The original name of the reflected method
* @param argTypes array of Class specifying the method signature
* @return the identifier index of the original method
* @throws NoSuchMethodException if the method does not exist
*
* @see ClassMetaobject#getMethod(int)
*/
public final int getMethodIndex(String originalName, Class[] argTypes)
throws NoSuchMethodException
{
Method[] mthds = getReflectiveMethods();
for (int i = 0; i < mthds.length; i++) {
if (mthds[i] == null)
continue;
// check name and parameter types match
if (getMethodName(i).equals(originalName)
&& Arrays.equals(argTypes, mthds[i].getParameterTypes()))
return i;
}
throw new NoSuchMethodException("Method " + originalName
+ " not found");
}
}