View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl2.internal.introspection;
18  
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.Field;
22  import java.util.Map;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.LinkedList;
26  import java.util.List;
27  
28  import org.apache.commons.logging.Log;
29  
30  /**
31   * This basic function of this class is to return a Method object for a
32   * particular class given the name of a method and the parameters to the method
33   * in the form of an Object[]
34   * <p/>
35   * The first time the Introspector sees a class it creates a class method map
36   * for the class in question. Basically the class method map is a Hastable where
37   * Method objects are keyed by a concatenation of the method name and the names
38   * of classes that make up the parameters.
39   *
40   * For example, a method with the following signature:
41   *
42   * public void method(String a, StringBuffer b)
43   *
44   * would be mapped by the key:
45   *
46   * "method" + "java.lang.String" + "java.lang.StringBuffer"
47   *
48   * This mapping is performed for all the methods in a class and stored.
49   * @since 1.0
50   */
51  public class IntrospectorBase {
52      /** the logger. */
53      protected final Log rlog;
54      /**
55       * Holds the method maps for the classes we know about, keyed by Class.
56       */
57      private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>();
58      /**
59       * The class loader used to solve constructors if needed.
60       */
61      private ClassLoader loader;
62      /**
63       * Holds the map of classes ctors we know about as well as unknown ones.
64       */
65      private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>();
66      /**
67       * Holds the set of classes we have introspected.
68       */
69      private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>();
70  
71      /**
72       * Create the introspector.
73       * @param log the logger to use
74       */
75      public IntrospectorBase(Log log) {
76          this.rlog = log;
77          loader = getClass().getClassLoader();
78      }
79  
80      /**
81       * Gets a class by name through this introspector class loader.
82       * @param className the class name
83       * @return the class instance or null if it could not be found
84       */
85      public Class<?> getClassByName(String className) {
86          try {
87              return Class.forName(className, false, loader);
88          } catch (ClassNotFoundException xignore) {
89              return null;
90          }
91      }
92  
93      /**
94       * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>.
95       *
96       * @param c     Class in which the method search is taking place
97       * @param key   Key of the method being searched for
98       * @return The desired method object
99       * @throws MethodKey.AmbiguousException if no unambiguous method could be found through introspection
100      */
101     public Method getMethod(Class<?> c, MethodKey key) {
102         try {
103             ClassMap classMap = getMap(c);
104             return classMap.findMethod(key);
105         } catch (MethodKey.AmbiguousException xambiguous) {
106             // whoops.  Ambiguous.  Make a nice log message and return null...
107             if (rlog != null && rlog.isInfoEnabled()) {
108                 rlog.info("ambiguous method invocation: "
109                         + c.getName() + "."
110                         + key.debugString(), xambiguous);
111             }
112             return null;
113         }
114     }
115 
116     /**
117      * Gets the field named by <code>key</code> for the class <code>c</code>.
118      *
119      * @param c     Class in which the field search is taking place
120      * @param key   Name of the field being searched for
121      * @return the desired field or null if it does not exist or is not accessible
122      * */
123     public Field getField(Class<?> c, String key) {
124         ClassMap classMap = getMap(c);
125         return classMap.findField(c, key);
126     }
127 
128     /**
129      * Gets the array of accessible field names known for a given class.
130      * @param c the class
131      * @return the class field names
132      */
133     public String[] getFieldNames(Class<?> c) {
134         if (c == null) {
135             return new String[0];
136         }
137         ClassMap classMap = getMap(c);
138         return classMap.getFieldNames();
139     }
140 
141     /**
142      * Gets the array of accessible methods names known for a given class.
143      * @param c the class
144      * @return the class method names
145      */
146     public String[] getMethodNames(Class<?> c) {
147         if (c == null) {
148             return new String[0];
149         }
150         ClassMap classMap = getMap(c);
151         return classMap.getMethodNames();
152     }
153 
154     /**
155      * Gets the array of accessible method known for a given class.
156      * @param c the class
157      * @param methodName the method name
158      * @return the array of methods (null or not empty)
159      */
160     public Method[] getMethods(Class<?> c, String methodName) {
161         if (c == null) {
162             return null;
163         }
164         ClassMap classMap = getMap(c);
165         return classMap.get(methodName);
166     }
167 
168     /**
169      * A Constructor get cache-miss.
170      */
171     private static class CacheMiss {
172         /** The constructor used as cache-miss. */
173         @SuppressWarnings("unused")
174         public CacheMiss() {}
175     }
176     
177     /** The cache-miss marker for the constructors map. */
178     private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0];
179 
180     /**
181      * Sets the class loader used to solve constructors.
182      * <p>Also cleans the constructors and methods caches.</p>
183      * @param cloader the class loader; if null, use this instance class loader
184      */
185     public void setLoader(ClassLoader cloader) {
186         ClassLoader previous = loader;
187         if (cloader == null) {
188             cloader = getClass().getClassLoader();
189         }
190         if (!cloader.equals(loader)) {
191             // clean up constructor and class maps
192             synchronized (constructorsMap) {
193                 Iterator<Map.Entry<MethodKey, Constructor<?>>> entries = constructorsMap.entrySet().iterator();
194                 while (entries.hasNext()) {
195                     Map.Entry<MethodKey, Constructor<?>> entry = entries.next();
196                     Class<?> clazz = entry.getValue().getDeclaringClass();
197                     if (isLoadedBy(previous, clazz)) {
198                         entries.remove();
199                         // the method name is the name of the class
200                         constructibleClasses.remove(entry.getKey().getMethod());
201                     }
202                 }
203             }
204             // clean up method maps
205             synchronized (classMethodMaps) {
206                 Iterator<Map.Entry<Class<?>, ClassMap>> entries = classMethodMaps.entrySet().iterator();
207                 while (entries.hasNext()) {
208                     Map.Entry<Class<?>, ClassMap> entry = entries.next();
209                     Class<?> clazz = entry.getKey();
210                     if (isLoadedBy(previous, clazz)) {
211                         entries.remove();
212                     }
213                 }
214             }
215             loader = cloader;
216         }
217     }
218 
219     /**
220      * Checks whether a class is loaded through a given class loader or one of its ascendants.
221      * @param loader the class loader
222      * @param clazz the class to check
223      * @return true if clazz was loaded through the loader, false otherwise
224      */
225     private static boolean isLoadedBy(ClassLoader loader, Class<?> clazz) {
226         if (loader != null) {
227             ClassLoader cloader = clazz.getClassLoader();
228             while (cloader != null) {
229                 if (cloader.equals(loader)) {
230                     return true;
231                 } else {
232                     cloader = cloader.getParent();
233                 }
234             }
235         }
236         return false;
237     }
238 
239     /**
240      * Gets the constructor defined by the <code>MethodKey</code>.
241      *
242      * @param key   Key of the constructor being searched for
243      * @return The desired constructor object
244      * or null if no unambiguous constructor could be found through introspection.
245      */
246     public Constructor<?> getConstructor(final MethodKey key) {
247         return getConstructor(null, key);
248     }
249 
250     /**
251      * Gets the constructor defined by the <code>MethodKey</code>.
252      * @param c the class we want to instantiate
253      * @param key   Key of the constructor being searched for
254      * @return The desired constructor object
255      * or null if no unambiguous constructor could be found through introspection.
256      */
257     public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) {
258         Constructor<?> ctor = null;
259         synchronized (constructorsMap) {
260             ctor = constructorsMap.get(key);
261             // that's a clear miss
262             if (CTOR_MISS.equals(ctor)) {
263                 return null;
264             }
265             // let's introspect...
266             if (ctor == null) {
267                 final String cname = key.getMethod();
268                 // do we know about this class?
269                 Class<?> clazz = constructibleClasses.get(cname);
270                 try {
271                     // do find the most specific ctor
272                     if (clazz == null) {
273                         if (c != null && c.getName().equals(key.getMethod())) {
274                             clazz = c;
275                         } else {
276                             clazz = loader.loadClass(cname);
277                         }
278                         // add it to list of known loaded classes
279                         constructibleClasses.put(cname, clazz);
280                     }
281                     List<Constructor<?>> l = new LinkedList<Constructor<?>>();
282                     for (Constructor<?> ictor : clazz.getConstructors()) {
283                         l.add(ictor);
284                     }
285                     // try to find one
286                     ctor = key.getMostSpecificConstructor(l);
287                     if (ctor != null) {
288                         constructorsMap.put(key, ctor);
289                     } else {
290                         constructorsMap.put(key, CTOR_MISS);
291                     }
292                 } catch (ClassNotFoundException xnotfound) {
293                     if (rlog != null && rlog.isInfoEnabled()) {
294                         rlog.info("unable to find class: "
295                                 + cname + "."
296                                 + key.debugString(), xnotfound);
297                     }
298                     ctor = null;
299                 } catch (MethodKey.AmbiguousException xambiguous) {
300                     if (rlog != null && rlog.isInfoEnabled()) {
301                         rlog.info("ambiguous constructor invocation: "
302                                 + cname + "."
303                                 + key.debugString(), xambiguous);
304                     }
305                     ctor = null;
306                 }
307             }
308             return ctor;
309         }
310     }
311 
312     /**
313      * Gets the ClassMap for a given class.
314      * @param c the class
315      * @return the class map
316      */
317     private ClassMap getMap(Class<?> c) {
318         synchronized (classMethodMaps) {
319             ClassMap classMap = classMethodMaps.get(c);
320             if (classMap == null) {
321                 classMap = new ClassMap(c, rlog);
322                 classMethodMaps.put(c, classMap);
323             }
324             return classMap;
325         }
326     }
327 }