001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.jxpath.util;
018    
019    import java.lang.reflect.Array;
020    import java.lang.reflect.Modifier;
021    import java.math.BigDecimal;
022    import java.math.BigInteger;
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.Collections;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Set;
030    import java.util.SortedSet;
031    
032    import org.apache.commons.beanutils.ConvertUtils;
033    import org.apache.commons.beanutils.Converter;
034    import org.apache.commons.jxpath.JXPathInvalidAccessException;
035    import org.apache.commons.jxpath.JXPathTypeConversionException;
036    import org.apache.commons.jxpath.NodeSet;
037    import org.apache.commons.jxpath.Pointer;
038    
039    /**
040     * The default implementation of TypeConverter.
041     *
042     * @author Dmitri Plotnikov
043     * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
044     */
045    public class BasicTypeConverter implements TypeConverter {
046    
047        /**
048         * Returns true if it can convert the supplied
049         * object to the specified class.
050         * @param object to check
051         * @param toType prospective destination class
052         * @return boolean
053         */
054        public boolean canConvert(Object object, final Class toType) {
055            if (object == null) {
056                return true;
057            }
058            final Class useType = TypeUtils.wrapPrimitive(toType);
059            Class fromType = object.getClass();
060    
061            if (useType.isAssignableFrom(fromType)) {
062                return true;
063            }
064    
065            if (useType == String.class) {
066                return true;
067            }
068    
069            if (object instanceof Boolean && (Number.class.isAssignableFrom(useType)
070                    || "java.util.concurrent.atomic.AtomicBoolean"
071                            .equals(useType.getName()))) {
072                return true;
073            }
074            if (object instanceof Number
075                    && (Number.class.isAssignableFrom(useType) || useType == Boolean.class)) {
076                return true;
077            }
078            if (object instanceof String
079                    && (useType == Boolean.class
080                            || useType == Character.class
081                            || useType == Byte.class
082                            || useType == Short.class
083                            || useType == Integer.class
084                            || useType == Long.class
085                            || useType == Float.class
086                            || useType == Double.class)) {
087                    return true;
088            }
089            if (fromType.isArray()) {
090                // Collection -> array
091                if (useType.isArray()) {
092                    Class cType = useType.getComponentType();
093                    int length = Array.getLength(object);
094                    for (int i = 0; i < length; i++) {
095                        Object value = Array.get(object, i);
096                        if (!canConvert(value, cType)) {
097                            return false;
098                        }
099                    }
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    }