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 */ 017package org.apache.commons.jexl3.internal.introspection; 018 019import org.apache.commons.logging.Log; 020 021import java.lang.reflect.Constructor; 022import java.lang.reflect.Field; 023import java.lang.reflect.Method; 024 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030 031import java.util.concurrent.locks.ReadWriteLock; 032import java.util.concurrent.locks.ReentrantReadWriteLock; 033 034/** 035 * This basic function of this class is to return a Method object for a 036 * particular class given the name of a method and the parameters to the method 037 * in the form of an Object[]. 038 * 039 * <p>The first time the Introspector sees a class it creates a class method map 040 * for the class in question. 041 * Basically the class method map is a Hashtable where Method objects are keyed by the aggregation of 042 * the method name and the array of parameters classes. 043 * This mapping is performed for all the public methods of a class and stored.</p> 044 * 045 * @since 1.0 046 */ 047public final class Introspector { 048 /** 049 * A Constructor get cache-miss. 050 */ 051 private static class CacheMiss { 052 /** The constructor used as cache-miss. */ 053 @SuppressWarnings("unused") 054 public CacheMiss() { 055 } 056 } 057 /** 058 * The cache-miss marker for the constructors map. 059 */ 060 private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0]; 061 /** 062 * the logger. 063 */ 064 protected final Log logger; 065 /** 066 * The class loader used to solve constructors if needed. 067 */ 068 private ClassLoader loader; 069 /** 070 * The permissions. 071 */ 072 private final Permissions permissions; 073 /** 074 * The read/write lock. 075 */ 076 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 077 /** 078 * Holds the method maps for the classes we know about, keyed by Class. 079 */ 080 private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>(); 081 /** 082 * Holds the map of classes ctors we know about as well as unknown ones. 083 */ 084 private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>(); 085 /** 086 * Holds the set of classes we have introspected. 087 */ 088 private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>(); 089 090 /** 091 * Create the introspector. 092 * @param log the logger to use 093 * @param cloader the class loader 094 */ 095 public Introspector(final Log log, final ClassLoader cloader) { 096 this(log, cloader, null); 097 } 098 099 /** 100 * Create the introspector. 101 * @param log the logger to use 102 * @param cloader the class loader 103 * @param perms the permissions 104 */ 105 public Introspector(final Log log, final ClassLoader cloader, final Permissions perms) { 106 this.logger = log; 107 this.loader = cloader; 108 this.permissions = perms != null? perms : Permissions.DEFAULT; 109 } 110 111 /** 112 * Gets a class by name through this introspector class loader. 113 * @param className the class name 114 * @return the class instance or null if it could not be found 115 */ 116 public Class<?> getClassByName(final String className) { 117 try { 118 return Class.forName(className, false, loader); 119 } catch (final ClassNotFoundException xignore) { 120 return null; 121 } 122 } 123 124 /** 125 * Gets a method defined by a class, a name and a set of parameters. 126 * @param c the class 127 * @param name the method name 128 * @param params the method parameters 129 * @return the desired method object 130 * @throws MethodKey.AmbiguousException if no unambiguous method could be found through introspection 131 */ 132 public Method getMethod(final Class<?> c, final String name, final Object[] params) { 133 return getMethod(c, new MethodKey(name, params)); 134 } 135 136 /** 137 * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>. 138 * 139 * @param c Class in which the method search is taking place 140 * @param key Key of the method being searched for 141 * @return The desired method object 142 * @throws MethodKey.AmbiguousException if no unambiguous method could be found through introspection 143 */ 144 public Method getMethod(final Class<?> c, final MethodKey key) { 145 try { 146 return getMap(c).getMethod(key); 147 } catch (final MethodKey.AmbiguousException xambiguous) { 148 // whoops. Ambiguous and not benign. Make a nice log message and return null... 149 if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) { 150 logger.info("ambiguous method invocation: " 151 + c.getName() + "." 152 + key.debugString(), xambiguous); 153 } 154 return null; 155 } 156 } 157 158 /** 159 * Gets the field named by <code>key</code> for the class <code>c</code>. 160 * 161 * @param c Class in which the field search is taking place 162 * @param key Name of the field being searched for 163 * @return the desired field or null if it does not exist or is not accessible 164 */ 165 public Field getField(final Class<?> c, final String key) { 166 return getMap(c).getField(key); 167 } 168 169 /** 170 * Gets the array of accessible field names known for a given class. 171 * @param c the class 172 * @return the class field names 173 */ 174 public String[] getFieldNames(final Class<?> c) { 175 if (c == null) { 176 return new String[0]; 177 } 178 final ClassMap classMap = getMap(c); 179 return classMap.getFieldNames(); 180 } 181 182 /** 183 * Gets the array of accessible methods names known for a given class. 184 * @param c the class 185 * @return the class method names 186 */ 187 public String[] getMethodNames(final Class<?> c) { 188 if (c == null) { 189 return new String[0]; 190 } 191 final ClassMap classMap = getMap(c); 192 return classMap.getMethodNames(); 193 } 194 195 /** 196 * Gets the array of accessible method known for a given class. 197 * @param c the class 198 * @param methodName the method name 199 * @return the array of methods (null or not empty) 200 */ 201 public Method[] getMethods(final Class<?> c, final String methodName) { 202 if (c == null) { 203 return null; 204 } 205 final ClassMap classMap = getMap(c); 206 return classMap.getMethods(methodName); 207 } 208 209 /** 210 * Gets the constructor defined by the <code>MethodKey</code>. 211 * 212 * @param key Key of the constructor being searched for 213 * @return The desired constructor object 214 * or null if no unambiguous constructor could be found through introspection. 215 */ 216 public Constructor<?> getConstructor(final MethodKey key) { 217 return getConstructor(null, key); 218 } 219 220 /** 221 * Gets the constructor defined by the <code>MethodKey</code>. 222 * @param c the class we want to instantiate 223 * @param key Key of the constructor being searched for 224 * @return The desired constructor object 225 * or null if no unambiguous constructor could be found through introspection. 226 */ 227 public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) { 228 Constructor<?> ctor; 229 lock.readLock().lock(); 230 try { 231 ctor = constructorsMap.get(key); 232 if (ctor != null) { 233 // miss or not? 234 return CTOR_MISS.equals(ctor) ? null : ctor; 235 } 236 } finally { 237 lock.readLock().unlock(); 238 } 239 // let's introspect... 240 lock.writeLock().lock(); 241 try { 242 // again for kicks 243 ctor = constructorsMap.get(key); 244 if (ctor != null) { 245 // miss or not? 246 return CTOR_MISS.equals(ctor) ? null : ctor; 247 } 248 final String cname = key.getMethod(); 249 // do we know about this class? 250 Class<?> clazz = constructibleClasses.get(cname); 251 try { 252 // do find the most specific ctor 253 if (clazz == null) { 254 if (c != null && c.getName().equals(key.getMethod())) { 255 clazz = c; 256 } else { 257 clazz = loader.loadClass(cname); 258 } 259 // add it to list of known loaded classes 260 constructibleClasses.put(cname, clazz); 261 } 262 final List<Constructor<?>> l = new ArrayList<>(); 263 for (final Constructor<?> ictor : clazz.getConstructors()) { 264 if (permissions.allow(ictor)) { 265 l.add(ictor); 266 } 267 } 268 // try to find one 269 ctor = key.getMostSpecificConstructor(l.toArray(new Constructor<?>[l.size()])); 270 if (ctor != null) { 271 constructorsMap.put(key, ctor); 272 } else { 273 constructorsMap.put(key, CTOR_MISS); 274 } 275 } catch (final ClassNotFoundException xnotfound) { 276 if (logger != null && logger.isDebugEnabled()) { 277 logger.debug("unable to find class: " 278 + cname + "." 279 + key.debugString(), xnotfound); 280 } 281 ctor = null; 282 } catch (final MethodKey.AmbiguousException xambiguous) { 283 if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) { 284 logger.info("ambiguous constructor invocation: " 285 + cname + "." 286 + key.debugString(), xambiguous); 287 } 288 ctor = null; 289 } 290 return ctor; 291 } finally { 292 lock.writeLock().unlock(); 293 } 294 } 295 296 /** 297 * Gets the ClassMap for a given class. 298 * @param c the class 299 * @return the class map 300 */ 301 private ClassMap getMap(final Class<?> c) { 302 ClassMap classMap; 303 lock.readLock().lock(); 304 try { 305 classMap = classMethodMaps.get(c); 306 } finally { 307 lock.readLock().unlock(); 308 } 309 if (classMap == null) { 310 lock.writeLock().lock(); 311 try { 312 // try again 313 classMap = classMethodMaps.get(c); 314 if (classMap == null) { 315 classMap = new ClassMap(c, permissions, logger); 316 classMethodMaps.put(c, classMap); 317 } 318 } finally { 319 lock.writeLock().unlock(); 320 } 321 322 } 323 return classMap; 324 } 325 326 /** 327 * Sets the class loader used to solve constructors. 328 * <p>Also cleans the constructors and methods caches.</p> 329 * @param cloader the class loader; if null, use this instance class loader 330 */ 331 public void setLoader(ClassLoader cloader) { 332 final ClassLoader previous = loader; 333 if (cloader == null) { 334 cloader = getClass().getClassLoader(); 335 } 336 if (!cloader.equals(loader)) { 337 lock.writeLock().lock(); 338 try { 339 // clean up constructor and class maps 340 final Iterator<Map.Entry<MethodKey, Constructor<?>>> mentries = constructorsMap.entrySet().iterator(); 341 while (mentries.hasNext()) { 342 final Map.Entry<MethodKey, Constructor<?>> entry = mentries.next(); 343 final Class<?> clazz = entry.getValue().getDeclaringClass(); 344 if (isLoadedBy(previous, clazz)) { 345 mentries.remove(); 346 // the method name is the name of the class 347 constructibleClasses.remove(entry.getKey().getMethod()); 348 } 349 } 350 // clean up method maps 351 final Iterator<Map.Entry<Class<?>, ClassMap>> centries = classMethodMaps.entrySet().iterator(); 352 while (centries.hasNext()) { 353 final Map.Entry<Class<?>, ClassMap> entry = centries.next(); 354 final Class<?> clazz = entry.getKey(); 355 if (isLoadedBy(previous, clazz)) { 356 centries.remove(); 357 } 358 } 359 loader = cloader; 360 } finally { 361 lock.writeLock().unlock(); 362 } 363 } 364 } 365 366 /** 367 * Gets the class loader used by this introspector. 368 * @return the class loader 369 */ 370 public ClassLoader getLoader() { 371 return loader; 372 } 373 374 /** 375 * Checks whether a class is loaded through a given class loader or one of its ascendants. 376 * @param loader the class loader 377 * @param clazz the class to check 378 * @return true if clazz was loaded through the loader, false otherwise 379 */ 380 private static boolean isLoadedBy(final ClassLoader loader, final Class<?> clazz) { 381 if (loader != null) { 382 ClassLoader cloader = clazz.getClassLoader(); 383 while (cloader != null) { 384 if (cloader.equals(loader)) { 385 return true; 386 } 387 cloader = cloader.getParent(); 388 } 389 } 390 return false; 391 } 392}