blob: 958ce0c9f83c48400a9280d8eb9503ec14214ffa [file] [log] [blame]
/*
* 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() + "}";
}
}
}