001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.jexl2.internal.introspection; 018 019 import java.lang.reflect.Method; 020 import java.lang.reflect.Constructor; 021 import java.lang.reflect.Field; 022 import java.util.Map; 023 import java.util.HashMap; 024 import java.util.Iterator; 025 import java.util.LinkedList; 026 import java.util.List; 027 028 import org.apache.commons.logging.Log; 029 030 /** 031 * This basic function of this class is to return a Method object for a 032 * particular class given the name of a method and the parameters to the method 033 * in the form of an Object[] 034 * <p/> 035 * The first time the Introspector sees a class it creates a class method map 036 * for the class in question. Basically the class method map is a Hastable where 037 * Method objects are keyed by a concatenation of the method name and the names 038 * of classes that make up the parameters. 039 * 040 * For example, a method with the following signature: 041 * 042 * public void method(String a, StringBuffer b) 043 * 044 * would be mapped by the key: 045 * 046 * "method" + "java.lang.String" + "java.lang.StringBuffer" 047 * 048 * This mapping is performed for all the methods in a class and stored. 049 * @since 1.0 050 */ 051 public class IntrospectorBase { 052 /** the logger. */ 053 protected final Log rlog; 054 /** 055 * Holds the method maps for the classes we know about, keyed by Class. 056 */ 057 private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>(); 058 /** 059 * The class loader used to solve constructors if needed. 060 */ 061 private ClassLoader loader; 062 /** 063 * Holds the map of classes ctors we know about as well as unknown ones. 064 */ 065 private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>(); 066 /** 067 * Holds the set of classes we have introspected. 068 */ 069 private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>(); 070 071 /** 072 * Create the introspector. 073 * @param log the logger to use 074 */ 075 public IntrospectorBase(Log log) { 076 this.rlog = log; 077 loader = getClass().getClassLoader(); 078 } 079 080 /** 081 * Gets a class by name through this introspector class loader. 082 * @param className the class name 083 * @return the class instance or null if it could not be found 084 */ 085 public Class<?> getClassByName(String className) { 086 try { 087 return Class.forName(className, false, loader); 088 } catch (ClassNotFoundException xignore) { 089 return null; 090 } 091 } 092 093 /** 094 * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>. 095 * 096 * @param c Class in which the method search is taking place 097 * @param key Key of the method being searched for 098 * @return The desired method object 099 * @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 }