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