All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.PJConverter Maven / Gradle / Ivy

There is a newer version: 9.8.0.8
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.expr;

import net.sf.saxon.Configuration;
import net.sf.saxon.lib.ExternalObjectModel;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.wrapper.VirtualNode;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
import net.sf.saxon.value.StringValue;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * This class together with its embedded subclasses handles conversion from XPath values
 * to Java values
 */
public abstract class PJConverter implements Serializable {

    private static HashMap jpmap = new HashMap();

    static {
        jpmap.put(boolean.class, SequenceType.SINGLE_BOOLEAN);
        jpmap.put(Boolean.class, SequenceType.OPTIONAL_BOOLEAN);
        jpmap.put(String.class, SequenceType.OPTIONAL_STRING);
        jpmap.put(CharSequence.class, SequenceType.OPTIONAL_STRING);
        // Mappings for long and int are chosen to avoid static type errors when
        // a Java method expecting long or int is called with an integer literal
        jpmap.put(long.class, SequenceType.SINGLE_INTEGER);
        jpmap.put(Long.class, SequenceType.OPTIONAL_INTEGER);
        jpmap.put(int.class, SequenceType.SINGLE_INTEGER);
        jpmap.put(Integer.class, SequenceType.OPTIONAL_INTEGER);
        jpmap.put(short.class, SequenceType.SINGLE_SHORT);
        jpmap.put(Short.class, SequenceType.OPTIONAL_SHORT);
        jpmap.put(byte.class, SequenceType.SINGLE_BYTE);
        jpmap.put(Byte.class, SequenceType.OPTIONAL_BYTE);
        jpmap.put(float.class, SequenceType.SINGLE_FLOAT);
        jpmap.put(Float.class, SequenceType.OPTIONAL_FLOAT);
        jpmap.put(double.class, SequenceType.SINGLE_DOUBLE);
        jpmap.put(Double.class, SequenceType.OPTIONAL_DOUBLE);
        jpmap.put(URI.class, SequenceType.OPTIONAL_ANY_URI);
        jpmap.put(URL.class, SequenceType.OPTIONAL_ANY_URI);
        jpmap.put(BigInteger.class, SequenceType.OPTIONAL_INTEGER);
        jpmap.put(BigDecimal.class, SequenceType.OPTIONAL_DECIMAL);

        jpmap.put(StringValue.class, SequenceType.OPTIONAL_STRING);
        jpmap.put(BooleanValue.class, SequenceType.OPTIONAL_BOOLEAN);
        jpmap.put(DoubleValue.class, SequenceType.OPTIONAL_DOUBLE);
        jpmap.put(FloatValue.class, SequenceType.OPTIONAL_FLOAT);
        jpmap.put(DecimalValue.class, SequenceType.OPTIONAL_DECIMAL);
        jpmap.put(IntegerValue.class, SequenceType.OPTIONAL_INTEGER);
        jpmap.put(AnyURIValue.class, SequenceType.OPTIONAL_ANY_URI);
        jpmap.put(QNameValue.class, SequenceType.OPTIONAL_QNAME);
        jpmap.put(NotationValue.class, SequenceType.OPTIONAL_NOTATION);
        jpmap.put(DateValue.class, SequenceType.OPTIONAL_DATE);
        jpmap.put(DateTimeValue.class, SequenceType.OPTIONAL_DATE_TIME);
        jpmap.put(TimeValue.class, SequenceType.OPTIONAL_TIME);
        jpmap.put(DurationValue.class, SequenceType.OPTIONAL_DURATION);
        jpmap.put(DayTimeDurationValue.class, SequenceType.OPTIONAL_DAY_TIME_DURATION);
        jpmap.put(YearMonthDurationValue.class, SequenceType.OPTIONAL_YEAR_MONTH_DURATION);
        jpmap.put(GYearValue.class, SequenceType.OPTIONAL_G_YEAR);
        jpmap.put(GYearMonthValue.class, SequenceType.OPTIONAL_G_YEAR_MONTH);
        jpmap.put(GMonthValue.class, SequenceType.OPTIONAL_G_MONTH);
        jpmap.put(GMonthDayValue.class, SequenceType.OPTIONAL_G_MONTH_DAY);
        jpmap.put(GDayValue.class, SequenceType.OPTIONAL_G_DAY);
        jpmap.put(Base64BinaryValue.class, SequenceType.OPTIONAL_BASE64_BINARY);
        jpmap.put(HexBinaryValue.class, SequenceType.OPTIONAL_HEX_BINARY);

    }



