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
16package javassist.tools.reflect;
17
18import java.lang.reflect.*;
19import java.util.Arrays;
20import java.io.Serializable;
21import java.io.IOException;
22import java.io.ObjectInputStream;
23import 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 */
41public 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     */
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
85    private void writeObject(ObjectOutputStream out) throws IOException {
86        out.writeUTF(javaClass.getName());
87    }
88
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
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     */
108    public final Class getJavaClass() {
109        return javaClass;
110    }
111
112    /**
113     * Obtains the name of this class.
114     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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