    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
    018package org.apache.commons.jxpath.util;
    019
    020import java.lang.reflect.Array;
    021import java.lang.reflect.Modifier;
    022import java.math.BigDecimal;
    023import java.math.BigInteger;
    024import java.util.ArrayList;
    025import java.util.Collection;
    026import java.util.Collections;
    027import java.util.HashSet;
    028import java.util.Iterator;
    029import java.util.List;
    030import java.util.Set;
    031import java.util.SortedSet;
    032
    033import org.apache.commons.beanutils.ConvertUtils;
    034import org.apache.commons.beanutils.Converter;
    035import org.apache.commons.jxpath.JXPathInvalidAccessException;
    036import org.apache.commons.jxpath.JXPathTypeConversionException;
    037import org.apache.commons.jxpath.NodeSet;
    038import org.apache.commons.jxpath.Pointer;
    039
    040/**
    041 * The default implementation of {@link TypeConverter}.
    042 */
    043public class BasicTypeConverter implements TypeConverter {
    044
    045    /**
    046     * Value {@link Pointer}.
    047     */
    048    static final class ValuePointer implements Pointer {
    049
    050        private static final long serialVersionUID = -4817239482392206188L;
    051        private final Object bean;
    052
    053        /**
    054         * Constructs a new ValuePointer.
    055         *
    056         * @param object value
    057         */
    058        public ValuePointer(final Object object) {
    059            this.bean = object;
    060        }
    061
    062        @Override
    063        public String asPath() {
    064            if (bean == null) {
    065                return "null()";
    066            }
    067            if (bean instanceof Number) {
    068                String string = bean.toString();
    069                if (string.endsWith(".0")) {
    070                    string = string.substring(0, string.length() - 2);
    071                }
    072                return string;
    073            }
    074            if (bean instanceof Boolean) {
    075                return ((Boolean) bean).booleanValue() ? "true()" : "false()";
    076            }
    077            if (bean instanceof String) {
    078                return "'" + bean + "'";
    079            }
    080            return "{object of type " + bean.getClass().getName() + "}";
    081        }
    082
    083        @Override
    084        public Object clone() {
    085            return this;
    086        }
    087
    088        @Override
    089        public int compareTo(final Object object) {
    090            return 0;
    091        }
    092
    093        @Override
    094        public Object getNode() {
    095            return bean;
    096        }
    097
    098        @Override
    099        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}