    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 }