1 /*
2  * Javassist, a Java-bytecode translator toolkit.
3  * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License.  Alternatively, the contents of this file may be used under
8  * the terms of the GNU Lesser General Public License Version 2.1 or later.
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  */
15 
16 package javassist.tools.reflect;
17 
18 import java.lang.reflect.*;
19 import java.util.Arrays;
20 import java.io.Serializable;
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.io.ObjectOutputStream;
24 
25 /**
26  * A runtime class metaobject.
27  *
28  * <p>A <code>ClassMetaobject</code> is created for every
29  * class of reflective objects.  It can be used to hold values
30  * shared among the reflective objects of the same class.
31  *
32  * <p>To obtain a class metaobject, calls <code>_getClass()</code>
33  * on a reflective object.  For example,
34  *
35  * <ul><pre>ClassMetaobject cm = ((Metalevel)reflectiveObject)._getClass();
36  * </pre></ul>
37  *
38  * @see javassist.tools.reflect.Metaobject
39  * @see javassist.tools.reflect.Metalevel
40  */
41 public class ClassMetaobject implements Serializable {
42     /**
43      * The base-level methods controlled by a metaobject
44      * are renamed so that they begin with
45      * <code>methodPrefix "_m_"</code>.
46      */
47     static final String methodPrefix = "_m_";
48     static final int methodPrefixLen = 3;
49 
50     private Class javaClass;
51     private Constructor[] constructors;
52     private Method[] methods;
53 
54     /**
55      * Specifies how a <code>java.lang.Class</code> object is loaded.
56      *
57      * <p>If true, it is loaded by:
58      * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul>
59      * <p>If false, it is loaded by <code>Class.forName()</code>.
60      * The default value is false.
61      */
62     public static boolean useContextClassLoader = false;
63 
64     /**
65      * Constructs a <code>ClassMetaobject</code>.
66      *
67      * @param params    <code>params[0]</code> is the name of the class
68      *                  of the reflective objects.
69      */
ClassMetaobject(String[] params)70     public ClassMetaobject(String[] params)
71     {
72         try {
73             javaClass = getClassObject(params[0]);
74         }
75         catch (ClassNotFoundException e) {
76             throw new RuntimeException("not found: " + params[0]
77                                        + ", useContextClassLoader: "
78                                        + Boolean.toString(useContextClassLoader), e);
79         }
80 
81         constructors = javaClass.getConstructors();
82         methods = null;
83     }
84 
writeObject(ObjectOutputStream out)85     private void writeObject(ObjectOutputStream out) throws IOException {
86         out.writeUTF(javaClass.getName());
87     }
88 
readObject(ObjectInputStream in)89     private void readObject(ObjectInputStream in)
90         throws IOException, ClassNotFoundException
91     {
92         javaClass = getClassObject(in.readUTF());
93         constructors = javaClass.getConstructors();
94         methods = null;
95     }
96 
getClassObject(String name)97     private Class getClassObject(String name) throws ClassNotFoundException {
98         if (useContextClassLoader)
99             return Thread.currentThread().getContextClassLoader()
100                    .loadClass(name);
101         else
102             return Class.forName(name);
103     }
104 
105     /**
106      * Obtains the <code>java.lang.Class</code> representing this class.
107      */
getJavaClass()108     public final Class getJavaClass() {
109         return javaClass;
110     }
111 
112     /**
113      * Obtains the name of this class.
114      */
getName()115     public final String getName() {
116         return javaClass.getName();
117     }
118 
119     /**
120      * Returns true if <code>obj</code> is an instance of this class.
121      */
isInstance(Object obj)122     public final boolean isInstance(Object obj) {
123         return javaClass.isInstance(obj);
124     }
125 
126     /**
127      * Creates a new instance of the class.
128      *
129      * @param args              the arguments passed to the constructor.
130      */
newInstance(Object[] args)131     public final Object newInstance(Object[] args)
132         throws CannotCreateException
133     {
134         int n = constructors.length;
135         for (int i = 0; i < n; ++i) {
136             try {
137                 return constructors[i].newInstance(args);
138             }
139             catch (IllegalArgumentException e) {
140                 // try again
141             }
142             catch (InstantiationException e) {
143                 throw new CannotCreateException(e);
144             }
145             catch (IllegalAccessException e) {
146                 throw new CannotCreateException(e);
147             }
148             catch (InvocationTargetException e) {
149                 throw new CannotCreateException(e);
150             }
151         }
152 
153         throw new CannotCreateException("no constructor matches");
154     }
155 
156     /**
157      * Is invoked when <code>static</code> fields of the base-level
158      * class are read and the runtime system intercepts it.
159      * This method simply returns the value of the field.
160      *
161      * <p>Every subclass of this class should redefine this method.
162      */
trapFieldRead(String name)163     public Object trapFieldRead(String name) {
164         Class jc = getJavaClass();
165         try {
166             return jc.getField(name).get(null);
167         }
168         catch (NoSuchFieldException e) {
169             throw new RuntimeException(e.toString());
170         }
171         catch (IllegalAccessException e) {
172             throw new RuntimeException(e.toString());
173         }
174     }
175 
176     /**
177      * Is invoked when <code>static</code> fields of the base-level
178      * class are modified and the runtime system intercepts it.
179      * This method simply sets the field to the given value.
180      *
181      * <p>Every subclass of this class should redefine this method.
182      */
trapFieldWrite(String name, Object value)183     public void trapFieldWrite(String name, Object value) {
184         Class jc = getJavaClass();
185         try {
186             jc.getField(name).set(null, value);
187         }
188         catch (NoSuchFieldException e) {
189             throw new RuntimeException(e.toString());
190         }
191         catch (IllegalAccessException e) {
192             throw new RuntimeException(e.toString());
193         }
194     }
195 
196     /**
197      * Invokes a method whose name begins with
198      * <code>methodPrefix "_m_"</code> and the identifier.
199      *
200      * @exception CannotInvokeException         if the invocation fails.
201      */
invoke(Object target, int identifier, Object[] args)202     static public Object invoke(Object target, int identifier, Object[] args)
203         throws Throwable
204     {
205         Method[] allmethods = target.getClass().getMethods();
206         int n = allmethods.length;
207         String head = methodPrefix + identifier;
208         for (int i = 0; i < n; ++i)
209             if (allmethods[i].getName().startsWith(head)) {
210                 try {
211                     return allmethods[i].invoke(target, args);
212                 } catch (java.lang.reflect.InvocationTargetException e) {
213                     throw e.getTargetException();
214                 } catch (java.lang.IllegalAccessException e) {
215                     throw new CannotInvokeException(e);
216                 }
217             }
218 
219         throw new CannotInvokeException("cannot find a method");
220     }
221 
222     /**
223      * Is invoked when <code>static</code> methods of the base-level
224      * class are called and the runtime system intercepts it.
225      * This method simply executes the intercepted method invocation
226      * with the original parameters and returns the resulting value.
227      *
228      * <p>Every subclass of this class should redefine this method.
229      */
trapMethodcall(int identifier, Object[] args)230     public Object trapMethodcall(int identifier, Object[] args)
231         throws Throwable
232     {
233         try {
234             Method[] m = getReflectiveMethods();
235             return m[identifier].invoke(null, args);
236         }
237         catch (java.lang.reflect.InvocationTargetException e) {
238             throw e.getTargetException();
239         }
240         catch (java.lang.IllegalAccessException e) {
241             throw new CannotInvokeException(e);
242         }
243     }
244 
245     /**
246      * Returns an array of the methods defined on the given reflective
247      * object.  This method is for the internal use only.
248      */
getReflectiveMethods()249     public final Method[] getReflectiveMethods() {
250         if (methods != null)
251             return methods;
252 
253         Class baseclass = getJavaClass();
254         Method[] allmethods = baseclass.getDeclaredMethods();
255         int n = allmethods.length;
256         int[] index = new int[n];
257         int max = 0;
258         for (int i = 0; i < n; ++i) {
259             Method m = allmethods[i];
260             String mname = m.getName();
261             if (mname.startsWith(methodPrefix)) {
262                 int k = 0;
263                 for (int j = methodPrefixLen;; ++j) {
264                     char c = mname.charAt(j);
265                     if ('0' <= c && c <= '9')
266                         k = k * 10 + c - '0';
267                     else
268                         break;
269                 }
270 
271                 index[i] = ++k;
272                 if (k > max)
273                     max = k;
274             }
275         }
276 
277         methods = new Method[max];
278         for (int i = 0; i < n; ++i)
279             if (index[i] > 0)
280                 methods[index[i] - 1] = allmethods[i];
281 
282         return methods;
283     }
284 
285     /**
286      * Returns the <code>java.lang.reflect.Method</code> object representing
287      * the method specified by <code>identifier</code>.
288      *
289      * <p>Note that the actual method returned will be have an altered,
290      * reflective name i.e. <code>_m_2_..</code>.
291      *
292      * @param identifier        the identifier index
293      *                          given to <code>trapMethodcall()</code> etc.
294      * @see Metaobject#trapMethodcall(int,Object[])
295      * @see #trapMethodcall(int,Object[])
296      */
getMethod(int identifier)297     public final Method getMethod(int identifier) {
298         return getReflectiveMethods()[identifier];
299     }
300 
301     /**
302      * Returns the name of the method specified
303      * by <code>identifier</code>.
304      */
getMethodName(int identifier)305     public final String getMethodName(int identifier) {
306         String mname = getReflectiveMethods()[identifier].getName();
307         int j = ClassMetaobject.methodPrefixLen;
308         for (;;) {
309             char c = mname.charAt(j++);
310             if (c < '0' || '9' < c)
311                 break;
312         }
313 
314         return mname.substring(j);
315     }
316 
317     /**
318      * Returns an array of <code>Class</code> objects representing the
319      * formal parameter types of the method specified
320      * by <code>identifier</code>.
321      */
getParameterTypes(int identifier)322     public final Class[] getParameterTypes(int identifier) {
323         return getReflectiveMethods()[identifier].getParameterTypes();
324     }
325 
326     /**
327      * Returns a <code>Class</code> objects representing the
328      * return type of the method specified by <code>identifier</code>.
329      */
getReturnType(int identifier)330     public final Class getReturnType(int identifier) {
331         return getReflectiveMethods()[identifier].getReturnType();
332     }
333 
334     /**
335      * Returns the identifier index of the method, as identified by its
336      * original name.
337      *
338      * <p>This method is useful, in conjuction with
339      * <link>ClassMetaobject#getMethod()</link>, to obtain a quick reference
340      * to the original method in the reflected class (i.e. not the proxy
341      * method), using the original name of the method.
342      *
343      * <p>Written by Brett Randall and Shigeru Chiba.
344      *
345      * @param originalName      The original name of the reflected method
346      * @param argTypes          array of Class specifying the method signature
347      * @return      the identifier index of the original method
348      * @throws NoSuchMethodException    if the method does not exist
349      *
350      * @see ClassMetaobject#getMethod(int)
351      */
getMethodIndex(String originalName, Class[] argTypes)352     public final int getMethodIndex(String originalName, Class[] argTypes)
353         throws NoSuchMethodException
354     {
355         Method[] mthds = getReflectiveMethods();
356         for (int i = 0; i < mthds.length; i++) {
357             if (mthds[i] == null)
358                 continue;
359 
360             // check name and parameter types match
361             if (getMethodName(i).equals(originalName)
362                 && Arrays.equals(argTypes, mthds[i].getParameterTypes()))
363                 return i;
364         }
365 
366         throw new NoSuchMethodException("Method " + originalName
367                                         + " not found");
368     }
369 }
370