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