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.jexl3.JexlArithmetic; 020import org.apache.commons.jexl3.JexlEngine; 021import org.apache.commons.jexl3.JexlOperator; 022import org.apache.commons.jexl3.introspection.JexlMethod; 023import org.apache.commons.jexl3.introspection.JexlPropertyGet; 024import org.apache.commons.jexl3.introspection.JexlPropertySet; 025import org.apache.commons.jexl3.introspection.JexlUberspect; 026 027import org.apache.commons.logging.Log; 028 029import java.lang.reflect.Field; 030import java.lang.reflect.Method; 031 032import java.util.EnumSet; 033import java.util.Enumeration; 034import java.util.Iterator; 035import java.util.Map; 036import java.util.Set; 037import java.util.concurrent.atomic.AtomicInteger; 038import java.util.concurrent.ConcurrentHashMap; 039 040import java.lang.ref.Reference; 041import java.lang.ref.SoftReference; 042import java.util.List; 043 044/** 045 * Implementation of Uberspect to provide the default introspective 046 * functionality of JEXL. 047 * <p> 048 * This is the class to derive to customize introspection.</p> 049 * 050 * @since 1.0 051 */ 052public class Uberspect implements JexlUberspect { 053 /** Publicly exposed special failure object returned by tryInvoke. */ 054 public static final Object TRY_FAILED = JexlEngine.TRY_FAILED; 055 /** The logger to use for all warnings and errors. */ 056 protected final Log logger; 057 /** The resolver strategy. */ 058 private final JexlUberspect.ResolverStrategy strategy; 059 /** The permissions. */ 060 private final Permissions permissions; 061 /** The introspector version. */ 062 private final AtomicInteger version; 063 /** The soft reference to the introspector currently in use. */ 064 private volatile Reference<Introspector> ref; 065 /** The class loader reference; used to recreate the introspector when necessary. */ 066 private volatile Reference<ClassLoader> loader; 067 /** 068 * The map from arithmetic classes to overloaded operator sets. 069 * <p> 070 * This keeps track of which operator methods are overloaded per JexlArithemtic classes 071 * allowing a fail fast test during interpretation by avoiding seeking a method when there is none. 072 */ 073 private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap; 074 075 /** 076 * Creates a new Uberspect. 077 * @param runtimeLogger the logger used for all logging needs 078 * @param sty the resolver strategy 079 */ 080 public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty) { 081 this(runtimeLogger, sty, null); 082 } 083 084 /** 085 * Creates a new Uberspect. 086 * @param runtimeLogger the logger used for all logging needs 087 * @param sty the resolver strategy 088 * @param perms the introspector permissions 089 */ 090 public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty, final Permissions perms) { 091 logger = runtimeLogger; 092 strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty; 093 permissions = perms; 094 ref = new SoftReference<Introspector>(null); 095 loader = new SoftReference<ClassLoader>(getClass().getClassLoader()); 096 operatorMap = new ConcurrentHashMap<Class<? extends JexlArithmetic>, Set<JexlOperator>>(); 097 version = new AtomicInteger(0); 098 } 099 100 /** 101 * Gets the current introspector base. 102 * <p> 103 * If the reference has been collected, this method will recreate the underlying introspector.</p> 104 * @return the introspector 105 */ 106 // CSOFF: DoubleCheckedLocking 107 protected final Introspector base() { 108 Introspector intro = ref.get(); 109 if (intro == null) { 110 // double checked locking is ok (fixed by Java 5 memory model). 111 synchronized (this) { 112 intro = ref.get(); 113 if (intro == null) { 114 intro = new Introspector(logger, loader.get(), permissions); 115 ref = new SoftReference<Introspector>(intro); 116 loader = new SoftReference<ClassLoader>(intro.getLoader()); 117 version.incrementAndGet(); 118 } 119 } 120 } 121 return intro; 122 } 123 // CSON: DoubleCheckedLocking 124 125 @Override 126 public void setClassLoader(final ClassLoader nloader) { 127 synchronized (this) { 128 Introspector intro = ref.get(); 129 if (intro != null) { 130 intro.setLoader(nloader); 131 } else { 132 intro = new Introspector(logger, nloader, permissions); 133 ref = new SoftReference<Introspector>(intro); 134 } 135 loader = new SoftReference<ClassLoader>(intro.getLoader()); 136 operatorMap.clear(); 137 version.incrementAndGet(); 138 } 139 } 140 141 @Override 142 public ClassLoader getClassLoader() { 143 return loader.get(); 144 } 145 146 @Override 147 public int getVersion() { 148 return version.intValue(); 149 } 150 151 /** 152 * Gets a class by name through this introspector class loader. 153 * @param className the class name 154 * @return the class instance or null if it could not be found 155 */ 156 public final Class<?> getClassByName(final String className) { 157 return base().getClassByName(className); 158 } 159 160 /** 161 * Gets the field named by 162 * <code>key</code> for the class 163 * <code>c</code>. 164 * 165 * @param c Class in which the field search is taking place 166 * @param key Name of the field being searched for 167 * @return a {@link java.lang.reflect.Field} or null if it does not exist or is not accessible 168 */ 169 public final Field getField(final Class<?> c, final String key) { 170 return base().getField(c, key); 171 } 172 173 /** 174 * Gets the accessible field names known for a given class. 175 * @param c the class 176 * @return the class field names 177 */ 178 public final String[] getFieldNames(final Class<?> c) { 179 return base().getFieldNames(c); 180 } 181 182 /** 183 * Gets the method defined by 184 * <code>name</code> and 185 * <code>params</code> for the Class 186 * <code>c</code>. 187 * 188 * @param c Class in which the method search is taking place 189 * @param name Name of the method being searched for 190 * @param params An array of Objects (not Classes) that describe the 191 * the parameters 192 * 193 * @return a {@link java.lang.reflect.Method} 194 * or null if no unambiguous method could be found through introspection. 195 */ 196 public final Method getMethod(final Class<?> c, final String name, final Object[] params) { 197 return base().getMethod(c, new MethodKey(name, params)); 198 } 199 200 /** 201 * Gets the method defined by 202 * <code>key</code> and for the Class 203 * <code>c</code>. 204 * 205 * @param c Class in which the method search is taking place 206 * @param key MethodKey of the method being searched for 207 * 208 * @return a {@link java.lang.reflect.Method} 209 * or null if no unambiguous method could be found through introspection. 210 */ 211 public final Method getMethod(final Class<?> c, final MethodKey key) { 212 return base().getMethod(c, key); 213 } 214 215 /** 216 * Gets the accessible methods names known for a given class. 217 * @param c the class 218 * @return the class method names 219 */ 220 public final String[] getMethodNames(final Class<?> c) { 221 return base().getMethodNames(c); 222 } 223 224 /** 225 * Gets all the methods with a given name from this map. 226 * @param c the class 227 * @param methodName the seeked methods name 228 * @return the array of methods 229 */ 230 public final Method[] getMethods(final Class<?> c, final String methodName) { 231 return base().getMethods(c, methodName); 232 } 233 234 @Override 235 public JexlMethod getMethod(final Object obj, final String method, final Object... args) { 236 return MethodExecutor.discover(base(), obj, method, args); 237 } 238 239 @Override 240 public List<PropertyResolver> getResolvers(final JexlOperator op, final Object obj) { 241 return strategy.apply(op, obj); 242 } 243 244 @Override 245 public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) { 246 return getPropertyGet(null, obj, identifier); 247 } 248 249 @Override 250 public JexlPropertyGet getPropertyGet( 251 final List<PropertyResolver> resolvers, final Object obj, final Object identifier 252 ) { 253 final Class<?> claz = obj.getClass(); 254 final String property = AbstractExecutor.castString(identifier); 255 final Introspector is = base(); 256 final List<PropertyResolver> r = resolvers == null? strategy.apply(null, obj) : resolvers; 257 JexlPropertyGet executor = null; 258 for (final PropertyResolver resolver : r) { 259 if (resolver instanceof JexlResolver) { 260 switch ((JexlResolver) resolver) { 261 case PROPERTY: 262 // first try for a getFoo() type of property (also getfoo() ) 263 executor = PropertyGetExecutor.discover(is, claz, property); 264 if (executor == null) { 265 executor = BooleanGetExecutor.discover(is, claz, property); 266 } 267 break; 268 case MAP: 269 // let's see if we are a map... 270 executor = MapGetExecutor.discover(is, claz, identifier); 271 break; 272 case LIST: 273 // let's see if this is a list or array 274 final Integer index = AbstractExecutor.castInteger(identifier); 275 if (index != null) { 276 executor = ListGetExecutor.discover(is, claz, index); 277 } 278 break; 279 case DUCK: 280 // if that didn't work, look for get(foo) 281 executor = DuckGetExecutor.discover(is, claz, identifier); 282 if (executor == null && property != null && property != identifier) { 283 // look for get("foo") if we did not try yet (just above) 284 executor = DuckGetExecutor.discover(is, claz, property); 285 } 286 break; 287 case FIELD: 288 // a field may be? (can not be a number) 289 executor = FieldGetExecutor.discover(is, claz, property); 290 // static class fields (enums included) 291 if (obj instanceof Class<?>) { 292 executor = FieldGetExecutor.discover(is, (Class<?>) obj, property); 293 } 294 break; 295 case CONTAINER: 296 // or an indexed property? 297 executor = IndexedType.discover(is, obj, property); 298 break; 299 default: 300 continue; // in case we add new ones in enum 301 } 302 } else { 303 executor = resolver.getPropertyGet(this, obj, identifier); 304 } 305 if (executor != null) { 306 return executor; 307 } 308 } 309 return null; 310 } 311 312 @Override 313 public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) { 314 return getPropertySet(null, obj, identifier, arg); 315 } 316 317 @Override 318 public JexlPropertySet getPropertySet( 319 final List<PropertyResolver> resolvers, final Object obj, final Object identifier, final Object arg 320 ) { 321 final Class<?> claz = obj.getClass(); 322 final String property = AbstractExecutor.castString(identifier); 323 final Introspector is = base(); 324 final List<PropertyResolver> actual = resolvers == null? strategy.apply(null, obj) : resolvers; 325 JexlPropertySet executor = null; 326 for (final PropertyResolver resolver : actual) { 327 if (resolver instanceof JexlResolver) { 328 switch ((JexlResolver) resolver) { 329 case PROPERTY: 330 // first try for a setFoo() type of property (also setfoo() ) 331 executor = PropertySetExecutor.discover(is, claz, property, arg); 332 break; 333 case MAP: 334 // let's see if we are a map... 335 executor = MapSetExecutor.discover(is, claz, identifier, arg); 336 break; 337 case LIST: 338 // let's see if we can convert the identifier to an int, 339 // if obj is an array or a list, we can still do something 340 final Integer index = AbstractExecutor.castInteger(identifier); 341 if (index != null) { 342 executor = ListSetExecutor.discover(is, claz, identifier, arg); 343 } 344 break; 345 case DUCK: 346 // if that didn't work, look for set(foo) 347 executor = DuckSetExecutor.discover(is, claz, identifier, arg); 348 if (executor == null && property != null && property != identifier) { 349 executor = DuckSetExecutor.discover(is, claz, property, arg); 350 } 351 break; 352 case FIELD: 353 // a field may be? 354 executor = FieldSetExecutor.discover(is, claz, property, arg); 355 break; 356 case CONTAINER: 357 default: 358 continue; // in case we add new ones in enum 359 } 360 } else { 361 executor = resolver.getPropertySet(this, obj, identifier, arg); 362 } 363 if (executor != null) { 364 return executor; 365 } 366 } 367 return null; 368 } 369 370 @Override 371 @SuppressWarnings("unchecked") 372 public Iterator<?> getIterator(final Object obj) { 373 if (obj instanceof Iterator<?>) { 374 return ((Iterator<?>) obj); 375 } 376 if (obj.getClass().isArray()) { 377 return new ArrayIterator(obj); 378 } 379 if (obj instanceof Map<?, ?>) { 380 return ((Map<?, ?>) obj).values().iterator(); 381 } 382 if (obj instanceof Enumeration<?>) { 383 return new EnumerationIterator<Object>((Enumeration<Object>) obj); 384 } 385 if (obj instanceof Iterable<?>) { 386 return ((Iterable<?>) obj).iterator(); 387 } 388 try { 389 // look for an iterator() method to support the JDK5 Iterable 390 // interface or any user tools/DTOs that want to work in 391 // foreach without implementing the Collection interface 392 final JexlMethod it = getMethod(obj, "iterator", (Object[]) null); 393 if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) { 394 return (Iterator<Object>) it.invoke(obj, (Object[]) null); 395 } 396 } catch (final Exception xany) { 397 if (logger != null && logger.isDebugEnabled()) { 398 logger.info("unable to solve iterator()", xany); 399 } 400 } 401 return null; 402 } 403 404 @Override 405 public JexlMethod getConstructor(final Object ctorHandle, final Object... args) { 406 return ConstructorMethod.discover(base(), ctorHandle, args); 407 } 408 409 /** 410 * The concrete uberspect Arithmetic class. 411 */ 412 protected class ArithmeticUberspect implements JexlArithmetic.Uberspect { 413 /** The arithmetic instance being analyzed. */ 414 private final JexlArithmetic arithmetic; 415 /** The set of overloaded operators. */ 416 private final Set<JexlOperator> overloads; 417 418 /** 419 * Creates an instance. 420 * @param theArithmetic the arithmetic instance 421 * @param theOverloads the overloaded operators 422 */ 423 private ArithmeticUberspect(final JexlArithmetic theArithmetic, final Set<JexlOperator> theOverloads) { 424 this.arithmetic = theArithmetic; 425 this.overloads = theOverloads; 426 } 427 428 @Override 429 public JexlMethod getOperator(final JexlOperator operator, final Object... args) { 430 return overloads.contains(operator) && args != null 431 ? getMethod(arithmetic, operator.getMethodName(), args) 432 : null; 433 } 434 435 @Override 436 public boolean overloads(final JexlOperator operator) { 437 return overloads.contains(operator); 438 } 439 } 440 441 @Override 442 public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) { 443 JexlArithmetic.Uberspect jau = null; 444 if (arithmetic != null) { 445 final Class<? extends JexlArithmetic> aclass = arithmetic.getClass(); 446 Set<JexlOperator> ops = operatorMap.get(aclass); 447 if (ops == null) { 448 ops = EnumSet.noneOf(JexlOperator.class); 449 // deal only with derived classes 450 if (!JexlArithmetic.class.equals(aclass)) { 451 for (final JexlOperator op : JexlOperator.values()) { 452 final Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName()); 453 if (methods != null) { 454 mloop: 455 for (final Method method : methods) { 456 final Class<?>[] parms = method.getParameterTypes(); 457 if (parms.length != op.getArity()) { 458 continue; 459 } 460 // filter method that is an actual overload: 461 // - not inherited (not declared by base class) 462 // - nor overridden (not present in base class) 463 if (!JexlArithmetic.class.equals(method.getDeclaringClass())) { 464 try { 465 JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes()); 466 } catch (final NoSuchMethodException xmethod) { 467 // method was not found in JexlArithmetic; this is an operator definition 468 ops.add(op); 469 } 470 } 471 } 472 } 473 } 474 } 475 // register this arithmetic class in the operator map 476 operatorMap.put(aclass, ops); 477 } 478 jau = new ArithmeticUberspect(arithmetic, ops); 479 } 480 return jau; 481 } 482}