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}