    /**
     * Get the nearest XPath equivalent to a Java class. A function call will
     * be type-checked against an XPath function signature in which the Java classes
     * are replaced by their nearest equivalent XPath types
     * @param javaClass a Java class
     * @return the nearest equivalent XPath SequenceType
     */

    public static SequenceType getEquivalentItemType(Class javaClass) {
        return jpmap.get(javaClass);
    }

    /**
     * Convert an XPath value to a Java value of a specified class
     *
     * @param value the supplied XPath value
     * @param targetClass the class of the required Java value
     * @param context the XPath dynamic context
     * @return the corresponding Java value, which is guaranteed to be an instance of the
     * target class (except that an empty sequence is converted to null)
     * @throws XPathException if the conversion is not possible or fails
     */

    /*@Nullable*/ public abstract Object convert(Sequence value, Class targetClass, XPathContext context)
            throws XPathException;

    /**
     * Factory method to instantiate a converter from a given XPath type to a given Java class
     * @param config the Saxon Configuration
     * @param itemType the item type of the XPath value to be converted
     * @param cardinality the cardinality of the XPath value to be converted
     * @param targetClass the Java class required for the conversion result
     * @return a suitable converter
     * @throws net.sf.saxon.trans.XPathException if no conversion is possible
     */

    public static PJConverter allocate(Configuration config, ItemType itemType,
                                       int cardinality, Class targetClass)
    throws XPathException {
        TypeHierarchy th = config.getTypeHierarchy();
        if (targetClass == SequenceIterator.class) {
            return ToSequenceIterator.INSTANCE;
        }
        if (targetClass == Sequence.class || targetClass == Item.class) {
            return Identity.INSTANCE;
        }
        if (targetClass == GroundedValue.class | targetClass == SequenceExtent.class) {
            return ToSequenceExtent.INSTANCE;
        }

        if (!itemType.isPlainType()) {
            List externalObjectModels = config.getExternalObjectModels();
            for (ExternalObjectModel model : externalObjectModels) {
                PJConverter converter = model.getPJConverter(targetClass);
                if (converter != null) {
                    return converter;
                }
            }

            if (NodeInfo.class.isAssignableFrom(targetClass)) {
                return Identity.INSTANCE;
            }
        }

        if (Collection.class.isAssignableFrom(targetClass)) {
            return ToCollection.INSTANCE;
        }
        if (targetClass.isArray()) {
            PJConverter itemConverter =
                    allocate(config, itemType, StaticProperty.EXACTLY_ONE, targetClass.getComponentType());
            return new ToArray(itemConverter);
        }
        if (!Cardinality.allowsMany(cardinality)) {
            if (itemType.isPlainType()) {
                if (itemType == ErrorType.getInstance()) {
                    // supplied value is (); we need to convert it to null; this converter does the job.
                    return StringValueToString.INSTANCE;
                } else if (th.isSubType(itemType, BuiltInAtomicType.STRING)) {
                    if (targetClass == Object.class || targetClass == String.class || targetClass == CharSequence.class) {
                        return StringValueToString.INSTANCE;
                    } else if (targetClass.isAssignableFrom(StringValue.class)) {
                        return Identity.INSTANCE;
                    } else if (targetClass == char.class || targetClass == Character.class) {
                        return StringValueToChar.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (itemType == BuiltInAtomicType.UNTYPED_ATOMIC) {
                    if (targetClass == Object.class || targetClass == String.class || targetClass == CharSequence.class) {
                        return StringValueToString.INSTANCE;
                    } else if (targetClass.isAssignableFrom(UntypedAtomicValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.BOOLEAN)) {
                    if (targetClass == Object.class || targetClass == Boolean.class || targetClass == boolean.class) {
                        return BooleanValueToBoolean.INSTANCE;
                    } else if (targetClass.isAssignableFrom(BooleanValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.INTEGER)) {
                    if (targetClass == Object.class || targetClass == BigInteger.class) {
                        return IntegerValueToBigInteger.INSTANCE;
                    } else if (targetClass == long.class || targetClass == Long.class) {
                        return IntegerValueToLong.INSTANCE;
                    } else if (targetClass == int.class || targetClass == Integer.class) {
                        return IntegerValueToInt.INSTANCE;
                    } else if (targetClass == short.class || targetClass == Short.class) {
                        return IntegerValueToShort.INSTANCE;
                    } else if (targetClass == byte.class || targetClass == Byte.class) {
                        return IntegerValueToByte.INSTANCE;
                    } else if (targetClass == char.class || targetClass == Character.class) {
                        return IntegerValueToChar.INSTANCE;
                    } else if (targetClass == double.class || targetClass == Double.class) {
                        return NumericValueToDouble.INSTANCE;
                    } else if (targetClass == float.class || targetClass == Float.class) {
                        return NumericValueToFloat.INSTANCE;
                    } else if (targetClass == BigDecimal.class) {
                        return NumericValueToBigDecimal.INSTANCE;
                    } else if (targetClass.isAssignableFrom(IntegerValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.DECIMAL)) {
                    if (targetClass == Object.class || targetClass == BigDecimal.class) {
                        return NumericValueToBigDecimal.INSTANCE;
                    } else if (targetClass == double.class || targetClass == Double.class) {
                        return NumericValueToDouble.INSTANCE;
                    } else if (targetClass == float.class || targetClass == Float.class) {
                        return NumericValueToFloat.INSTANCE;
                    } else if (targetClass.isAssignableFrom(DecimalValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.FLOAT)) {
                    if (targetClass == Object.class || targetClass == Float.class || targetClass == float.class) {
                        return NumericValueToFloat.INSTANCE;
                    } else if (targetClass == double.class || targetClass == Double.class) {
                        return NumericValueToDouble.INSTANCE;
                    } else if (targetClass.isAssignableFrom(FloatValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.DOUBLE)) {
                    if (targetClass == Object.class || targetClass == Double.class || targetClass == double.class) {
                        return NumericValueToDouble.INSTANCE;
                    } else if (targetClass.isAssignableFrom(DoubleValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        return Atomic.INSTANCE;
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.ANY_URI)) {
                    if (targetClass == Object.class || URI.class.isAssignableFrom(targetClass)) {
                        return AnyURIValueToURI.INSTANCE;
                    } else if (URL.class.isAssignableFrom(targetClass)) {
                        return AnyURIValueToURL.INSTANCE;
                    } else if (targetClass == String.class || targetClass == CharSequence.class) {
                        return StringValueToString.INSTANCE;
                    } else if (targetClass.isAssignableFrom(AnyURIValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.QNAME)) {
                    if (targetClass == Object.class || targetClass == javax.xml.namespace.QName.class) {
                        // Note JDK1.5 dependency
                        return QualifiedNameValueToQName.INSTANCE;
                    } else if (targetClass.isAssignableFrom(QNameValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.NOTATION)) {
                    if (targetClass == Object.class || targetClass == javax.xml.namespace.QName.class) {
                        // Note JDK1.5 dependency
                        return QualifiedNameValueToQName.INSTANCE;
                    } else if (targetClass.isAssignableFrom(NotationValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.DURATION)) {
                    if (targetClass.isAssignableFrom(DurationValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.DATE_TIME)) {
                    if (targetClass.isAssignableFrom(DateTimeValue.class)) {
                        return Identity.INSTANCE;
                    } else if (targetClass == java.util.Date.class) {
                        return CalendarValueToDate.INSTANCE;
                    } else if (targetClass == java.util.Calendar.class) {
                        return CalendarValueToCalendar.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.DATE)) {
                    if (targetClass.isAssignableFrom(DateValue.class)) {
                        return Identity.INSTANCE;
                    } else if (targetClass == java.util.Date.class) {
                        return CalendarValueToDate.INSTANCE;
                    } else if (targetClass == java.util.Calendar.class) {
                        return CalendarValueToCalendar.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.TIME)) {
                    if (targetClass.isAssignableFrom(TimeValue.class)) {
                        return Identity.INSTANCE;
                    } else if (targetClass == java.util.Date.class) {
                        return CalendarValueToDate.INSTANCE;
                    } else if (targetClass == java.util.Calendar.class) {
                        return CalendarValueToCalendar.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.G_YEAR)) {
                    if (targetClass.isAssignableFrom(GYearValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.G_YEAR_MONTH)) {
                    if (targetClass.isAssignableFrom(GYearMonthValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.G_MONTH)) {
                    if (targetClass.isAssignableFrom(GMonthValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.G_MONTH_DAY)) {
                    if (targetClass.isAssignableFrom(GMonthDayValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.G_DAY)) {
                    if (targetClass.isAssignableFrom(GDayValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.BASE64_BINARY)) {
                    if (targetClass.isAssignableFrom(Base64BinaryValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else if (th.isSubType(itemType, BuiltInAtomicType.HEX_BINARY)) {
                    if (targetClass.isAssignableFrom(HexBinaryValue.class)) {
                        return Identity.INSTANCE;
                    } else {
                        throw cannotConvert(itemType, targetClass, config);
                    }
                } else {
                    return Atomic.INSTANCE;
                }

            } else if (itemType instanceof ExternalObjectType) {
                    return UnwrapExternalObject.INSTANCE;

            } else if (itemType instanceof ErrorType) {
                return ToNull.INSTANCE;

            } else if (itemType instanceof NodeTest) {
                if (NodeInfo.class.isAssignableFrom(targetClass)) {
                    return Identity.INSTANCE;
                } else {
                    return General.INSTANCE;
                }

            } else {
                // ItemType is item()
                return General.INSTANCE;
            }
        } else {
            // Cardinality allows many (but target type is not a collection)
            return General.INSTANCE;
        }
    }

    private static XPathException cannotConvert(ItemType source, Class target, Configuration config) {
        return new XPathException("Cannot convert from " + source.toString() +
            " to " + target.getName());
    }

    /**
     * Static method to get a converter from an XPath sequence of nodes to the representation of a NodeList
     * in an external object model (this is really a special for DOM, which uses NodeList rather than general
     * purpose Java collection classes)
     * @param config the Saxon configuration
     * @param node an object representing a node in an external model
     * @return the Java object representing the external node
     */

    public static PJConverter allocateNodeListCreator(Configuration config, Object node) {
        List externalObjectModels = config.getExternalObjectModels();
        for (ExternalObjectModel model : externalObjectModels) {
            PJConverter converter = model.getNodeListCreator(node);
            if (converter != null) {
                return converter;
            }
        }
        return ToCollection.INSTANCE;
    }

    public static class ToSequenceIterator extends PJConverter {

        public static final ToSequenceIterator INSTANCE = new ToSequenceIterator();

        public Object convert(Sequence value, Class targetClass, XPathContext context) throws XPathException {
            return value.iterate();
        }

    }

    public static class ToNull extends PJConverter {

        public static final ToNull INSTANCE = new ToNull();

        public Object convert(Sequence value, Class targetClass, XPathContext context) throws XPathException {
            return null;
        }

    }

    public static class ToSequenceExtent extends PJConverter {

        public static final ToSequenceExtent INSTANCE = new ToSequenceExtent();

        public Object convert(Sequence value, Class targetClass, XPathContext context) throws XPathException {
            return SequenceExtent.makeSequenceExtent(value.iterate());
        }

    }

    /**
     * Converter for use when the target class is a collection class. Also used when the target
     * class is Object
     */

    public static class ToCollection extends PJConverter {

        public static final ToCollection INSTANCE = new ToCollection();

        public Object convert(Sequence value, Class targetClass, XPathContext context) throws XPathException {
            Collection list;
            if (targetClass.isAssignableFrom(ArrayList.class)) {
                list = new ArrayList(100);
            } else {
                try {
                    list = (Collection)targetClass.newInstance();
                } catch (InstantiationException e) {
                    XPathException de = new XPathException("Cannot instantiate collection class " + targetClass);
                    de.setXPathContext(context);
                    throw de;
                } catch (IllegalAccessException e) {
                    XPathException de = new XPathException("Cannot access collection class " + targetClass);
                    de.setXPathContext(context);
                    throw de;
                }
            }
            Configuration config = context.getConfiguration();
            SequenceIterator iter = value.iterate();
            while (true) {
                Item it = iter.next();
                if (it == null) {
                    return list;
                }
                if (it instanceof AtomicValue) {
                    PJConverter pj = allocate(
                            config, ((AtomicValue)it).getItemType(), StaticProperty.EXACTLY_ONE, Object.class);
                    list.add(pj.convert(it, Object.class, context));
                    //list.add(((AtomicValue)it).convertToJava(Object.class, context));
                } else if (it instanceof VirtualNode) {
                    list.add(((VirtualNode)it).getRealNode());
                } else {
                    list.add(it);
                }
            }
            //return Value.asValue(value).convertToJavaList(list, context);
        }

    }

    /**
     * Converter for use when the target class is an array
     */

    public static class ToArray extends PJConverter {

        private PJConverter itemConverter;

        public ToArray(PJConverter itemConverter) {
            this.itemConverter = itemConverter;
        }

        public Object convert(Sequence value, Class targetClass, XPathContext context) throws XPathException {
            Class componentClass = targetClass.getComponentType();
            List list = new ArrayList(20);
            SequenceIterator iter = value.iterate();
            while (true) {
                Item item = iter.next();
                if (item == null) break;
                Object obj = itemConverter.convert(item, componentClass, context);
                if (obj != null) {
                    list.add(obj);
                }
            }
            Object array = Array.newInstance(componentClass, list.size());
            for (int i=0; i