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 */
017package org.apache.commons.jxpath.util;
018
019import java.lang.reflect.Array;
020import java.lang.reflect.Modifier;
021import java.math.BigDecimal;
022import java.math.BigInteger;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Set;
030import java.util.SortedSet;
031
032import org.apache.commons.beanutils.ConvertUtils;
033import org.apache.commons.beanutils.Converter;
034import org.apache.commons.jxpath.JXPathInvalidAccessException;
035import org.apache.commons.jxpath.JXPathTypeConversionException;
036import org.apache.commons.jxpath.NodeSet;
037import 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 22:10:38 +0200 (Mo, 23 Jun 2008) $
044 */
045public 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}