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 }