Hi,

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]

Reply via email to