    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    }