1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.jxpath.util;
17
18 import java.beans.IndexedPropertyDescriptor;
19 import java.beans.PropertyDescriptor;
20 import java.lang.reflect.Array;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31
32 import org.apache.commons.jxpath.Container;
33 import org.apache.commons.jxpath.DynamicPropertyHandler;
34 import org.apache.commons.jxpath.JXPathException;
35
36 /***
37 * Collection and property access utilities.
38 *
39 * @author Dmitri Plotnikov
40 * @version $Revision: 1.19 $ $Date: 2004/04/04 22:06:36 $
41 */
42 public class ValueUtils {
43 private static Map dynamicPropertyHandlerMap = new HashMap();
44 private static final int UNKNOWN_LENGTH_MAX_COUNT = 16000;
45
46 /***
47 * Returns true if the object is an array or a Collection
48 */
49 public static boolean isCollection(Object value) {
50 if (value == null) {
51 return false;
52 }
53 value = getValue(value);
54 if (value.getClass().isArray()) {
55 return true;
56 }
57 else if (value instanceof Collection) {
58 return true;
59 }
60 return false;
61 }
62
63 /***
64 * Returns 1 if the type is a collection,
65 * -1 if it is definitely not
66 * and 0 if it may be a collection in some cases.
67 */
68 public static int getCollectionHint(Class clazz) {
69 if (clazz.isArray()) {
70 return 1;
71 }
72
73 if (Collection.class.isAssignableFrom(clazz)) {
74 return 1;
75 }
76
77 if (clazz.isPrimitive()) {
78 return -1;
79 }
80
81 if (clazz.isInterface()) {
82 return 0;
83 }
84
85 if (Modifier.isFinal(clazz.getModifiers())) {
86 return -1;
87 }
88
89 return 0;
90 }
91
92 /***
93 * If there is a regular non-indexed read method for this property,
94 * uses this method to obtain the collection and then returns its
95 * length.
96 * Otherwise, attempts to guess the length of the collection by
97 * calling the indexed get method repeatedly. The method is supposed
98 * to throw an exception if the index is out of bounds.
99 */
100 public static int getIndexedPropertyLength(
101 Object object,
102 IndexedPropertyDescriptor pd)
103 {
104 if (pd.getReadMethod() != null) {
105 return getLength(getValue(object, pd));
106 }
107
108 Method readMethod = pd.getIndexedReadMethod();
109 if (readMethod == null) {
110 throw new JXPathException(
111 "No indexed read method for property " + pd.getName());
112 }
113
114 for (int i = 0; i < UNKNOWN_LENGTH_MAX_COUNT; i++) {
115 try {
116 readMethod.invoke(object, new Object[] { new Integer(i)});
117 }
118 catch (Throwable t) {
119 return i;
120 }
121 }
122
123 throw new JXPathException(
124 "Cannot determine the length of the indexed property "
125 + pd.getName());
126 }
127
128 /***
129 * Returns the length of the supplied collection. If the supplied object
130 * is not a collection, returns 1. If collection is null, returns 0.
131 */
132 public static int getLength(Object collection) {
133 if (collection == null) {
134 return 0;
135 }
136 collection = getValue(collection);
137 if (collection.getClass().isArray()) {
138 return Array.getLength(collection);
139 }
140 else if (collection instanceof Collection) {
141 return ((Collection) collection).size();
142 }
143 else {
144 return 1;
145 }
146 }
147
148 /***
149 * Returns an iterator for the supplied collection. If the argument
150 * is null, returns an empty iterator. If the argument is not
151 * a collection, returns an iterator that produces just that one object.
152 */
153 public static Iterator iterate(Object collection) {
154 if (collection == null) {
155 return Collections.EMPTY_LIST.iterator();
156 }
157 if (collection.getClass().isArray()) {
158 int length = Array.getLength(collection);
159 if (length == 0) {
160 return Collections.EMPTY_LIST.iterator();
161 }
162 ArrayList list = new ArrayList();
163 for (int i = 0; i < length; i++) {
164 list.add(Array.get(collection, i));
165 }
166 return list.iterator();
167 }
168 else if (collection instanceof Collection) {
169 return ((Collection) collection).iterator();
170 }
171 else {
172 return Collections.singletonList(collection).iterator();
173 }
174 }
175
176 /***
177 * Grows the collection if necessary to the specified size. Returns
178 * the new, expanded collection.
179 */
180 public static Object expandCollection(Object collection, int size) {
181 if (collection == null) {
182 return null;
183 }
184 else if (collection.getClass().isArray()) {
185 Object bigger =
186 Array.newInstance(
187 collection.getClass().getComponentType(),
188 size);
189 System.arraycopy(
190 collection,
191 0,
192 bigger,
193 0,
194 Array.getLength(collection));
195 return bigger;
196 }
197 else if (collection instanceof Collection) {
198 while (((Collection) collection).size() < size) {
199 ((Collection) collection).add(null);
200 }
201 return collection;
202 }
203 else {
204 throw new JXPathException(
205 "Cannot turn "
206 + collection.getClass().getName()
207 + " into a collection of size "
208 + size);
209 }
210 }
211
212 /***
213 * Returns the index'th element from the supplied collection.
214 */
215 public static Object remove(Object collection, int index) {
216 collection = getValue(collection);
217 if (collection == null) {
218 return null;
219 }
220 else if (collection.getClass().isArray()) {
221 int length = Array.getLength(collection);
222 Object smaller =
223 Array.newInstance(
224 collection.getClass().getComponentType(),
225 length - 1);
226 if (index > 0) {
227 System.arraycopy(collection, 0, smaller, 0, index);
228 }
229 if (index < length - 1) {
230 System.arraycopy(
231 collection,
232 index + 1,
233 smaller,
234 index,
235 length - index - 1);
236 }
237 return smaller;
238 }
239 else if (collection instanceof List) {
240 int size = ((List) collection).size();
241 if (index < size) {
242 ((List) collection).remove(index);
243 }
244 return collection;
245 }
246 else if (collection instanceof Collection) {
247 Iterator it = ((Collection) collection).iterator();
248 for (int i = 0; i < index; i++) {
249 if (!it.hasNext()) {
250 break;
251 }
252 it.next();
253 }
254 if (it.hasNext()) {
255 it.next();
256 it.remove();
257 }
258 return collection;
259 }
260 else {
261 throw new JXPathException(
262 "Cannot remove "
263 + collection.getClass().getName()
264 + "["
265 + index
266 + "]");
267 }
268 }
269
270 /***
271 * Returns the index'th element of the supplied collection.
272 */
273 public static Object getValue(Object collection, int index) {
274 collection = getValue(collection);
275 Object value = collection;
276 if (collection != null) {
277 if (collection.getClass().isArray()) {
278 if (index < 0 || index >= Array.getLength(collection)) {
279 return null;
280 }
281 value = Array.get(collection, index);
282 }
283 else if (collection instanceof List) {
284 if (index < 0 || index >= ((List) collection).size()) {
285 return null;
286 }
287 value = ((List) collection).get(index);
288 }
289 else if (collection instanceof Collection) {
290 int i = 0;
291 Iterator it = ((Collection) collection).iterator();
292 for (; i < index; i++) {
293 it.next();
294 }
295 if (it.hasNext()) {
296 value = it.next();
297 }
298 else {
299 value = null;
300 }
301 }
302 }
303 return value;
304 }
305
306 /***
307 * Modifies the index'th element of the supplied collection.
308 * Converts the value to the required type if necessary.
309 */
310 public static void setValue(Object collection, int index, Object value) {
311 collection = getValue(collection);
312 if (collection != null) {
313 if (collection.getClass().isArray()) {
314 Array.set(
315 collection,
316 index,
317 convert(value, collection.getClass().getComponentType()));
318 }
319 else if (collection instanceof List) {
320 ((List) collection).set(index, value);
321 }
322 else if (collection instanceof Collection) {
323 throw new UnsupportedOperationException(
324 "Cannot set value of an element of a "
325 + collection.getClass().getName());
326 }
327 }
328 }
329
330 /***
331 * Returns the value of the bean's property represented by
332 * the supplied property descriptor.
333 */
334 public static Object getValue(
335 Object bean,
336 PropertyDescriptor propertyDescriptor)
337 {
338 Object value;
339 try {
340 Method method =
341 getAccessibleMethod(propertyDescriptor.getReadMethod());
342 if (method == null) {
343 throw new JXPathException("No read method");
344 }
345 value = method.invoke(bean, new Object[0]);
346 }
347 catch (Exception ex) {
348 throw new JXPathException(
349 "Cannot access property: "
350 + (bean == null ? "null" : bean.getClass().getName())
351 + "."
352 + propertyDescriptor.getName(),
353 ex);
354 }
355 return value;
356 }
357
358 /***
359 * Modifies the value of the bean's property represented by
360 * the supplied property descriptor.
361 */
362 public static void setValue(
363 Object bean,
364 PropertyDescriptor propertyDescriptor,
365 Object value)
366 {
367 try {
368 Method method =
369 getAccessibleMethod(propertyDescriptor.getWriteMethod());
370 if (method == null) {
371 throw new JXPathException("No write method");
372 }
373 value = convert(value, propertyDescriptor.getPropertyType());
374 value = method.invoke(bean, new Object[] { value });
375 }
376 catch (Exception ex) {
377 throw new JXPathException(
378 "Cannot modify property: "
379 + (bean == null ? "null" : bean.getClass().getName())
380 + "."
381 + propertyDescriptor.getName(),
382 ex);
383 }
384 }
385
386 private static Object convert(Object value, Class type) {
387 try {
388 return TypeUtils.convert(value, type);
389 }
390 catch (Exception ex) {
391 throw new JXPathException(
392 "Cannot convert value of class "
393 + (value == null ? "null" : value.getClass().getName())
394 + " to type "
395 + type,
396 ex);
397 }
398 }
399
400 /***
401 * Returns the index'th element of the bean's property represented by
402 * the supplied property descriptor.
403 */
404 public static Object getValue(
405 Object bean,
406 PropertyDescriptor propertyDescriptor,
407 int index)
408 {
409 if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
410 try {
411 IndexedPropertyDescriptor ipd =
412 (IndexedPropertyDescriptor) propertyDescriptor;
413 Method method = ipd.getIndexedReadMethod();
414 if (method != null) {
415 return method.invoke(
416 bean,
417 new Object[] { new Integer(index)});
418 }
419 }
420 catch (InvocationTargetException ex) {
421 Throwable t =
422 ((InvocationTargetException) ex).getTargetException();
423 if (t instanceof ArrayIndexOutOfBoundsException) {
424 return null;
425 }
426
427 throw new JXPathException(
428 "Cannot access property: " + propertyDescriptor.getName(),
429 t);
430 }
431 catch (Throwable ex) {
432 throw new JXPathException(
433 "Cannot access property: " + propertyDescriptor.getName(),
434 ex);
435 }
436 }
437
438
439
440 return getValue(getValue(bean, propertyDescriptor), index);
441 }
442
443 /***
444 * Modifies the index'th element of the bean's property represented by
445 * the supplied property descriptor. Converts the value to the required
446 * type if necessary.
447 */
448 public static void setValue(
449 Object bean,
450 PropertyDescriptor propertyDescriptor,
451 int index,
452 Object value)
453 {
454 if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
455 try {
456 IndexedPropertyDescriptor ipd =
457 (IndexedPropertyDescriptor) propertyDescriptor;
458 Method method = ipd.getIndexedWriteMethod();
459 if (method != null) {
460 method.invoke(
461 bean,
462 new Object[] {
463 new Integer(index),
464 convert(value, ipd.getIndexedPropertyType())});
465 return;
466 }
467 }
468 catch (Exception ex) {
469 throw new RuntimeException(
470 "Cannot access property: "
471 + propertyDescriptor.getName()
472 + ", "
473 + ex.getMessage());
474 }
475 }
476
477 Object collection = getValue(bean, propertyDescriptor);
478 if (isCollection(collection)) {
479 setValue(collection, index, value);
480 }
481 else if (index == 0) {
482 setValue(bean, propertyDescriptor, value);
483 }
484 else {
485 throw new RuntimeException(
486 "Not a collection: " + propertyDescriptor.getName());
487 }
488 }
489
490 /***
491 * If the parameter is a container, opens the container and
492 * return the contents. The method is recursive.
493 */
494 public static Object getValue(Object object) {
495 while (object instanceof Container) {
496 object = ((Container) object).getValue();
497 }
498 return object;
499 }
500
501 /***
502 * Returns a shared instance of the dynamic property handler class
503 * returned by <code>getDynamicPropertyHandlerClass()</code>.
504 */
505 public static DynamicPropertyHandler getDynamicPropertyHandler(Class clazz)
506 {
507 DynamicPropertyHandler handler =
508 (DynamicPropertyHandler) dynamicPropertyHandlerMap.get(clazz);
509 if (handler == null) {
510 try {
511 handler = (DynamicPropertyHandler) clazz.newInstance();
512 }
513 catch (Exception ex) {
514 throw new JXPathException(
515 "Cannot allocate dynamic property handler of class "
516 + clazz.getName(),
517 ex);
518 }
519 dynamicPropertyHandlerMap.put(clazz, handler);
520 }
521 return handler;
522 }
523
524
525
526
527
528
529
530
531 /***
532 * Return an accessible method (that is, one that can be invoked via
533 * reflection) that implements the specified Method. If no such method
534 * can be found, return <code>null</code>.
535 *
536 * @param method The method that we wish to call
537 */
538 public static Method getAccessibleMethod(Method method) {
539
540
541 if (method == null) {
542 return (null);
543 }
544
545
546 if (!Modifier.isPublic(method.getModifiers())) {
547 return (null);
548 }
549
550
551 Class clazz = method.getDeclaringClass();
552 if (Modifier.isPublic(clazz.getModifiers())) {
553 return (method);
554 }
555
556
557 method =
558 getAccessibleMethodFromInterfaceNest(
559 clazz,
560 method.getName(),
561 method.getParameterTypes());
562 return (method);
563 }
564
565
566 /***
567 * Return an accessible method (that is, one that can be invoked via
568 * reflection) that implements the specified method, by scanning through
569 * all implemented interfaces and subinterfaces. If no such Method
570 * can be found, return <code>null</code>.
571 *
572 * @param clazz Parent class for the interfaces to be checked
573 * @param methodName Method name of the method we wish to call
574 * @param parameterTypes The parameter type signatures
575 */
576 private static Method getAccessibleMethodFromInterfaceNest(
577 Class clazz,
578 String methodName,
579 Class parameterTypes[])
580 {
581
582 Method method = null;
583
584
585 Class interfaces[] = clazz.getInterfaces();
586 for (int i = 0; i < interfaces.length; i++) {
587
588
589 if (!Modifier.isPublic(interfaces[i].getModifiers())) {
590 continue;
591 }
592
593
594 try {
595 method =
596 interfaces[i].getDeclaredMethod(methodName, parameterTypes);
597 }
598 catch (NoSuchMethodException e) {
599 ;
600 }
601 if (method != null) {
602 break;
603 }
604
605
606 method =
607 getAccessibleMethodFromInterfaceNest(
608 interfaces[i],
609 methodName,
610 parameterTypes);
611 if (method != null) {
612 break;
613 }
614 }
615
616
617 return (method);
618 }
619 }