I started using JXPath last week (and I am very excited about it!), and think found a problem in the conversions to String types in BasicTypeConverter. I ran into it using an extension function that refers to the commons StringUtils class. Basically I use the expression "stringutils:getChomp(type,'.')". Due to a problem in the type conversion from a NodeSet to a String, the actual method gets called with String arguments "[EMAIL PROTECTED]" and ".".
I added tests to the BasicTypeConverterTest class to illustrate the problem, and then I made a simple change to BasicTypeConverter to make the adapted test succeed. Presumably there are other approaches possible to resolve the problem (like implementing toString in EvalContext$SimpleNodeSet).
Please find both classes/changes in attachment.
Regards, Joachim Maes
1) added tests BasicTypeConverterTest :
public void testSingletonCollectionToString() {
assertConversion(Collections.singleton("Earth"), String.class,
"Earth");
} public void testSingletonArrayToString() {
assertConversion(new String[] {"Earth"}, String.class, "Earth");
} public void testPointerToString() {
assertConversion(
new Pointer() {
public Object getValue() {
return "value";
}
public Object getNode() {
return null;
}
public void setValue(Object value) {
}
public Object getRootNode() {
return null;
}
public String asPath() {
return null;
}
public Object clone() {
return null;
}
public int compareTo(Object o) {
return 0;
}
},
String.class,
"value");
} public void testNodeSetToString() {
assertConversion(
new NodeSet() {
public List getNodes() {
return null;
}
public List getPointers() {
return null;
}
public List getValues() {
return Collections.singletonList("hello");
}
},
String.class,
"hello");
} // succeeds in current version
public void testNodeSetToInteger() {
assertConversion(
new NodeSet() {
public List getNodes() {
return null;
}
public List getPointers() {
return null;
}
public List getValues() {
return Collections.singletonList("9");
}
},
Integer.class,
new Integer(9));
}2) change to BasicTypeConverter at line 223:
if (toType == String.class
&& !(object instanceof Collection)
&& !fromType.isArray()
&& !(object instanceof NodeSet)
&& !(object instanceof Pointer)) { return object.toString();
}/* * $Header: /home/cvs/jakarta-commons/jxpath/src/java/org/apache/commons/jxpath/util/BasicTypeConverter.java,v 1.7 2003/03/11 00:59:34 dmitri Exp $ * $Revision: 1.7 $ * $Date: 2003/03/11 00:59:34 $ * * ==================================================================== * The Apache Software License, Version 1.1 * * * Copyright (c) 1999-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Commons", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation and was * originally based on software copyright (c) 2001, Plotnix, Inc, * <http://www.plotnix.com/>. * For more information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.commons.jxpath.util;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
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 org.apache.commons.jxpath.JXPathException;
import org.apache.commons.jxpath.Pointer;
import org.apache.commons.jxpath.NodeSet;
/**
* The default implementation of TypeConverter.
*
* @author Dmitri Plotnikov
* @version $Revision: 1.7 $ $Date: 2003/03/11 00:59:34 $
*/
public class BasicTypeConverter implements TypeConverter {
/**
* Returns true if it can convert the supplied
* object to the specified class.
*/
public boolean canConvert(Object object, Class toType) {
if (object == null) {
return true;
}
if (toType == Object.class) {
return true;
}
Class fromType = object.getClass();
if (fromType.equals(toType)) {
return true;
}
if (toType.isAssignableFrom(fromType)) {
return true;
}
if (toType == String.class) {
return true;
}
if (object instanceof Boolean) {
if (toType == boolean.class
|| Number.class.isAssignableFrom(toType)) {
return true;
}
}
else if (object instanceof Number) {
if (toType.isPrimitive()
|| Number.class.isAssignableFrom(toType)) {
return true;
}
}
else if (object instanceof Character) {
if (toType == char.class) {
return true;
}
}
else if (object instanceof String) {
if (toType.isPrimitive()) {
return true;
}
if (toType == Boolean.class
|| toType == Character.class
|| toType == Byte.class
|| toType == Short.class
|| toType == Integer.class
|| toType == Long.class
|| toType == Float.class
|| toType == Double.class) {
return true;
}
}
else if (fromType.isArray()) {
// Collection -> array
if (toType.isArray()) {
Class cType = toType.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;
}
else if (Collection.class.isAssignableFrom(toType)) {
return canCreateCollection(toType);
}
else if (Array.getLength(object) == 1) {
Object value = Array.get(object, 0);
return canConvert(value, toType);
}
}
else if (object instanceof Collection) {
// Collection -> array
if (toType.isArray()) {
Class cType = toType.getComponentType();
Iterator it = ((Collection) object).iterator();
while (it.hasNext()) {
Object value = it.next();
if (!canConvert(value, cType)) {
return false;
}
}
return true;
}
else if (Collection.class.isAssignableFrom(toType)) {
return canCreateCollection(toType);
}
else if (((Collection) object).size() == 1) {
Object value;
if (object instanceof List) {
value = ((List) object).get(0);
}
else {
Iterator it = ((Collection) object).iterator();
value = it.next();
}
return canConvert(value, toType);
}
}
else if (object instanceof NodeSet) {
return canConvert(((NodeSet) object).getValues(), toType);
}
else if (object instanceof Pointer) {
return canConvert(((Pointer) object).getValue(), toType);
}
return false;
}
/**
* Converts the supplied object to the specified
* type. Throws a runtime exception if the conversion is
* not possible.
*/
public Object convert(Object object, Class toType) {
if (object == null) {
if (toType.isPrimitive()) {
return convertNullToPrimitive(toType);
}
return null;
}
if (toType == Object.class) {
return object;
}
Class fromType = object.getClass();
if (fromType.equals(toType) || toType.isAssignableFrom(fromType)) {
return object;
}
if (toType == String.class
&& !(object instanceof Collection)
&& !fromType.isArray()
&& !(object instanceof NodeSet)
&& !(object instanceof Pointer)) {
return object.toString();
}
if (object instanceof Boolean) {
if (toType == boolean.class) {
return object;
}
boolean value = ((Boolean) object).booleanValue();
return allocateNumber(toType, value ? 1 : 0);
}
else if (object instanceof Number) {
double value = ((Number) object).doubleValue();
if (toType == boolean.class || toType == Boolean.class) {
return value == 0.0 ? Boolean.FALSE : Boolean.TRUE;
}
if (toType.isPrimitive()
|| Number.class.isAssignableFrom(toType)) {
return allocateNumber(toType, value);
}
}
else if (object instanceof Character) {
if (toType == char.class) {
return object;
}
}
else if (object instanceof String) {
Object value = convertStringToPrimitive(object, toType);
if (value != null) {
return value;
}
}
else if (fromType.isArray()) {
int length = Array.getLength(object);
if (toType.isArray()) {
Class cType = toType.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;
}
else if (Collection.class.isAssignableFrom(toType)) {
Collection collection = allocateCollection(toType);
for (int i = 0; i < length; i++) {
collection.add(Array.get(object, i));
}
return unmodifiableCollection(collection);
}
else if (length == 1) {
Object value = Array.get(object, 0);
return convert(value, toType);
}
}
else if (object instanceof Collection) {
int length = ((Collection) object).size();
if (toType.isArray()) {
Class cType = toType.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;
}
else if (Collection.class.isAssignableFrom(toType)) {
Collection collection = allocateCollection(toType);
collection.addAll((Collection) object);
return unmodifiableCollection(collection);
}
else if (length == 1) {
Object value;
if (object instanceof List) {
value = ((List) object).get(0);
}
else {
Iterator it = ((Collection) object).iterator();
value = it.next();
}
return convert(value, toType);
}
else {
throw new RuntimeException(
"Cannot convert collection to "
+ toType
+ ", it contains "
+ length
+ " elements");
}
}
else if (object instanceof NodeSet) {
return convert(((NodeSet) object).getValues(), toType);
}
else if (object instanceof Pointer) {
return convert(((Pointer) object).getValue(), toType);
}
throw new RuntimeException(
"Cannot convert " + object.getClass() + " to " + toType);
}
private 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;
}
private Object convertStringToPrimitive(Object object, Class toType) {
if (toType == boolean.class || toType == Boolean.class) {
return Boolean.valueOf((String) object);
}
if (toType == char.class || toType == Character.class) {
return new Character(((String) object).charAt(0));
}
if (toType == byte.class || toType == Byte.class) {
return new Byte((String) object);
}
if (toType == short.class || toType == Short.class) {
return new Short((String) object);
}
if (toType == int.class || toType == Integer.class) {
return new Integer((String) object);
}
if (toType == long.class || toType == Long.class) {
return new Long((String) object);
}
if (toType == float.class || toType == Float.class) {
return new Float((String) object);
}
if (toType == double.class || toType == Double.class) {
return new Double((String) object);
}
return null;
}
private static Number allocateNumber(Class type, double value) {
if (type == Byte.class || type == byte.class) {
return new Byte((byte) value);
}
if (type == Short.class || type == short.class) {
return new Short((short) value);
}
if (type == Integer.class || type == int.class) {
return new Integer((int) value);
}
if (type == Long.class || type == long.class) {
return new Long((long) value);
}
if (type == Float.class || type == float.class) {
return new Float((float) value);
}
if (type == Double.class || type == double.class) {
return new Double(value);
}
return null;
}
private static boolean canCreateCollection(Class type) {
if (!type.isInterface()
&& ((type.getModifiers() | Modifier.ABSTRACT) == 0)) {
return true;
}
if (type == List.class) {
return true;
}
if (type == Set.class) {
return true;
}
return false;
}
private static Collection allocateCollection(Class type) {
if (!type.isInterface()
&& ((type.getModifiers() | Modifier.ABSTRACT) == 0)) {
try {
return (Collection) type.newInstance();
}
catch (Exception ex) {
throw new JXPathException(
"Cannot create collection of type: " + type,
ex);
}
}
if (type == List.class) {
return new ArrayList();
}
if (type == Set.class) {
return new HashSet();
}
throw new RuntimeException("Cannot create collection of type: " + type);
}
private Collection unmodifiableCollection(Collection collection) {
if (collection instanceof List) {
return Collections.unmodifiableList((List) collection);
}
else if (collection instanceof Set) {
return Collections.unmodifiableSet((Set) collection);
}
// Cannot wrap it into a proper unmodifiable collection,
// so we just return the original collection itself
return collection;
}
static class ValueNodeSet implements NodeSet {
private List values;
private List pointers;
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;
}
}
static final class ValuePointer implements Pointer {
private Object bean;
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()";
}
else if (bean instanceof Number) {
String string = bean.toString();
if (string.endsWith(".0")) {
string = string.substring(0, string.length() - 2);
}
return string;
}
else if (bean instanceof Boolean) {
return ((Boolean) bean).booleanValue() ? "true()" : "false()";
}
else if (bean instanceof String) {
return "'" + bean + "'";
}
return "{object of type " + bean.getClass().getName() + "}";
}
}
}
/* * $Header: /home/cvs/jakarta-commons/jxpath/src/test/org/apache/commons/jxpath/util/BasicTypeConverterTest.java,v 1.3 2003/03/11 00:59:40 dmitri Exp $ * $Revision: 1.3 $ * $Date: 2003/03/11 00:59:40 $ * * ==================================================================== * The Apache Software License, Version 1.1 * * * Copyright (c) 1999-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Commons", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation and was * originally based on software copyright (c) 2001, Plotnix, Inc, * <http://www.plotnix.com/>. * For more information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.commons.jxpath.util; import java.lang.reflect.Array; import java.util.*; import junit.framework.TestCase; import org.apache.commons.jxpath.NodeSet; import org.apache.commons.jxpath.Pointer; /** * Tests BasicTypeConverter * * @author Dmitri Plotnikov * @version $Revision: 1.3 $ $Date: 2003/03/11 00:59:40 $ */ public class BasicTypeConverterTest extends TestCase { /** * Construct a new instance of this test case. * * @param name Name of the test case */ public BasicTypeConverterTest(String name) { super(name); } public void testPrimitiveToString() { assertConversion(new Integer(1), String.class, "1"); } public void testArrayToList() { assertConversion( new int[] { 1, 2 }, List.class, Arrays.asList(new Object[] { new Integer(1), new Integer(2)})); } public void testArrayToArray() { assertConversion( new int[] { 1, 2 }, String[].class, Arrays.asList(new String[] { "1", "2" })); } public void testListToArray() { assertConversion( Arrays.asList(new Integer[] { new Integer(1), new Integer(2)}), String[].class, Arrays.asList(new String[] { "1", "2" })); assertConversion( Arrays.asList(new String[] { "1", "2" }), int[].class, Arrays.asList(new Integer[] { new Integer(1), new Integer(2)})); } public void testInvalidConversion() { boolean exception = false; try { TypeUtils.convert("'foo'", Date.class); } catch (Throwable ex) { exception = true; } assertTrue("Type conversion exception", exception); } public void testSingletonCollectionToString() { assertConversion(Collections.singleton("Earth"), String.class, "Earth"); } public void testSingletonArrayToString() { assertConversion(new String[] {"Earth"}, String.class, "Earth"); } public void testPointerToString() { assertConversion( new Pointer() { public Object getValue() { return "value"; } public Object getNode() { return null; } public void setValue(Object value) { } public Object getRootNode() { return null; } public String asPath() { return null; } public Object clone() { return null; } public int compareTo(Object o) { return 0; } }, String.class, "value"); } public void testNodeSetToString() { assertConversion( new NodeSet() { public List getNodes() { return null; } public List getPointers() { return null; } public List getValues() { return Collections.singletonList("hello"); } }, String.class, "hello"); } // succeeds in current version public void testNodeSetToInteger() { assertConversion( new NodeSet() { public List getNodes() { return null; } public List getPointers() { return null; } public List getValues() { return Collections.singletonList("9"); } }, Integer.class, new Integer(9)); } public void assertConversion(Object from, Class toType, Object expected) { boolean can = TypeUtils.canConvert(from, toType); assertTrue("Can convert: " + from.getClass() + " to " + toType, can); Object result = TypeUtils.convert(from, toType); if (result.getClass().isArray()) { ArrayList list = new ArrayList(); for (int j = 0; j < Array.getLength(result); j++) { list.add(Array.get(result, j)); } result = list; } assertEquals( "Convert: " + from.getClass() + " to " + toType, expected, result); } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
