1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.orchestra.dynaForm.metadata.impl.ejb;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.util.ArrayList;
27 import java.util.List;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.myfaces.orchestra.lib.OrchestraException;
32 import org.objectweb.asm.ClassReader;
33 import org.objectweb.asm.ClassVisitor;
34 import org.objectweb.asm.FieldVisitor;
35 import org.objectweb.asm.MethodVisitor;
36
37 /**
38 * Reimplement the reflection facilities provided by Class.class, but preserving the order in which
39 * the properties exist within the .class file.
40 * <p>
41 * Presumably the field order in the .class file is the same as the order they are declared within
42 * the source file. And presumably the code author arranged those in some logical order.
43 * <p>
44 * This class uses the ASM library to inspect the class data.
45 */
46 public class AsmHelper implements ClassHelper
47 {
48 /**
49 * Alas, the ASM project broke binary compatibility between the 2.x and 3.x
50 * release for no good reason. In order to be compatible with either version
51 * of ASM, we need to use reflection to invoke ClassReader.accept.
52 * <p>
53 * In 2.x releases, we must call ClassReader.accept(classVisitor, boolean).
54 * In 3.x releases we must call ClassReader.accept(classVisitor, int).
55 * <p>
56 * In both cases, the second arg is a constant value (though of different type).
57 * <p>
58 * This class therefore supports checking what ASM version is in the classpath
59 * and handles invoking the appropriate method variant.
60 * <p>
61 * The code below does not support ASM 1.x.
62 */
63 private static class AcceptMethodInfo
64 {
65 private final Method method;
66 private final Object arg;
67
68 AcceptMethodInfo(Method method, Object arg)
69 {
70 this.method = method;
71 this.arg = arg;
72 }
73
74 private void invoke(ClassReader instance, Object visitor)
75 throws IOException, IllegalAccessException, InvocationTargetException
76 {
77 Object[] args = new Object[2];
78 args[0] = visitor;
79 args[1] = arg;
80 method.invoke(instance, args);
81 }
82
83 private static AcceptMethodInfo getInstance() throws ExceptionInInitializerError
84 {
85 // signature for ASM 3.x
86 Class[] argsForm2 = new Class[] { ClassVisitor.class, Integer.TYPE };
87 try
88 {
89 Method m = ClassReader.class.getMethod("accept", argsForm2);
90 // ok, we are running with asm 3.0.x, 3.1.x or compatible
91 // flags=SKIP_DEBUG|SKIP_CODE|SKIP_FRAMES
92 Integer flags = Integer.valueOf(1 | 2 | 4);
93 return new AcceptMethodInfo(m, flags);
94 }
95 catch (NoSuchMethodException e)
96 {
97 // ok
98 }
99
100 // signature for ASM 1.x and 2.x
101 Class[] argsForm1 = new Class[] { ClassVisitor.class, Boolean.TYPE };
102 try
103 {
104 Method m = ClassReader.class.getMethod("accept", argsForm1);
105 // ok, we are running with asm 1.x or 2.x
106 // skipDebug=true
107 return new AcceptMethodInfo(m, Boolean.TRUE);
108 }
109 catch (NoSuchMethodException e)
110 {
111 // ok
112 }
113
114 throw new ExceptionInInitializerError(
115 "AsmHelper cannot find compatible ClassReader.access method");
116 }
117 }
118
119 private final Log log = LogFactory.getLog(AsmHelper.class);
120 private static final Field[] FIELDS_TYPE = new Field[0];
121 private static final Method[] METHODS_TYPE = new Method[0];
122 private static final AcceptMethodInfo ACCEPT_METHOD_INFO = AcceptMethodInfo.getInstance();
123
124 private void applyVisitorToClass(Class clazz, ClassVisitor visitor)
125 {
126 // Note: Simply using the constructor ClassReader(clazz.getName())
127 // does not allow control over which classloader the class is loaded
128 // from.
129 String className = clazz.getName().replace('.', '/') + ".class";
130 ClassReader cr = null;
131 InputStream is = null;
132 try
133 {
134 is = this.getClass().getClassLoader().getResourceAsStream(className);
135 cr = new ClassReader(is);
136
137 // Invoke ClassReader.accept(ClassVisitor, ?).
138 // See method initAsm for further details...
139 ACCEPT_METHOD_INFO.invoke(cr, visitor);
140 }
141 catch (IOException e)
142 {
143 throw new OrchestraException("Unable to read class " + className, e);
144 }
145 catch(InvocationTargetException e)
146 {
147 // should never happen
148 throw new OrchestraException("Unable to read class " + className, e);
149 }
150 catch(IllegalAccessException e)
151 {
152 // should never happen
153 throw new OrchestraException("Unable to read class " + className, e);
154 }
155 finally
156 {
157 if (is != null)
158 {
159 try
160 {
161 is.close();
162 }
163 catch (IOException e2)
164 {
165 // Not likely to occur, and nothing can be done about it.
166 log.error("Unable to close input stream", e2);
167 }
168 }
169 }
170 }
171
172 /**
173 * Return a list of all the static and non-static fields of the specified class, regardless of
174 * their accessability.
175 * <p>
176 * The array order matches the order in which the fields were declared within the original
177 * source file.
178 */
179 public Field[] getFields(final Class<?> clazz)
180 {
181 final List<Field> fields = new ArrayList<Field>(50);
182 ClassVisitor fv = new EmptyClassVisitor()
183 {
184 @Override
185 public FieldVisitor visitField(int access, String name, String desc, String signature,
186 Object value)
187 {
188 try
189 {
190 // Note: no two fields can have the same name, so we don't need to check
191 // whether the found field is already in the fields list.
192 Field f = clazz.getDeclaredField(name);
193 fields.add(f);
194 }
195 catch (NoSuchFieldException e)
196 {
197 log.warn("Cannot find Field object for " + name);
198 }
199 return super.visitField(access, name, desc, signature, value);
200 }
201 };
202 applyVisitorToClass(clazz, fv);
203 return fields.toArray(FIELDS_TYPE);
204 }
205
206 /**
207 * Given an ASM method descriptor, find the associated java.lang.reflect.Method object.
208 * <p>
209 * Returns null if no such method is found.
210 */
211 private Method getMethod(Class<?> clazz, String methodName, String descriptor)
212 {
213 // TODO: this looping through all methods and calling getMethodDescriptor
214 // on each one is not very efficient. It might be nice to build all the
215 // descriptor->Method mappings once and cache them in a map. The question
216 // then is how long to hold onto that cached data though. And where to
217 // store it, as keeping the cache in a parent classloader will prevent unloading
218 // of any of the classes that those Method objects are on, unless weak
219 // references are used. For now, keep things simple..
220 Method[] methods = clazz.getDeclaredMethods();
221 for (Method m : methods)
222 {
223 if (m.getName().equals(methodName))
224 {
225 String thisMethodDesc = org.objectweb.asm.Type.getMethodDescriptor(m);
226 if (thisMethodDesc.equals(descriptor))
227 {
228 return m;
229 }
230 }
231 }
232
233 return null;
234 }
235
236 /**
237 * Return a list of all the get*, set* and is* method on the specified class, regardless of
238 * their accessability.
239 * <p>
240 * The array order matches the order in which the methods were declared within the original
241 * source file.
242 */
243 public Method[] getMethods(final Class<?> clazz)
244 {
245 final List<Method> methods = new ArrayList<Method>(50);
246 ClassVisitor fv = new EmptyClassVisitor()
247 {
248 @Override
249 public MethodVisitor visitMethod(int access, String name, String descriptor,
250 String signature, String[] exceptions)
251 {
252 // Note that "descriptor" is like signature, except without any info about
253 // java15 generic type params. When a visited method has no generic types
254 // in its prototype, then param signature=null.
255 //
256 // As there can never be two methods with the same "descriptor", we just
257 // use that to locate the real Method object.
258 if (name.startsWith("set") || name.startsWith("get") || name.startsWith("is"))
259 {
260 Method m = getMethod(clazz, name, descriptor);
261 if ((m != null) && !methods.contains(m))
262 {
263 methods.add(m);
264 }
265 }
266 return super.visitMethod(access, name, descriptor, signature, exceptions);
267 }
268
269 };
270 applyVisitorToClass(clazz, fv);
271 return methods.toArray(METHODS_TYPE);
272 }
273 }