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