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.introspection; 018 019 import java.beans.IntrospectionException; 020 import org.apache.commons.jexl2.internal.Introspector; 021 import java.lang.reflect.Constructor; 022 import java.lang.reflect.Field; 023 import java.lang.reflect.Modifier; 024 import java.lang.reflect.InvocationTargetException; 025 026 import java.lang.reflect.Method; 027 import java.util.Arrays; 028 import java.util.Enumeration; 029 import java.util.Iterator; 030 import java.util.Map; 031 032 import org.apache.commons.jexl2.JexlInfo; 033 import org.apache.commons.jexl2.JexlException; 034 import org.apache.commons.jexl2.internal.AbstractExecutor; 035 import org.apache.commons.jexl2.internal.ArrayIterator; 036 import org.apache.commons.jexl2.internal.EnumerationIterator; 037 import org.apache.commons.jexl2.internal.introspection.MethodKey; 038 import org.apache.commons.logging.Log; 039 040 /** 041 * Implementation of Uberspect to provide the default introspective 042 * functionality of JEXL. 043 * <p>This is the class to derive to customize introspection.</p> 044 * 045 * @since 1.0 046 */ 047 public class UberspectImpl extends Introspector implements Uberspect { 048 /** 049 * Publicly exposed special failure object returned by tryInvoke. 050 */ 051 public static final Object TRY_FAILED = AbstractExecutor.TRY_FAILED; 052 053 /** 054 * Creates a new UberspectImpl. 055 * @param runtimeLogger the logger used for all logging needs 056 */ 057 public UberspectImpl(Log runtimeLogger) { 058 super(runtimeLogger); 059 } 060 061 /** 062 * Resets this Uberspect class loader. 063 * @param cloader the class loader to use 064 * @since 2.1 065 */ 066 public void setLoader(ClassLoader cloader) { 067 base().setLoader(cloader); 068 } 069 070 /** 071 * {@inheritDoc} 072 */ 073 @SuppressWarnings("unchecked") 074 public Iterator<?> getIterator(Object obj, JexlInfo info) { 075 if (obj instanceof Iterator<?>) { 076 return ((Iterator<?>) obj); 077 } 078 if (obj.getClass().isArray()) { 079 return new ArrayIterator(obj); 080 } 081 if (obj instanceof Map<?, ?>) { 082 return ((Map<?, ?>) obj).values().iterator(); 083 } 084 if (obj instanceof Enumeration<?>) { 085 return new EnumerationIterator<Object>((Enumeration<Object>) obj); 086 } 087 if (obj instanceof Iterable<?>) { 088 return ((Iterable<?>) obj).iterator(); 089 } 090 try { 091 // look for an iterator() method to support the JDK5 Iterable 092 // interface or any user tools/DTOs that want to work in 093 // foreach without implementing the Collection interface 094 AbstractExecutor.Method it = getMethodExecutor(obj, "iterator", null); 095 if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) { 096 return (Iterator<Object>) it.execute(obj, null); 097 } 098 } catch (Exception xany) { 099 throw new JexlException(info, "unable to generate iterator()", xany); 100 } 101 return null; 102 } 103 104 /** 105 * {@inheritDoc} 106 */ 107 public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) { 108 return getMethodExecutor(obj, method, args); 109 } 110 111 /** 112 * {@inheritDoc} 113 */ 114 @Deprecated 115 public Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info) { 116 return getConstructor(ctorHandle, args); 117 } 118 119 /** 120 * {@inheritDoc} 121 * @since 2.1 122 */ 123 public JexlMethod getConstructorMethod(Object ctorHandle, Object[] args, JexlInfo info) { 124 final Constructor<?> ctor = getConstructor(ctorHandle, args); 125 if (ctor != null) { 126 return new ConstructorMethod(ctor); 127 } else { 128 return null; 129 } 130 } 131 132 /** 133 * {@inheritDoc} 134 */ 135 public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) { 136 JexlPropertyGet get = getGetExecutor(obj, identifier); 137 if (get == null && obj != null && identifier != null) { 138 get = getIndexedGet(obj, identifier.toString()); 139 if (get == null) { 140 Field field = getField(obj, identifier.toString(), info); 141 if (field != null) { 142 return new FieldPropertyGet(field); 143 } 144 } 145 } 146 return get; 147 } 148 149 /** 150 * {@inheritDoc} 151 */ 152 public JexlPropertySet getPropertySet(final Object obj, final Object identifier, Object arg, JexlInfo info) { 153 JexlPropertySet set = getSetExecutor(obj, identifier, arg); 154 if (set == null && obj != null && identifier != null) { 155 Field field = getField(obj, identifier.toString(), info); 156 if (field != null 157 && !Modifier.isFinal(field.getModifiers()) 158 && (arg == null || MethodKey.isInvocationConvertible(field.getType(), arg.getClass(), false))) { 159 return new FieldPropertySet(field); 160 } 161 } 162 return set; 163 } 164 165 /** 166 * Returns a class field. 167 * Only for use by sub-classes, will be made protected in a later version 168 * @param obj the object 169 * @param name the field name 170 * @param info debug info 171 * @return a {@link Field}. 172 */ 173 public Field getField(Object obj, String name, JexlInfo info) { 174 final Class<?> clazz = obj instanceof Class<?> ? (Class<?>) obj : obj.getClass(); 175 return getField(clazz, name); 176 } 177 178 /** 179 * Attempts to find an indexed-property getter in an object. 180 * The code attempts to find the list of methods getXXX() and setXXX(). 181 * Note that this is not equivalent to the strict bean definition of indexed properties; the type of the key 182 * is not necessarily an int and the set/get arrays are not resolved. 183 * @param object the object 184 * @param name the container name 185 * @return a JexlPropertyGet is successfull, null otherwise 186 * @since 2.1 187 */ 188 protected JexlPropertyGet getIndexedGet(Object object, String name) { 189 if (object != null && name != null) { 190 String base = name.substring(0, 1).toUpperCase() + name.substring(1); 191 final String container = name; 192 final Class<?> clazz = object.getClass(); 193 final Method[] getters = getMethods(object.getClass(), "get" + base); 194 final Method[] setters = getMethods(object.getClass(), "set" + base); 195 if (getters != null) { 196 return new IndexedType(container, clazz, getters, setters); 197 } 198 } 199 return null; 200 } 201 202 /** 203 * Abstract an indexed property container. 204 * This stores the container name and owning class as well as the list of available getter and setter methods. 205 * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set). 206 * @since 2.1 207 */ 208 private static final class IndexedType implements JexlPropertyGet { 209 /** The container name. */ 210 private final String container; 211 /** The owning class. */ 212 private final Class<?> clazz; 213 /** The array of getter methods. */ 214 private final Method[] getters; 215 /** The array of setter methods. */ 216 private final Method[] setters; 217 218 /** 219 * Creates a new indexed type. 220 * @param name the container name 221 * @param c the owning class 222 * @param gets the array of getter methods 223 * @param sets the array of setter methods 224 */ 225 IndexedType(String name, Class<?> c, Method[] gets, Method[] sets) { 226 this.container = name; 227 this.clazz = c; 228 this.getters = gets; 229 this.setters = sets; 230 } 231 232 /** 233 * {@inheritDoc} 234 */ 235 public Object invoke(Object obj) throws Exception { 236 if (obj != null && clazz.equals(obj.getClass())) { 237 return new IndexedContainer(this, obj); 238 } else { 239 throw new IntrospectionException("property resolution error"); 240 } 241 } 242 243 /** 244 * {@inheritDoc} 245 */ 246 public Object tryInvoke(Object obj, Object key) { 247 if (obj != null && key != null && clazz.equals(obj.getClass()) && container.equals(key.toString())) { 248 return new IndexedContainer(this, obj); 249 } else { 250 return TRY_FAILED; 251 } 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 public boolean tryFailed(Object rval) { 258 return rval == TRY_FAILED; 259 } 260 261 /** 262 * {@inheritDoc} 263 */ 264 public boolean isCacheable() { 265 return true; 266 } 267 268 /** 269 * Gets the value of a property from a container. 270 * @param object the instance owning the container (not null) 271 * @param key the property key (not null) 272 * @return the property value 273 * @throws Exception if invocation failed; IntrospectionException if a property getter could not be found 274 */ 275 private Object invokeGet(Object object, Object key) throws Exception { 276 if (getters != null) { 277 final Object[] args = {key}; 278 final Method jm; 279 if (getters.length == 1) { 280 jm = getters[0]; 281 } else { 282 jm = new MethodKey(getters[0].getName(), args).getMostSpecificMethod(Arrays.asList(getters)); 283 } 284 if (jm != null) { 285 return jm.invoke(object, args); 286 } 287 } 288 throw new IntrospectionException("property get error: " 289 + object.getClass().toString() + "@" + key.toString()); 290 } 291 292 /** 293 * Sets the value of a property in a container. 294 * @param object the instance owning the container (not null) 295 * @param key the property key (not null) 296 * @param value the property value (not null) 297 * @return the result of the method invocation (frequently null) 298 * @throws Exception if invocation failed; IntrospectionException if a property setter could not be found 299 */ 300 private Object invokeSet(Object object, Object key, Object value) throws Exception { 301 if (setters != null) { 302 final Object[] args = {key, value}; 303 final Method jm; 304 if (setters.length == 1) { 305 jm = setters[0]; 306 } else { 307 jm = new MethodKey(setters[0].getName(), args).getMostSpecificMethod(Arrays.asList(setters)); 308 } 309 if (jm != null) { 310 return jm.invoke(object, args); 311 } 312 } 313 throw new IntrospectionException("property set error: " 314 + object.getClass().toString() + "@" + key.toString()); 315 } 316 } 317 318 /** 319 * A generic indexed property container, exposes get(key) and set(key, value) and solves method call dynamically 320 * based on arguments. 321 * @since 2.1 322 */ 323 public static final class IndexedContainer { 324 /** The instance owning the container. */ 325 private final Object object; 326 /** The container type instance. */ 327 private final IndexedType type; 328 329 /** 330 * Creates a new duck container. 331 * @param theType the container type 332 * @param theObject the instance owning the container 333 */ 334 private IndexedContainer(IndexedType theType, Object theObject) { 335 this.type = theType; 336 this.object = theObject; 337 } 338 339 /** 340 * Gets a property from a container. 341 * @param key the property key 342 * @return the property value 343 * @throws Exception if inner invocation fails 344 */ 345 public Object get(Object key) throws Exception { 346 return type.invokeGet(object, key); 347 } 348 349 /** 350 * Sets a property in a container. 351 * @param key the property key 352 * @param value the property value 353 * @return the invocation result (frequently null) 354 * @throws Exception if inner invocation fails 355 */ 356 public Object set(Object key, Object value) throws Exception { 357 return type.invokeSet(object, key, value); 358 } 359 } 360 361 /** 362 * A JexlMethod that wraps constructor. 363 * @since 2.1 364 */ 365 private final class ConstructorMethod implements JexlMethod { 366 /** The wrapped constructor. */ 367 private final Constructor<?> ctor; 368 369 /** 370 * Creates a constructor method. 371 * @param theCtor the constructor to wrap 372 */ 373 private ConstructorMethod(Constructor<?> theCtor) { 374 this.ctor = theCtor; 375 } 376 377 /** 378 * {@inheritDoc} 379 */ 380 public Object invoke(Object obj, Object[] params) throws Exception { 381 Class<?> clazz = null; 382 if (obj instanceof Class<?>) { 383 clazz = (Class<?>) obj; 384 } else if (obj != null) { 385 clazz = getClassByName(obj.toString()); 386 } else { 387 clazz = ctor.getDeclaringClass(); 388 } 389 if (clazz.equals(ctor.getDeclaringClass())) { 390 return ctor.newInstance(params); 391 } else { 392 throw new IntrospectionException("constructor resolution error"); 393 } 394 } 395 396 /** 397 * {@inheritDoc} 398 */ 399 public Object tryInvoke(String name, Object obj, Object[] params) { 400 Class<?> clazz = null; 401 if (obj instanceof Class<?>) { 402 clazz = (Class<?>) obj; 403 } else if (obj != null) { 404 clazz = getClassByName(obj.toString()); 405 } else { 406 clazz = ctor.getDeclaringClass(); 407 } 408 if (clazz.equals(ctor.getDeclaringClass()) 409 && (name == null || name.equals(clazz.getName()))) { 410 try { 411 return ctor.newInstance(params); 412 } catch (InstantiationException xinstance) { 413 return TRY_FAILED; 414 } catch (IllegalAccessException xaccess) { 415 return TRY_FAILED; 416 } catch (IllegalArgumentException xargument) { 417 return TRY_FAILED; 418 } catch (InvocationTargetException xinvoke) { 419 return TRY_FAILED; 420 } 421 } 422 return TRY_FAILED; 423 } 424 425 /** 426 * {@inheritDoc} 427 */ 428 public boolean tryFailed(Object rval) { 429 return rval == TRY_FAILED; 430 } 431 432 /** 433 * {@inheritDoc} 434 */ 435 public boolean isCacheable() { 436 return true; 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 public Class<?> getReturnType() { 443 return ctor.getDeclaringClass(); 444 } 445 } 446 447 /** 448 * A JexlPropertyGet for public fields. 449 * @deprecated Do not use externally - will be made private in a later version 450 */ 451 @Deprecated 452 public static final class FieldPropertyGet implements JexlPropertyGet { 453 /** 454 * The public field. 455 */ 456 private final Field field; 457 458 /** 459 * Creates a new instance of FieldPropertyGet. 460 * @param theField the class public field 461 */ 462 public FieldPropertyGet(Field theField) { 463 field = theField; 464 } 465 466 /** 467 * {@inheritDoc} 468 */ 469 public Object invoke(Object obj) throws Exception { 470 return field.get(obj); 471 } 472 473 /** 474 * {@inheritDoc} 475 */ 476 public Object tryInvoke(Object obj, Object key) { 477 if (obj.getClass().equals(field.getDeclaringClass()) && key.equals(field.getName())) { 478 try { 479 return field.get(obj); 480 } catch (IllegalAccessException xill) { 481 return TRY_FAILED; 482 } 483 } 484 return TRY_FAILED; 485 } 486 487 /** 488 * {@inheritDoc} 489 */ 490 public boolean tryFailed(Object rval) { 491 return rval == TRY_FAILED; 492 } 493 494 /** 495 * {@inheritDoc} 496 */ 497 public boolean isCacheable() { 498 return true; 499 } 500 } 501 502 /** 503 * A JexlPropertySet for public fields. 504 * @deprecated Do not use externally - will be made private in a later version 505 */ 506 @Deprecated 507 public static final class FieldPropertySet implements JexlPropertySet { 508 /** 509 * The public field. 510 */ 511 private final Field field; 512 513 /** 514 * Creates a new instance of FieldPropertySet. 515 * @param theField the class public field 516 */ 517 public FieldPropertySet(Field theField) { 518 field = theField; 519 } 520 521 /** 522 * {@inheritDoc} 523 */ 524 public Object invoke(Object obj, Object arg) throws Exception { 525 field.set(obj, arg); 526 return arg; 527 } 528 529 /** 530 * {@inheritDoc} 531 */ 532 public Object tryInvoke(Object obj, Object key, Object value) { 533 if (obj.getClass().equals(field.getDeclaringClass()) 534 && key.equals(field.getName()) 535 && (value == null || MethodKey.isInvocationConvertible(field.getType(), value.getClass(), false))) { 536 try { 537 field.set(obj, value); 538 return value; 539 } catch (IllegalAccessException xill) { 540 return TRY_FAILED; 541 } 542 } 543 return TRY_FAILED; 544 } 545 546 /** 547 * {@inheritDoc} 548 */ 549 public boolean tryFailed(Object rval) { 550 return rval == TRY_FAILED; 551 } 552 553 /** 554 * {@inheritDoc} 555 */ 556 public boolean isCacheable() { 557 return true; 558 } 559 } 560 }