| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.jxpath.util; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Modifier; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.SortedSet; |
| |
| import org.apache.commons.beanutils.ConvertUtils; |
| import org.apache.commons.beanutils.Converter; |
| import org.apache.commons.jxpath.JXPathInvalidAccessException; |
| import org.apache.commons.jxpath.JXPathTypeConversionException; |
| import org.apache.commons.jxpath.NodeSet; |
| import org.apache.commons.jxpath.Pointer; |
| |
| /** |
| * The default implementation of TypeConverter. |
| * |
| * @author Dmitri Plotnikov |
| * @version $Revision$ $Date$ |
| */ |
| public class BasicTypeConverter implements TypeConverter { |
| |
| /** |
| * Returns true if it can convert the supplied |
| * object to the specified class. |
| * @param object to check |
| * @param toType prospective destination class |
| * @return boolean |
| */ |
| public boolean canConvert(Object object, final Class toType) { |
| if (object == null) { |
| return true; |
| } |
| final Class useType = TypeUtils.wrapPrimitive(toType); |
| Class fromType = object.getClass(); |
| |
| if (useType.isAssignableFrom(fromType)) { |
| return true; |
| } |
| |
| if (useType == String.class) { |
| return true; |
| } |
| |
| if (object instanceof Boolean) { |
| if (Number.class.isAssignableFrom(useType) |
| || "java.util.concurrent.atomic.AtomicBoolean" |
| .equals(useType.getName())) { |
| return true; |
| } |
| } |
| if (object instanceof Number) { |
| if (Number.class.isAssignableFrom(useType) || useType == Boolean.class) { |
| return true; |
| } |
| } |
| if (object instanceof String) { |
| if (useType == Boolean.class |
| || useType == Character.class |
| || useType == Byte.class |
| || useType == Short.class |
| || useType == Integer.class |
| || useType == Long.class |
| || useType == Float.class |
| || useType == Double.class) { |
| return true; |
| } |
| } |
| if (fromType.isArray()) { |
| // Collection -> array |
| if (useType.isArray()) { |
| Class cType = useType.getComponentType(); |
| int length = Array.getLength(object); |
| for (int i = 0; i < length; i++) { |
| Object value = Array.get(object, i); |
| if (!canConvert(value, cType)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (Collection.class.isAssignableFrom(useType)) { |
| return canCreateCollection(useType); |
| } |
| if (Array.getLength(object) > 0) { |
| Object value = Array.get(object, 0); |
| return canConvert(value, useType); |
| } |
| return canConvert("", useType); |
| } |
| if (object instanceof Collection) { |
| // Collection -> array |
| if (useType.isArray()) { |
| Class cType = useType.getComponentType(); |
| Iterator it = ((Collection) object).iterator(); |
| while (it.hasNext()) { |
| Object value = it.next(); |
| if (!canConvert(value, cType)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (Collection.class.isAssignableFrom(useType)) { |
| return canCreateCollection(useType); |
| } |
| if (((Collection) object).size() > 0) { |
| Object value; |
| if (object instanceof List) { |
| value = ((List) object).get(0); |
| } |
| else { |
| Iterator it = ((Collection) object).iterator(); |
| value = it.next(); |
| } |
| return canConvert(value, useType); |
| } |
| return canConvert("", useType); |
| } |
| if (object instanceof NodeSet) { |
| return canConvert(((NodeSet) object).getValues(), useType); |
| } |
| if (object instanceof Pointer) { |
| return canConvert(((Pointer) object).getValue(), useType); |
| } |
| return ConvertUtils.lookup(useType) != null; |
| } |
| |
| /** |
| * Converts the supplied object to the specified |
| * type. Throws a runtime exception if the conversion is |
| * not possible. |
| * @param object to convert |
| * @param toType destination class |
| * @return converted object |
| */ |
| public Object convert(Object object, final Class toType) { |
| if (object == null) { |
| return toType.isPrimitive() ? convertNullToPrimitive(toType) : null; |
| } |
| |
| if (toType == Object.class) { |
| if (object instanceof NodeSet) { |
| return convert(((NodeSet) object).getValues(), toType); |
| } |
| if (object instanceof Pointer) { |
| return convert(((Pointer) object).getValue(), toType); |
| } |
| return object; |
| } |
| final Class useType = TypeUtils.wrapPrimitive(toType); |
| Class fromType = object.getClass(); |
| |
| if (useType.isAssignableFrom(fromType)) { |
| return object; |
| } |
| |
| if (fromType.isArray()) { |
| int length = Array.getLength(object); |
| if (useType.isArray()) { |
| Class cType = useType.getComponentType(); |
| |
| Object array = Array.newInstance(cType, length); |
| for (int i = 0; i < length; i++) { |
| Object value = Array.get(object, i); |
| Array.set(array, i, convert(value, cType)); |
| } |
| return array; |
| } |
| if (Collection.class.isAssignableFrom(useType)) { |
| Collection collection = allocateCollection(useType); |
| for (int i = 0; i < length; i++) { |
| collection.add(Array.get(object, i)); |
| } |
| return unmodifiableCollection(collection); |
| } |
| if (length > 0) { |
| Object value = Array.get(object, 0); |
| return convert(value, useType); |
| } |
| return convert("", useType); |
| } |
| if (object instanceof Collection) { |
| int length = ((Collection) object).size(); |
| if (useType.isArray()) { |
| Class cType = useType.getComponentType(); |
| Object array = Array.newInstance(cType, length); |
| Iterator it = ((Collection) object).iterator(); |
| for (int i = 0; i < length; i++) { |
| Object value = it.next(); |
| Array.set(array, i, convert(value, cType)); |
| } |
| return array; |
| } |
| if (Collection.class.isAssignableFrom(useType)) { |
| Collection collection = allocateCollection(useType); |
| collection.addAll((Collection) object); |
| return unmodifiableCollection(collection); |
| } |
| if (length > 0) { |
| Object value; |
| if (object instanceof List) { |
| value = ((List) object).get(0); |
| } |
| else { |
| Iterator it = ((Collection) object).iterator(); |
| value = it.next(); |
| } |
| return convert(value, useType); |
| } |
| return convert("", useType); |
| } |
| if (object instanceof NodeSet) { |
| return convert(((NodeSet) object).getValues(), useType); |
| } |
| if (object instanceof Pointer) { |
| return convert(((Pointer) object).getValue(), useType); |
| } |
| if (useType == String.class) { |
| return object.toString(); |
| } |
| if (object instanceof Boolean) { |
| if (Number.class.isAssignableFrom(useType)) { |
| return allocateNumber(useType, ((Boolean) object).booleanValue() ? 1 : 0); |
| } |
| if ("java.util.concurrent.atomic.AtomicBoolean".equals(useType.getName())) { |
| try { |
| return useType.getConstructor(new Class[] { boolean.class }) |
| .newInstance(new Object[] { object }); |
| } |
| catch (Exception e) { |
| throw new JXPathTypeConversionException(useType.getName(), e); |
| } |
| } |
| } |
| if (object instanceof Number) { |
| double value = ((Number) object).doubleValue(); |
| if (useType == Boolean.class) { |
| return value == 0.0 ? Boolean.FALSE : Boolean.TRUE; |
| } |
| if (Number.class.isAssignableFrom(useType)) { |
| return allocateNumber(useType, value); |
| } |
| } |
| if (object instanceof String) { |
| Object value = convertStringToPrimitive(object, useType); |
| if (value != null) { |
| return value; |
| } |
| } |
| |
| Converter converter = ConvertUtils.lookup(useType); |
| if (converter != null) { |
| return converter.convert(useType, object); |
| } |
| |
| throw new JXPathTypeConversionException("Cannot convert " |
| + object.getClass() + " to " + useType); |
| } |
| |
| /** |
| * Convert null to a primitive type. |
| * @param toType destination class |
| * @return a wrapper |
| */ |
| protected Object convertNullToPrimitive(Class toType) { |
| if (toType == boolean.class) { |
| return Boolean.FALSE; |
| } |
| if (toType == char.class) { |
| return new Character('\0'); |
| } |
| if (toType == byte.class) { |
| return new Byte((byte) 0); |
| } |
| if (toType == short.class) { |
| return new Short((short) 0); |
| } |
| if (toType == int.class) { |
| return new Integer(0); |
| } |
| if (toType == long.class) { |
| return new Long(0L); |
| } |
| if (toType == float.class) { |
| return new Float(0.0f); |
| } |
| if (toType == double.class) { |
| return new Double(0.0); |
| } |
| return null; |
| } |
| |
| /** |
| * Convert a string to a primitive type. |
| * @param object String |
| * @param toType destination class |
| * @return wrapper |
| */ |
| protected Object convertStringToPrimitive(Object object, Class toType) { |
| toType = TypeUtils.wrapPrimitive(toType); |
| if (toType == Boolean.class) { |
| return Boolean.valueOf((String) object); |
| } |
| if (toType == Character.class) { |
| return new Character(((String) object).charAt(0)); |
| } |
| if (toType == Byte.class) { |
| return new Byte((String) object); |
| } |
| if (toType == Short.class) { |
| return new Short((String) object); |
| } |
| if (toType == Integer.class) { |
| return new Integer((String) object); |
| } |
| if (toType == Long.class) { |
| return new Long((String) object); |
| } |
| if (toType == Float.class) { |
| return new Float((String) object); |
| } |
| if (toType == Double.class) { |
| return new Double((String) object); |
| } |
| return null; |
| } |
| |
| /** |
| * Allocate a number of a given type and value. |
| * @param type destination class |
| * @param value double |
| * @return Number |
| */ |
| protected Number allocateNumber(Class type, double value) { |
| type = TypeUtils.wrapPrimitive(type); |
| if (type == Byte.class) { |
| return new Byte((byte) value); |
| } |
| if (type == Short.class) { |
| return new Short((short) value); |
| } |
| if (type == Integer.class) { |
| return new Integer((int) value); |
| } |
| if (type == Long.class) { |
| return new Long((long) value); |
| } |
| if (type == Float.class) { |
| return new Float((float) value); |
| } |
| if (type == Double.class) { |
| return new Double(value); |
| } |
| if (type == BigInteger.class) { |
| return BigInteger.valueOf((long) value); |
| } |
| if (type == BigDecimal.class) { |
| return new BigDecimal(value); |
| } |
| String classname = type.getName(); |
| Class initialValueType = null; |
| if ("java.util.concurrent.atomic.AtomicInteger".equals(classname)) { |
| initialValueType = int.class; |
| } |
| if ("java.util.concurrent.atomic.AtomicLong".equals(classname)) { |
| initialValueType = long.class; |
| } |
| if (initialValueType != null) { |
| try { |
| return (Number) type.getConstructor( |
| new Class[] { initialValueType }) |
| .newInstance( |
| new Object[] { allocateNumber(initialValueType, |
| value) }); |
| } |
| catch (Exception e) { |
| throw new JXPathTypeConversionException(classname, e); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Learn whether this BasicTypeConverter can create a collection of the specified type. |
| * @param type prospective destination class |
| * @return boolean |
| */ |
| protected boolean canCreateCollection(Class type) { |
| if (!type.isInterface() |
| && ((type.getModifiers() & Modifier.ABSTRACT) == 0)) { |
| try { |
| type.getConstructor(new Class[0]); |
| return true; |
| } |
| catch (Exception e) { |
| return false; |
| } |
| } |
| return type == List.class || type == Collection.class || type == Set.class; |
| } |
| |
| /** |
| * Create a collection of a given type. |
| * @param type destination class |
| * @return Collection |
| */ |
| protected Collection allocateCollection(Class type) { |
| if (!type.isInterface() |
| && ((type.getModifiers() & Modifier.ABSTRACT) == 0)) { |
| try { |
| return (Collection) type.newInstance(); |
| } |
| catch (Exception ex) { |
| throw new JXPathInvalidAccessException( |
| "Cannot create collection of type: " + type, ex); |
| } |
| } |
| |
| if (type == List.class || type == Collection.class) { |
| return new ArrayList(); |
| } |
| if (type == Set.class) { |
| return new HashSet(); |
| } |
| throw new JXPathInvalidAccessException( |
| "Cannot create collection of type: " + type); |
| } |
| |
| /** |
| * Get an unmodifiable version of a collection. |
| * @param collection to wrap |
| * @return Collection |
| */ |
| protected Collection unmodifiableCollection(Collection collection) { |
| if (collection instanceof List) { |
| return Collections.unmodifiableList((List) collection); |
| } |
| if (collection instanceof SortedSet) { |
| return Collections.unmodifiableSortedSet((SortedSet) collection); |
| } |
| if (collection instanceof Set) { |
| return Collections.unmodifiableSet((Set) collection); |
| } |
| return Collections.unmodifiableCollection(collection); |
| } |
| |
| /** |
| * NodeSet implementation |
| */ |
| static final class ValueNodeSet implements NodeSet { |
| private List values; |
| private List pointers; |
| |
| /** |
| * Create a new ValueNodeSet. |
| * @param values to return |
| */ |
| public ValueNodeSet(List values) { |
| this.values = values; |
| } |
| |
| public List getValues() { |
| return Collections.unmodifiableList(values); |
| } |
| |
| public List getNodes() { |
| return Collections.unmodifiableList(values); |
| } |
| |
| public List getPointers() { |
| if (pointers == null) { |
| pointers = new ArrayList(); |
| for (int i = 0; i < values.size(); i++) { |
| pointers.add(new ValuePointer(values.get(i))); |
| } |
| pointers = Collections.unmodifiableList(pointers); |
| } |
| return pointers; |
| } |
| } |
| |
| /** |
| * Value pointer |
| */ |
| static final class ValuePointer implements Pointer { |
| private Object bean; |
| |
| private static final long serialVersionUID = -4817239482392206188L; |
| |
| /** |
| * Create a new ValuePointer. |
| * @param object value |
| */ |
| public ValuePointer(Object object) { |
| this.bean = object; |
| } |
| |
| public Object getValue() { |
| return bean; |
| } |
| |
| public Object getNode() { |
| return bean; |
| } |
| |
| public Object getRootNode() { |
| return bean; |
| } |
| |
| public void setValue(Object value) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Object clone() { |
| return this; |
| } |
| |
| public int compareTo(Object object) { |
| return 0; |
| } |
| |
| public String asPath() { |
| if (bean == null) { |
| return "null()"; |
| } |
| if (bean instanceof Number) { |
| String string = bean.toString(); |
| if (string.endsWith(".0")) { |
| string = string.substring(0, string.length() - 2); |
| } |
| return string; |
| } |
| if (bean instanceof Boolean) { |
| return ((Boolean) bean).booleanValue() ? "true()" : "false()"; |
| } |
| if (bean instanceof String) { |
| return "'" + bean + "'"; |
| } |
| return "{object of type " + bean.getClass().getName() + "}"; |
| } |
| } |
| } |