001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jxpath.util;
017:
018: import java.beans.IndexedPropertyDescriptor;
019: import java.beans.PropertyDescriptor;
020: import java.lang.reflect.Array;
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.ArrayList;
025: import java.util.Collection;
026: import java.util.Collections;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031:
032: import org.apache.commons.jxpath.Container;
033: import org.apache.commons.jxpath.DynamicPropertyHandler;
034: import org.apache.commons.jxpath.JXPathException;
035:
036: /**
037: * Collection and property access utilities.
038: *
039: * @author Dmitri Plotnikov
040: * @version $Revision: 1.19 $ $Date: 2004/04/04 22:06:36 $
041: */
042: public class ValueUtils {
043: private static Map dynamicPropertyHandlerMap = new HashMap();
044: private static final int UNKNOWN_LENGTH_MAX_COUNT = 16000;
045:
046: /**
047: * Returns true if the object is an array or a Collection
048: */
049: public static boolean isCollection(Object value) {
050: if (value == null) {
051: return false;
052: }
053: value = getValue(value);
054: if (value.getClass().isArray()) {
055: return true;
056: } else if (value instanceof Collection) {
057: return true;
058: }
059: return false;
060: }
061:
062: /**
063: * Returns 1 if the type is a collection,
064: * -1 if it is definitely not
065: * and 0 if it may be a collection in some cases.
066: */
067: public static int getCollectionHint(Class clazz) {
068: if (clazz.isArray()) {
069: return 1;
070: }
071:
072: if (Collection.class.isAssignableFrom(clazz)) {
073: return 1;
074: }
075:
076: if (clazz.isPrimitive()) {
077: return -1;
078: }
079:
080: if (clazz.isInterface()) {
081: return 0;
082: }
083:
084: if (Modifier.isFinal(clazz.getModifiers())) {
085: return -1;
086: }
087:
088: return 0;
089: }
090:
091: /**
092: * If there is a regular non-indexed read method for this property,
093: * uses this method to obtain the collection and then returns its
094: * length.
095: * Otherwise, attempts to guess the length of the collection by
096: * calling the indexed get method repeatedly. The method is supposed
097: * to throw an exception if the index is out of bounds.
098: */
099: public static int getIndexedPropertyLength(Object object,
100: IndexedPropertyDescriptor pd) {
101: if (pd.getReadMethod() != null) {
102: return getLength(getValue(object, pd));
103: }
104:
105: Method readMethod = pd.getIndexedReadMethod();
106: if (readMethod == null) {
107: throw new JXPathException(
108: "No indexed read method for property "
109: + pd.getName());
110: }
111:
112: for (int i = 0; i < UNKNOWN_LENGTH_MAX_COUNT; i++) {
113: try {
114: readMethod.invoke(object,
115: new Object[] { new Integer(i) });
116: } catch (Throwable t) {
117: return i;
118: }
119: }
120:
121: throw new JXPathException(
122: "Cannot determine the length of the indexed property "
123: + pd.getName());
124: }
125:
126: /**
127: * Returns the length of the supplied collection. If the supplied object
128: * is not a collection, returns 1. If collection is null, returns 0.
129: */
130: public static int getLength(Object collection) {
131: if (collection == null) {
132: return 0;
133: }
134: collection = getValue(collection);
135: if (collection.getClass().isArray()) {
136: return Array.getLength(collection);
137: } else if (collection instanceof Collection) {
138: return ((Collection) collection).size();
139: } else {
140: return 1;
141: }
142: }
143:
144: /**
145: * Returns an iterator for the supplied collection. If the argument
146: * is null, returns an empty iterator. If the argument is not
147: * a collection, returns an iterator that produces just that one object.
148: */
149: public static Iterator iterate(Object collection) {
150: if (collection == null) {
151: return Collections.EMPTY_LIST.iterator();
152: }
153: if (collection.getClass().isArray()) {
154: int length = Array.getLength(collection);
155: if (length == 0) {
156: return Collections.EMPTY_LIST.iterator();
157: }
158: ArrayList list = new ArrayList();
159: for (int i = 0; i < length; i++) {
160: list.add(Array.get(collection, i));
161: }
162: return list.iterator();
163: } else if (collection instanceof Collection) {
164: return ((Collection) collection).iterator();
165: } else {
166: return Collections.singletonList(collection).iterator();
167: }
168: }
169:
170: /**
171: * Grows the collection if necessary to the specified size. Returns
172: * the new, expanded collection.
173: */
174: public static Object expandCollection(Object collection, int size) {
175: if (collection == null) {
176: return null;
177: } else if (collection.getClass().isArray()) {
178: Object bigger = Array.newInstance(collection.getClass()
179: .getComponentType(), size);
180: System.arraycopy(collection, 0, bigger, 0, Array
181: .getLength(collection));
182: return bigger;
183: } else if (collection instanceof Collection) {
184: while (((Collection) collection).size() < size) {
185: ((Collection) collection).add(null);
186: }
187: return collection;
188: } else {
189: throw new JXPathException("Cannot turn "
190: + collection.getClass().getName()
191: + " into a collection of size " + size);
192: }
193: }
194:
195: /**
196: * Returns the index'th element from the supplied collection.
197: */
198: public static Object remove(Object collection, int index) {
199: collection = getValue(collection);
200: if (collection == null) {
201: return null;
202: } else if (collection.getClass().isArray()) {
203: int length = Array.getLength(collection);
204: Object smaller = Array.newInstance(collection.getClass()
205: .getComponentType(), length - 1);
206: if (index > 0) {
207: System.arraycopy(collection, 0, smaller, 0, index);
208: }
209: if (index < length - 1) {
210: System.arraycopy(collection, index + 1, smaller, index,
211: length - index - 1);
212: }
213: return smaller;
214: } else if (collection instanceof List) {
215: int size = ((List) collection).size();
216: if (index < size) {
217: ((List) collection).remove(index);
218: }
219: return collection;
220: } else if (collection instanceof Collection) {
221: Iterator it = ((Collection) collection).iterator();
222: for (int i = 0; i < index; i++) {
223: if (!it.hasNext()) {
224: break;
225: }
226: it.next();
227: }
228: if (it.hasNext()) {
229: it.next();
230: it.remove();
231: }
232: return collection;
233: } else {
234: throw new JXPathException("Cannot remove "
235: + collection.getClass().getName() + "[" + index
236: + "]");
237: }
238: }
239:
240: /**
241: * Returns the index'th element of the supplied collection.
242: */
243: public static Object getValue(Object collection, int index) {
244: collection = getValue(collection);
245: Object value = collection;
246: if (collection != null) {
247: if (collection.getClass().isArray()) {
248: if (index < 0 || index >= Array.getLength(collection)) {
249: return null;
250: }
251: value = Array.get(collection, index);
252: } else if (collection instanceof List) {
253: if (index < 0 || index >= ((List) collection).size()) {
254: return null;
255: }
256: value = ((List) collection).get(index);
257: } else if (collection instanceof Collection) {
258: int i = 0;
259: Iterator it = ((Collection) collection).iterator();
260: for (; i < index; i++) {
261: it.next();
262: }
263: if (it.hasNext()) {
264: value = it.next();
265: } else {
266: value = null;
267: }
268: }
269: }
270: return value;
271: }
272:
273: /**
274: * Modifies the index'th element of the supplied collection.
275: * Converts the value to the required type if necessary.
276: */
277: public static void setValue(Object collection, int index,
278: Object value) {
279: collection = getValue(collection);
280: if (collection != null) {
281: if (collection.getClass().isArray()) {
282: Array.set(collection, index, convert(value, collection
283: .getClass().getComponentType()));
284: } else if (collection instanceof List) {
285: ((List) collection).set(index, value);
286: } else if (collection instanceof Collection) {
287: throw new UnsupportedOperationException(
288: "Cannot set value of an element of a "
289: + collection.getClass().getName());
290: }
291: }
292: }
293:
294: /**
295: * Returns the value of the bean's property represented by
296: * the supplied property descriptor.
297: */
298: public static Object getValue(Object bean,
299: PropertyDescriptor propertyDescriptor) {
300: Object value;
301: try {
302: Method method = getAccessibleMethod(propertyDescriptor
303: .getReadMethod());
304: if (method == null) {
305: throw new JXPathException("No read method");
306: }
307: value = method.invoke(bean, new Object[0]);
308: } catch (Exception ex) {
309: throw new JXPathException("Cannot access property: "
310: + (bean == null ? "null" : bean.getClass()
311: .getName()) + "."
312: + propertyDescriptor.getName(), ex);
313: }
314: return value;
315: }
316:
317: /**
318: * Modifies the value of the bean's property represented by
319: * the supplied property descriptor.
320: */
321: public static void setValue(Object bean,
322: PropertyDescriptor propertyDescriptor, Object value) {
323: try {
324: Method method = getAccessibleMethod(propertyDescriptor
325: .getWriteMethod());
326: if (method == null) {
327: throw new JXPathException("No write method");
328: }
329: value = convert(value, propertyDescriptor.getPropertyType());
330: value = method.invoke(bean, new Object[] { value });
331: } catch (Exception ex) {
332: throw new JXPathException("Cannot modify property: "
333: + (bean == null ? "null" : bean.getClass()
334: .getName()) + "."
335: + propertyDescriptor.getName(), ex);
336: }
337: }
338:
339: private static Object convert(Object value, Class type) {
340: try {
341: return TypeUtils.convert(value, type);
342: } catch (Exception ex) {
343: throw new JXPathException("Cannot convert value of class "
344: + (value == null ? "null" : value.getClass()
345: .getName()) + " to type " + type, ex);
346: }
347: }
348:
349: /**
350: * Returns the index'th element of the bean's property represented by
351: * the supplied property descriptor.
352: */
353: public static Object getValue(Object bean,
354: PropertyDescriptor propertyDescriptor, int index) {
355: if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
356: try {
357: IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) propertyDescriptor;
358: Method method = ipd.getIndexedReadMethod();
359: if (method != null) {
360: return method.invoke(bean,
361: new Object[] { new Integer(index) });
362: }
363: } catch (InvocationTargetException ex) {
364: Throwable t = ((InvocationTargetException) ex)
365: .getTargetException();
366: if (t instanceof ArrayIndexOutOfBoundsException) {
367: return null;
368: }
369:
370: throw new JXPathException("Cannot access property: "
371: + propertyDescriptor.getName(), t);
372: } catch (Throwable ex) {
373: throw new JXPathException("Cannot access property: "
374: + propertyDescriptor.getName(), ex);
375: }
376: }
377:
378: // We will fall through if there is no indexed read
379:
380: return getValue(getValue(bean, propertyDescriptor), index);
381: }
382:
383: /**
384: * Modifies the index'th element of the bean's property represented by
385: * the supplied property descriptor. Converts the value to the required
386: * type if necessary.
387: */
388: public static void setValue(Object bean,
389: PropertyDescriptor propertyDescriptor, int index,
390: Object value) {
391: if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
392: try {
393: IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) propertyDescriptor;
394: Method method = ipd.getIndexedWriteMethod();
395: if (method != null) {
396: method
397: .invoke(
398: bean,
399: new Object[] {
400: new Integer(index),
401: convert(
402: value,
403: ipd
404: .getIndexedPropertyType()) });
405: return;
406: }
407: } catch (Exception ex) {
408: throw new RuntimeException("Cannot access property: "
409: + propertyDescriptor.getName() + ", "
410: + ex.getMessage());
411: }
412: }
413: // We will fall through if there is no indexed read
414: Object collection = getValue(bean, propertyDescriptor);
415: if (isCollection(collection)) {
416: setValue(collection, index, value);
417: } else if (index == 0) {
418: setValue(bean, propertyDescriptor, value);
419: } else {
420: throw new RuntimeException("Not a collection: "
421: + propertyDescriptor.getName());
422: }
423: }
424:
425: /**
426: * If the parameter is a container, opens the container and
427: * return the contents. The method is recursive.
428: */
429: public static Object getValue(Object object) {
430: while (object instanceof Container) {
431: object = ((Container) object).getValue();
432: }
433: return object;
434: }
435:
436: /**
437: * Returns a shared instance of the dynamic property handler class
438: * returned by <code>getDynamicPropertyHandlerClass()</code>.
439: */
440: public static DynamicPropertyHandler getDynamicPropertyHandler(
441: Class clazz) {
442: DynamicPropertyHandler handler = (DynamicPropertyHandler) dynamicPropertyHandlerMap
443: .get(clazz);
444: if (handler == null) {
445: try {
446: handler = (DynamicPropertyHandler) clazz.newInstance();
447: } catch (Exception ex) {
448: throw new JXPathException(
449: "Cannot allocate dynamic property handler of class "
450: + clazz.getName(), ex);
451: }
452: dynamicPropertyHandlerMap.put(clazz, handler);
453: }
454: return handler;
455: }
456:
457: // -------------------------------------------------------- Private Methods
458: //
459: // The rest of the code in this file was copied FROM
460: // org.apache.commons.beanutils.PropertyUtil. We don't want to introduce
461: // a dependency on BeanUtils yet - DP.
462: //
463:
464: /**
465: * Return an accessible method (that is, one that can be invoked via
466: * reflection) that implements the specified Method. If no such method
467: * can be found, return <code>null</code>.
468: *
469: * @param method The method that we wish to call
470: */
471: public static Method getAccessibleMethod(Method method) {
472:
473: // Make sure we have a method to check
474: if (method == null) {
475: return (null);
476: }
477:
478: // If the requested method is not public we cannot call it
479: if (!Modifier.isPublic(method.getModifiers())) {
480: return (null);
481: }
482:
483: // If the declaring class is public, we are done
484: Class clazz = method.getDeclaringClass();
485: if (Modifier.isPublic(clazz.getModifiers())) {
486: return (method);
487: }
488:
489: // Check the implemented interfaces and subinterfaces
490: method = getAccessibleMethodFromInterfaceNest(clazz, method
491: .getName(), method.getParameterTypes());
492: return (method);
493: }
494:
495: /**
496: * Return an accessible method (that is, one that can be invoked via
497: * reflection) that implements the specified method, by scanning through
498: * all implemented interfaces and subinterfaces. If no such Method
499: * can be found, return <code>null</code>.
500: *
501: * @param clazz Parent class for the interfaces to be checked
502: * @param methodName Method name of the method we wish to call
503: * @param parameterTypes The parameter type signatures
504: */
505: private static Method getAccessibleMethodFromInterfaceNest(
506: Class clazz, String methodName, Class parameterTypes[]) {
507:
508: Method method = null;
509:
510: // Check the implemented interfaces of the parent class
511: Class interfaces[] = clazz.getInterfaces();
512: for (int i = 0; i < interfaces.length; i++) {
513:
514: // Is this interface public?
515: if (!Modifier.isPublic(interfaces[i].getModifiers())) {
516: continue;
517: }
518:
519: // Does the method exist on this interface?
520: try {
521: method = interfaces[i].getDeclaredMethod(methodName,
522: parameterTypes);
523: } catch (NoSuchMethodException e) {
524: ;
525: }
526: if (method != null) {
527: break;
528: }
529:
530: // Recursively check our parent interfaces
531: method = getAccessibleMethodFromInterfaceNest(
532: interfaces[i], methodName, parameterTypes);
533: if (method != null) {
534: break;
535: }
536: }
537:
538: // Return whatever we have found
539: return (method);
540: }
541: }
|