View Javadoc

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 }