View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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         // We will fall through if there is no indexed read
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         // We will fall through if there is no indexed read
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     // -------------------------------------------------------- Private Methods
525     //
526     //  The rest of the code in this file was copied FROM
527     //  org.apache.commons.beanutils.PropertyUtil. We don't want to introduce
528     //  a dependency on BeanUtils yet - DP.
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         // Make sure we have a method to check
541         if (method == null) {
542             return (null);
543         }
544 
545         // If the requested method is not public we cannot call it
546         if (!Modifier.isPublic(method.getModifiers())) {
547             return (null);
548         }
549 
550         // If the declaring class is public, we are done
551         Class clazz = method.getDeclaringClass();
552         if (Modifier.isPublic(clazz.getModifiers())) {
553             return (method);
554         }
555 
556         // Check the implemented interfaces and subinterfaces
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         // Check the implemented interfaces of the parent class
585         Class interfaces[] = clazz.getInterfaces();
586         for (int i = 0; i < interfaces.length; i++) {
587 
588             // Is this interface public?
589             if (!Modifier.isPublic(interfaces[i].getModifiers())) {
590                 continue;
591             }
592 
593             // Does the method exist on this interface?
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             // Recursively check our parent interfaces
606             method =
607                 getAccessibleMethodFromInterfaceNest(
608                     interfaces[i],
609                     methodName,
610                     parameterTypes);
611             if (method != null) {
612                 break;
613             }
614         }
615 
616         // Return whatever we have found
617         return (method);
618     }
619 }