net.sf.saxon.expr.PJConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 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.ma.map.MapItem;
import net.sf.saxon.ma.map.MapType;
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 java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
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.time.*;
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 {
private static final 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);
jpmap.put(FunctionItem.class, SequenceType.OPTIONAL_FUNCTION_ITEM);
jpmap.put(MapItem.class, MapType.OPTIONAL_MAP_ITEM);
jpmap.put(NodeInfo.class, SequenceType.OPTIONAL_NODE);
jpmap.put(TreeInfo.class, SequenceType.OPTIONAL_DOCUMENT_NODE);
}
/**
* 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 getEquivalentSequenceType(Class javaClass) {
if (javaClass.isArray()) {
Class memberClass = javaClass.getComponentType();
if (memberClass == byte.class) {
// special-case byte[] which maps to xs:unsignedByte* - see bugs 3525, 3818
return SequenceType.makeSequenceType(BuiltInAtomicType.UNSIGNED_BYTE, StaticProperty.ALLOWS_ZERO_OR_MORE);
} else {
SequenceType memberType = getEquivalentSequenceType(memberClass);
if (memberType != null) {
return SequenceType.makeSequenceType(memberType.getPrimaryType(), StaticProperty.ALLOWS_ZERO_OR_MORE);
}
}
}
return jpmap.get(javaClass);
}
/**
* Get the nearest XPath equivalent to a Java parameterized type. The only parameterized
* types we recognize are those like Sequence<StringValue> which correspond
* exactly to an XPath SequenceType.
*
* @param javaType a parameterized Java class
* @return the nearest equivalent XPath SequenceType
*/
public static SequenceType getParameterizedSequenceType(java.lang.reflect.Type javaType) {
if (javaType instanceof ParameterizedType) {
ParameterizedType aType = (ParameterizedType) javaType;
java.lang.reflect.Type[] parameterArgTypes = aType.getActualTypeArguments();
if (parameterArgTypes.length == 1 &&
parameterArgTypes[0] instanceof Class &&
Item.class.isAssignableFrom((Class)parameterArgTypes[0])) {
SequenceType memberType = getEquivalentSequenceType((Class)parameterArgTypes[0]);
ItemType itemType = memberType == null ? null : memberType.getPrimaryType();
java.lang.reflect.Type collectionType = aType.getRawType();
int cardinality = -1;
if (collectionType.equals(ZeroOrOne.class)) {
cardinality = StaticProperty.ALLOWS_ZERO_OR_ONE;
} else if (collectionType.equals(One.class)) {
cardinality = StaticProperty.ALLOWS_ONE;
} else if (collectionType.equals(OneOrMore.class)) {
cardinality = StaticProperty.ALLOWS_ONE_OR_MORE;
} else if (collectionType.equals(ZeroOrMore.class)) {
cardinality = StaticProperty.ALLOWS_ZERO_OR_MORE;
}
if (itemType != null && cardinality != -1) {
return SequenceType.makeSequenceType(itemType, cardinality);
}
}
}
return null;
}
/**
* 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 == One.class) {
return ToOne.INSTANCE;
}
if (targetClass == ZeroOrOne.class) {
return ToZeroOrOne.INSTANCE;
}
if (targetClass == OneOrMore.class) {
return ToOneOrMore.INSTANCE;
}
if (targetClass == ZeroOrMore.class) {
return ToZeroOrMore.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 StringItemToString.INSTANCE;
} else if (th.isSubType(itemType, BuiltInAtomicType.STRING)) {
if (targetClass == Object.class || targetClass == String.class || targetClass == CharSequence.class) {
return StringItemToString.INSTANCE;
} else if (targetClass.isAssignableFrom(StringValue.class)) {
return Identity.INSTANCE;
} else if (targetClass == char.class || targetClass == Character.class) {
return StringItemToChar.INSTANCE;
} else {
throw cannotConvert(itemType, targetClass, config);
}
} else if (itemType == BuiltInAtomicType.UNTYPED_ATOMIC) {
if (targetClass == Object.class || targetClass == String.class || targetClass == CharSequence.class) {
return StringItemToString.INSTANCE;
} else if (targetClass.isAssignableFrom(StringValue.class)) {
return Identity.INSTANCE;
} else {
try {
final Constructor constructor = targetClass.getConstructor(String.class);
return new PJConverter() {
@Override
public Object convert(Sequence value, Class> targetClass, XPathContext context) throws XPathException {
try {
return constructor.newInstance(value.head().getStringValue());
} catch (InstantiationException | IllegalAccessException e) {
throw new XPathException(e);
} catch (InvocationTargetException e) {
throw new XPathException("Cannot convert untypedAtomic to " + targetClass.getName() +
": " + e.getMessage(), "FORG0001");
}
}
};
} catch (NoSuchMethodException e) {
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(BigDecimalValue.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 StringItemToString.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) {
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) {
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 if (targetClass == Instant.class) {
return CalendarValueToInstant.INSTANCE;
} else if (targetClass == ZonedDateTime.class) {
return CalendarValueToZonedDateTime.INSTANCE;
} else if (targetClass == LocalDateTime.class) {
return CalendarValueToLocalDateTime.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 if (targetClass == Instant.class) {
return CalendarValueToInstant.INSTANCE;
} else if (targetClass == ZonedDateTime.class) {
return CalendarValueToZonedDateTime.INSTANCE;
} else if (targetClass == OffsetDateTime.class) {
return CalendarValueToOffsetDateTime.INSTANCE;
} else if (targetClass == LocalDateTime.class) {
return CalendarValueToLocalDateTime.INSTANCE;
} else if (targetClass == LocalDate.class) {
return DateValueToLocalDate.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 if (targetClass == Instant.class) {
return CalendarValueToInstant.INSTANCE;
} else if (targetClass == ZonedDateTime.class) {
return CalendarValueToZonedDateTime.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 JavaExternalObjectType) {
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 +
" 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();
@Override
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();
@Override
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();
@Override
public Object convert(Sequence value, Class> targetClass, XPathContext context) throws XPathException {
return value.materialize();
}
}
/**
* 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();
@Override
public Object convert(Sequence value, Class> targetClass, XPathContext context) throws XPathException {
Collection