View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath.util;
18  
19  import java.lang.reflect.Array;
20  import java.lang.reflect.Modifier;
21  import java.math.BigDecimal;
22  import java.math.BigInteger;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.SortedSet;
31  
32  import org.apache.commons.beanutils.ConvertUtils;
33  import org.apache.commons.beanutils.Converter;
34  import org.apache.commons.jxpath.JXPathInvalidAccessException;
35  import org.apache.commons.jxpath.JXPathTypeConversionException;
36  import org.apache.commons.jxpath.NodeSet;
37  import org.apache.commons.jxpath.Pointer;
38  
39  /**
40   * The default implementation of TypeConverter.
41   *
42   * @author Dmitri Plotnikov
43   * @version $Revision: 670727 $ $Date: 2008-06-23 22:10:38 +0200 (Mo, 23 Jun 2008) $
44   */
45  public class BasicTypeConverter implements TypeConverter {
46  
47      /**
48       * Returns true if it can convert the supplied
49       * object to the specified class.
50       * @param object to check
51       * @param toType prospective destination class
52       * @return boolean
53       */
54      public boolean canConvert(Object object, final Class toType) {
55          if (object == null) {
56              return true;
57          }
58          final Class useType = TypeUtils.wrapPrimitive(toType);
59          Class fromType = object.getClass();
60  
61          if (useType.isAssignableFrom(fromType)) {
62              return true;
63          }
64  
65          if (useType == String.class) {
66              return true;
67          }
68  
69          if (object instanceof Boolean && (Number.class.isAssignableFrom(useType)
70                  || "java.util.concurrent.atomic.AtomicBoolean"
71                          .equals(useType.getName()))) {
72              return true;
73          }
74          if (object instanceof Number
75                  && (Number.class.isAssignableFrom(useType) || useType == Boolean.class)) {
76              return true;
77          }
78          if (object instanceof String
79                  && (useType == Boolean.class
80                          || useType == Character.class
81                          || useType == Byte.class
82                          || useType == Short.class
83                          || useType == Integer.class
84                          || useType == Long.class
85                          || useType == Float.class
86                          || useType == Double.class)) {
87                  return true;
88          }
89          if (fromType.isArray()) {
90              // Collection -> array
91              if (useType.isArray()) {
92                  Class cType = useType.getComponentType();
93                  int length = Array.getLength(object);
94                  for (int i = 0; i < length; i++) {
95                      Object value = Array.get(object, i);
96                      if (!canConvert(value, cType)) {
97                          return false;
98                      }
99                  }
100                 return true;
101             }
102             if (Collection.class.isAssignableFrom(useType)) {
103                 return canCreateCollection(useType);
104             }
105             if (Array.getLength(object) > 0) {
106                 Object value = Array.get(object, 0);
107                 return canConvert(value, useType);
108             }
109             return canConvert("", useType);
110         }
111         if (object instanceof Collection) {
112             // Collection -> array
113             if (useType.isArray()) {
114                 Class cType = useType.getComponentType();
115                 Iterator it = ((Collection) object).iterator();
116                 while (it.hasNext()) {
117                     Object value = it.next();
118                     if (!canConvert(value, cType)) {
119                         return false;
120                     }
121                 }
122                 return true;
123             }
124             if (Collection.class.isAssignableFrom(useType)) {
125                 return canCreateCollection(useType);
126             }
127             if (((Collection) object).size() > 0) {
128                 Object value;
129                 if (object instanceof List) {
130                     value = ((List) object).get(0);
131                 }
132                 else {
133                     Iterator it = ((Collection) object).iterator();
134                     value = it.next();
135                 }
136                 return canConvert(value, useType);
137             }
138             return canConvert("", useType);
139         }
140         if (object instanceof NodeSet) {
141             return canConvert(((NodeSet) object).getValues(), useType);
142         }
143         if (object instanceof Pointer) {
144             return canConvert(((Pointer) object).getValue(), useType);
145         }
146         return ConvertUtils.lookup(useType) != null;
147     }
148 
149     /**
150      * Converts the supplied object to the specified
151      * type. Throws a runtime exception if the conversion is
152      * not possible.
153      * @param object to convert
154      * @param toType destination class
155      * @return converted object
156      */
157     public Object convert(Object object, final Class toType) {
158         if (object == null) {
159             return toType.isPrimitive() ? convertNullToPrimitive(toType) : null;
160         }
161 
162         if (toType == Object.class) {
163             if (object instanceof NodeSet) {
164                 return convert(((NodeSet) object).getValues(), toType);
165             }
166             if (object instanceof Pointer) {
167                 return convert(((Pointer) object).getValue(), toType);
168             }
169             return object;
170         }
171         final Class useType = TypeUtils.wrapPrimitive(toType);
172         Class fromType = object.getClass();
173 
174         if (useType.isAssignableFrom(fromType)) {
175             return object;
176         }
177 
178         if (fromType.isArray()) {
179             int length = Array.getLength(object);
180             if (useType.isArray()) {
181                 Class cType = useType.getComponentType();
182 
183                 Object array = Array.newInstance(cType, length);
184                 for (int i = 0; i < length; i++) {
185                     Object value = Array.get(object, i);
186                     Array.set(array, i, convert(value, cType));
187                 }
188                 return array;
189             }
190             if (Collection.class.isAssignableFrom(useType)) {
191                 Collection collection = allocateCollection(useType);
192                 for (int i = 0; i < length; i++) {
193                     collection.add(Array.get(object, i));
194                 }
195                 return unmodifiableCollection(collection);
196             }
197             if (length > 0) {
198                 Object value = Array.get(object, 0);
199                 return convert(value, useType);
200             }
201             return convert("", useType);
202         }
203         if (object instanceof Collection) {
204             int length = ((Collection) object).size();
205             if (useType.isArray()) {
206                 Class cType = useType.getComponentType();
207                 Object array = Array.newInstance(cType, length);
208                 Iterator it = ((Collection) object).iterator();
209                 for (int i = 0; i < length; i++) {
210                     Object value = it.next();
211                     Array.set(array, i, convert(value, cType));
212                 }
213                 return array;
214             }
215             if (Collection.class.isAssignableFrom(useType)) {
216                 Collection collection = allocateCollection(useType);
217                 collection.addAll((Collection) object);
218                 return unmodifiableCollection(collection);
219             }
220             if (length > 0) {
221                 Object value;
222                 if (object instanceof List) {
223                     value = ((List) object).get(0);
224                 }
225                 else {
226                     Iterator it = ((Collection) object).iterator();
227                     value = it.next();
228                 }
229                 return convert(value, useType);
230             }
231             return convert("", useType);
232         }
233         if (object instanceof NodeSet) {
234             return convert(((NodeSet) object).getValues(), useType);
235         }
236         if (object instanceof Pointer) {
237             return convert(((Pointer) object).getValue(), useType);
238         }
239         if (useType == String.class) {
240             return object.toString();
241         }
242         if (object instanceof Boolean) {
243             if (Number.class.isAssignableFrom(useType)) {
244                 return allocateNumber(useType, ((Boolean) object).booleanValue() ? 1 : 0);
245             }
246             if ("java.util.concurrent.atomic.AtomicBoolean".equals(useType.getName())) {
247                 try {
248                     return useType.getConstructor(new Class[] { boolean.class })
249                             .newInstance(new Object[] { object });
250                 }
251                 catch (Exception e) {
252                     throw new JXPathTypeConversionException(useType.getName(), e);
253                 }
254             }
255         }
256         if (object instanceof Number) {
257             double value = ((Number) object).doubleValue();
258             if (useType == Boolean.class) {
259                 return value == 0.0 ? Boolean.FALSE : Boolean.TRUE;
260             }
261             if (Number.class.isAssignableFrom(useType)) {
262                 return allocateNumber(useType, value);
263             }
264         }
265         if (object instanceof String) {
266             Object value = convertStringToPrimitive(object, useType);
267             if (value != null) {
268                 return value;
269             }
270         }
271 
272         Converter converter = ConvertUtils.lookup(useType);
273         if (converter != null) {
274             return converter.convert(useType, object);
275         }
276 
277         throw new JXPathTypeConversionException("Cannot convert "
278                 + object.getClass() + " to " + useType);
279     }
280 
281     /**
282      * Convert null to a primitive type.
283      * @param toType destination class
284      * @return a wrapper
285      */
286     protected Object convertNullToPrimitive(Class toType) {
287         if (toType == boolean.class) {
288             return Boolean.FALSE;
289         }
290         if (toType == char.class) {
291             return new Character('\0');
292         }
293         if (toType == byte.class) {
294             return new Byte((byte) 0);
295         }
296         if (toType == short.class) {
297             return new Short((short) 0);
298         }
299         if (toType == int.class) {
300             return new Integer(0);
301         }
302         if (toType == long.class) {
303             return new Long(0L);
304         }
305         if (toType == float.class) {
306             return new Float(0.0f);
307         }
308         if (toType == double.class) {
309             return new Double(0.0);
310         }
311         return null;
312     }
313 
314     /**
315      * Convert a string to a primitive type.
316      * @param object String
317      * @param toType destination class
318      * @return wrapper
319      */
320     protected Object convertStringToPrimitive(Object object, Class toType) {
321         toType = TypeUtils.wrapPrimitive(toType);
322         if (toType == Boolean.class) {
323             return Boolean.valueOf((String) object);
324         }
325         if (toType == Character.class) {
326             return new Character(((String) object).charAt(0));
327         }
328         if (toType == Byte.class) {
329             return new Byte((String) object);
330         }
331         if (toType == Short.class) {
332             return new Short((String) object);
333         }
334         if (toType == Integer.class) {
335             return new Integer((String) object);
336         }
337         if (toType == Long.class) {
338             return new Long((String) object);
339         }
340         if (toType == Float.class) {
341             return new Float((String) object);
342         }
343         if (toType == Double.class) {
344             return new Double((String) object);
345         }
346         return null;
347     }
348 
349     /**
350      * Allocate a number of a given type and value.
351      * @param type destination class
352      * @param value double
353      * @return Number
354      */
355     protected Number allocateNumber(Class type, double value) {
356         type = TypeUtils.wrapPrimitive(type);
357         if (type == Byte.class) {
358             return new Byte((byte) value);
359         }
360         if (type == Short.class) {
361             return new Short((short) value);
362         }
363         if (type == Integer.class) {
364             return new Integer((int) value);
365         }
366         if (type == Long.class) {
367             return new Long((long) value);
368         }
369         if (type == Float.class) {
370             return new Float((float) value);
371         }
372         if (type == Double.class) {
373             return new Double(value);
374         }
375         if (type == BigInteger.class) {
376             return BigInteger.valueOf((long) value);
377         }
378         if (type == BigDecimal.class) {
379             return new BigDecimal(value);
380         }
381         String classname = type.getName();
382         Class initialValueType = null;
383         if ("java.util.concurrent.atomic.AtomicInteger".equals(classname)) {
384             initialValueType = int.class;
385         }
386         if ("java.util.concurrent.atomic.AtomicLong".equals(classname)) {
387             initialValueType = long.class;
388         }
389         if (initialValueType != null) {
390             try {
391                 return (Number) type.getConstructor(
392                         new Class[] { initialValueType })
393                         .newInstance(
394                                 new Object[] { allocateNumber(initialValueType,
395                                         value) });
396             }
397             catch (Exception e) {
398                 throw new JXPathTypeConversionException(classname, e);
399             }
400         }
401         return null;
402     }
403 
404     /**
405      * Learn whether this BasicTypeConverter can create a collection of the specified type.
406      * @param type prospective destination class
407      * @return boolean
408      */
409     protected boolean canCreateCollection(Class type) {
410         if (!type.isInterface()
411                 && ((type.getModifiers() & Modifier.ABSTRACT) == 0)) {
412             try {
413                 type.getConstructor(new Class[0]);
414                 return true;
415             }
416             catch (Exception e) {
417                 return false;
418             }
419         }
420         return type == List.class || type == Collection.class || type == Set.class;
421     }
422 
423     /**
424      * Create a collection of a given type.
425      * @param type destination class
426      * @return Collection
427      */
428     protected Collection allocateCollection(Class type) {
429         if (!type.isInterface()
430                 && ((type.getModifiers() & Modifier.ABSTRACT) == 0)) {
431             try {
432                 return (Collection) type.newInstance();
433             }
434             catch (Exception ex) {
435                 throw new JXPathInvalidAccessException(
436                         "Cannot create collection of type: " + type, ex);
437             }
438         }
439 
440         if (type == List.class || type == Collection.class) {
441             return new ArrayList();
442         }
443         if (type == Set.class) {
444             return new HashSet();
445         }
446         throw new JXPathInvalidAccessException(
447                 "Cannot create collection of type: " + type);
448     }
449 
450     /**
451      * Get an unmodifiable version of a collection.
452      * @param collection to wrap
453      * @return Collection
454      */
455     protected Collection unmodifiableCollection(Collection collection) {
456         if (collection instanceof List) {
457             return Collections.unmodifiableList((List) collection);
458         }
459         if (collection instanceof SortedSet) {
460             return Collections.unmodifiableSortedSet((SortedSet) collection);
461         }
462         if (collection instanceof Set) {
463             return Collections.unmodifiableSet((Set) collection);
464         }
465         return Collections.unmodifiableCollection(collection);
466     }
467 
468     /**
469      * NodeSet implementation
470      */
471     static final class ValueNodeSet implements NodeSet {
472         private List values;
473         private List pointers;
474 
475         /**
476          * Create a new ValueNodeSet.
477          * @param values to return
478          */
479         public ValueNodeSet(List values) {
480            this.values = values;
481         }
482 
483         public List getValues() {
484             return Collections.unmodifiableList(values);
485         }
486 
487         public List getNodes() {
488             return Collections.unmodifiableList(values);
489         }
490 
491         public List getPointers() {
492             if (pointers == null) {
493                 pointers = new ArrayList();
494                 for (int i = 0; i < values.size(); i++) {
495                     pointers.add(new ValuePointer(values.get(i)));
496                 }
497                 pointers = Collections.unmodifiableList(pointers);
498             }
499             return pointers;
500         }
501     }
502 
503     /**
504      * Value pointer
505      */
506     static final class ValuePointer implements Pointer {
507         private static final long serialVersionUID = -4817239482392206188L;
508 
509         private Object bean;
510 
511         /**
512          * Create a new ValuePointer.
513          * @param object value
514          */
515         public ValuePointer(Object object) {
516             this.bean = object;
517         }
518 
519         public Object getValue() {
520             return bean;
521         }
522 
523         public Object getNode() {
524             return bean;
525         }
526 
527         public Object getRootNode() {
528             return bean;
529         }
530 
531         public void setValue(Object value) {
532             throw new UnsupportedOperationException();
533         }
534 
535         public Object clone() {
536             return this;
537         }
538 
539         public int compareTo(Object object) {
540             return 0;
541         }
542 
543         public String asPath() {
544             if (bean == null) {
545                 return "null()";
546             }
547             if (bean instanceof Number) {
548                 String string = bean.toString();
549                 if (string.endsWith(".0")) {
550                     string = string.substring(0, string.length() - 2);
551                 }
552                 return string;
553             }
554             if (bean instanceof Boolean) {
555                 return ((Boolean) bean).booleanValue() ? "true()" : "false()";
556             }
557             if (bean instanceof String) {
558                 return "'" + bean + "'";
559             }
560             return "{object of type " + bean.getClass().getName() + "}";
561         }
562     }
563 }