net.sf.saxon.value.AtomicValue Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.value;
import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.expr.StaticProperty;
import net.sf.saxon.expr.sort.AtomicMatchKey;
import net.sf.saxon.expr.sort.CodepointCollator;
import net.sf.saxon.expr.sort.SimpleTypeComparison;
import net.sf.saxon.expr.sort.XPathComparable;
import net.sf.saxon.functions.AccessorFn;
import net.sf.saxon.lib.StringCollator;
import net.sf.saxon.om.AtomicSequence;
import net.sf.saxon.om.Genre;
import net.sf.saxon.om.IdentityComparable;
import net.sf.saxon.om.Item;
import net.sf.saxon.str.UnicodeString;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.NoDynamicContextException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.SingleAtomicIterator;
import net.sf.saxon.tree.jiter.MonoIterator;
import net.sf.saxon.type.*;
import java.util.Iterator;
import java.util.Objects;
/**
* The AtomicValue class corresponds to the concept of an atomic value in the
* XPath 2.0 data model. Atomic values belong to one of the 19 primitive types
* defined in XML Schema; or they are of type xs:untypedAtomic; or they are
* "external objects", representing a Saxon extension to the XPath 2.0 type system.
*
* The AtomicValue class contains some methods that are suitable for applications
* to use, and many others that are designed for internal use by Saxon itself.
* These have not been fully classified. At present, therefore, none of the methods on this
* class should be considered to be part of the public Saxon API.
*
* The AtomicValue class and its subclasses are essentially immutable (since 12.x).
* There is an exception, in that {@link DecimalValue} caches the result of converting
* its value to a double internally.
*/
public abstract class AtomicValue
implements Item, AtomicSequence, ConversionResult, IdentityComparable {
protected final AtomicType typeLabel;
public AtomicValue(AtomicType typeLabel) {
Objects.requireNonNull(typeLabel);
this.typeLabel = typeLabel;
}
/**
* Atomize the item.
*
* @return the result of atomization
* @throws net.sf.saxon.trans.XPathException
* if atomization is not allowed for this kind of item
*/
@Override
public AtomicSequence atomize() throws XPathException {
return this;
}
/**
* To implement {@link net.sf.saxon.om.Sequence}, this method returns the item itself
*
* @return this item
*/
@Override
public final AtomicValue head() {
return this;
}
/**
* Get the length of the sequence
*
* @return the number of items in the sequence (always one)
*/
@Override
public int getLength() {
return 1;
}
/**
* Ask whether the value is of type xs:untypedAtomic
* @return true if the value is untyped atomic
*/
public boolean isUntypedAtomic() {
return typeLabel == BuiltInAtomicType.UNTYPED_ATOMIC;
}
/**
* Get an object value that implements the XPath equality comparison semantics for this value.
* A collation is supplied for comparing strings, and an implicit timezone
* for comparing date/time values that have no saved timezone. The returned object supports
* equality matching only, not ordering. An atomic value may return itself as its own
* {@code AtomicMatchKey} provided that its equality semantics are context-free.
*
*
* @param collator the collation to be used when comparing strings
* @param implicitTimezone the implicit timezone in the dynamic context, used when comparing
* dates/times with and without timezone
* @return an Object whose equals() and hashCode() methods implement the XPath comparison semantics
* with respect to this atomic value. If ordered is specified, the result will either be null if
* no ordering is defined, or will be a Comparable
* @throws NoDynamicContextException if the supplied implicit timezone is "NO_TIMEZONE" (meaning
* unknown), and the implicit timezone is actually required because the value in question is a date/time
* value with no timezone. This can cause a failure to evaluate expressions statically (because the implicit
* timezone is not known statically), and it will then be caught, meaning that the expression has to be
* evaluated dynamically.
*/
public abstract AtomicMatchKey getXPathMatchKey(StringCollator collator, int implicitTimezone)
throws NoDynamicContextException;
/**
* Get an object value that implements the XPath equality and ordering comparison semantics for this value.
* A collation is supplied for comparing strings, and an implicit timezone for comparing date/time values
* that have no saved timezone. An atomic value may return itself as the result, provided that its ordering
* rules are independent of the collation and timezone, and provided that it implements the XPathComparable
* interface: which means that its compareTo, equals, and hashCode methods must be compatible with the
* rules for XPath value comparisons.
*
* @param collator the collation to be used when comparing strings
* @param implicitTimezone the implicit timezone in the dynamic context, used when comparing
* dates/times with and without timezone
* @return an Object that implements the XPath value comparison semantics
* with respect to this atomic value. For an atomic type that is not ordered (according to XPath
* rules), return null.
* @throws NoDynamicContextException if the supplied implicit timezone is "NO_TIMEZONE" (meaning
* unknown), and the implicit timezone is actually required because the value in question is a date/time
* value with no timezone. This can cause a failure to evaluate expressions statically (because the implicit
* timezone is not known statically), and it will then be caught, meaning that the expression has to be
* evaluated dynamically.
*/
public abstract XPathComparable getXPathComparable(StringCollator collator, int implicitTimezone)
throws NoDynamicContextException;
/**
* Get a value whose equals() method follows the "same key" rules for comparing the keys of a map.
* @return a value with the property that the equals() and hashCode() methods follow the rules for comparing
* keys in maps.
*/
public AtomicMatchKey asMapKey() {
try {
return getXPathMatchKey(CodepointCollator.getInstance(), CalendarValue.NO_TIMEZONE);
} catch (NoDynamicContextException e) {
// Should not happen
throw new IllegalStateException("No implicit timezone available");
}
}
/**
* The equals() methods on atomic values is defined to follow the semantics of eq when applied
* to two atomic values. When the other operand is not an atomic value, the result is undefined
* (may be false, may be an exception). When the other operand is an atomic value that cannot be
* compared with this one, the method must throw a ClassCastException.
* The hashCode() method is consistent with equals().
*
* @param o the other value
* @return true (in a subclass) if the other operand is an atomic value and the two values are equal as defined
* by the XPath eq operator
*/
public boolean equals(Object o) {
throw new UnsupportedOperationException("equals() not implemented");
}
/**
* Returns a hash code value for the object.
*/
@Override
public int hashCode() {
throw new UnsupportedOperationException("hashCode() not implemented");
}
/**
* Determine whether two atomic values are identical, as determined by XML Schema rules. This is a stronger
* test than equality (even schema-equality); for example two dateTime values are not identical unless
* they are in the same timezone.
* Note that even this check ignores the type annotation of the value. The integer 3 and the short 3
* are considered identical, even though they are not fully interchangeable. "Identical" means the
* same point in the value space, regardless of type annotation.
* NaN is identical to itself.
*
* @param v the other value to be compared with this one
* @return true if the two values are identical, false otherwise.
*/
public boolean isIdentical(/*@NotNull*/ AtomicValue v) {
// default implementation
return SimpleTypeComparison.getInstance().equal(this, v);
}
/**
* Determine whether two IdentityComparable objects are identical. This is a stronger
* test than equality (even schema-equality); for example two dateTime values are not identical unless
* they are in the same timezone.
*
* @param other the value to be compared
* @return true if the two values are identical, false otherwise
*/
@Override
public boolean isIdentical(IdentityComparable other) {
return other instanceof AtomicValue && isIdentical((AtomicValue) other);
}
/**
* Get a hashCode that offers the guarantee that if A.isIdentical(B), then A.identityHashCode() == B.identityHashCode()
*
* @return a hashCode suitable for use when testing for identity.
*/
@Override
public int identityHashCode() {
// default implementation, which presumes that if two objects are identical then they are equal.
return hashCode();
}
/**
* Get the value of the item as a UnicodeString.
* @return the string value (the result of casting to string using the XPath casting rules)
*/
@Override
public UnicodeString getUnicodeStringValue() {
UnicodeString cs = getPrimitiveStringValue();
try {
return typeLabel.postprocess(cs);
} catch (XPathException err) {
// Ignore any XPath errors that occur during postprocessing
return cs;
}
}
/**
* Get the canonical lexical representation as defined in XML Schema. This is not always the same
* as the result of casting to a string according to the XPath rules.
*
* @return the canonical lexical representation if defined in XML Schema; otherwise, the result
* of casting to string according to the XPath 2.0 rules
*/
@Override
public UnicodeString getCanonicalLexicalRepresentation() {
return this.getUnicodeStringValue();
}
/**
* Get the n'th item in the sequence (starting from 0). This is defined for all
* Values, but its real benefits come for a sequence Value stored extensionally
* (or for a MemoClosure, once all the values have been read)
*
* @param n position of the required item, counting from zero.
* @return the n'th item in the sequence, where the first item in the sequence is
* numbered zero. If n is negative or >= the length of the sequence, returns null.
*/
/*@Nullable*/
@Override
public final AtomicValue itemAt(int n) {
return n == 0 ? head() : null;
}
/**
* Determine the data type of the value
*
* @return the type annotation of the atomic value
*/
/*@NotNull*/
public final AtomicType getItemType() {
return typeLabel;
}
/**
* Determine the primitive type of the value. This delivers the same answer as
* getItemType().getPrimitiveItemType(). The primitive types are
* the 19 primitive types of XML Schema, plus xs:integer, xs:dayTimeDuration and xs:yearMonthDuration,
* and xs:untypedAtomic. For external objects, the result is xs:anyAtomicType.
*
* @return the primitive type
*/
public abstract BuiltInAtomicType getPrimitiveType();
/**
* Determine the UType of the value. This delivers the same answer as
* getItemType().getUType()
* @return the primitive UType
*/
public final UType getUType() {
return getItemType().getUType();
}
/**
* Determine the static cardinality
*
* @return code identifying the cardinality
* @see net.sf.saxon.value.Cardinality
*/
public final int getCardinality() {
return StaticProperty.EXACTLY_ONE;
}
/**
* Create a copy of this atomic value, with a different type label
*
* @param typeLabel the type label of the new copy. The caller is responsible for checking that
* the value actually conforms to this type.
* @return the copied value
*/
public abstract AtomicValue copyAsSubType(AtomicType typeLabel);
/**
* Test whether the value is the special value NaN
*
* @return true if the value is float NaN or double NaN or precisionDecimal NaN; otherwise false
*/
public boolean isNaN() {
return false;
}
/**
* Convert the value to a string, using the serialization rules for the primitive type.
* This is the result of conversion to a string except that postprocessing defined by the
* saxon:preprocess facet is not (yet) applied.
*
* @return the value converted to a string according to the rules for the primitive type
*/
public abstract UnicodeString getPrimitiveStringValue();
/**
* Get the effective boolean value of the value
*
* @return true, unless the value is boolean false, numeric zero, or
* zero-length string
* @throws XPathException if effective boolean value is not defined for this type (the default behaviour)
*/
@Override
public boolean effectiveBooleanValue() throws XPathException {
XPathException err = new XPathException("Effective boolean value is not defined for an atomic value of type " +
Type.displayTypeName(this));
err.setIsTypeError(true);
err.setErrorCode("FORG0006");
throw err;
// unless otherwise specified in a subclass
}
/**
* Method to extract components of a value. Implemented by some subclasses,
* but defined at this level for convenience
*
* @param component identifies the required component, as a constant defined in class
* {@link net.sf.saxon.functions.AccessorFn}
* @return the value of the requested component of this value
* @throws net.sf.saxon.trans.XPathException
* if a dynamic error occurs
* @throws UnsupportedOperationException if applied to a value of a type that has no components
*/
public AtomicValue getComponent(AccessorFn.Component component) throws XPathException {
throw new UnsupportedOperationException("Data type does not support component extraction");
}
/**
* Check statically that the results of the expression are capable of constructing the content
* of a given schema type.
*
* @param parentType The schema type
* @param env the static context
* @param whole true if this atomic value accounts for the entire content of the containing node
* @throws net.sf.saxon.trans.XPathException
* if the expression doesn't match the required content type
*/
public void checkPermittedContents(/*@NotNull*/ SchemaType parentType, /*@NotNull*/ StaticContext env, boolean whole) throws XPathException {
if (whole) {
SimpleType stype = null;
if (parentType instanceof SimpleType) {
stype = (SimpleType) parentType;
} else if (parentType instanceof ComplexType && ((ComplexType) parentType).isSimpleContent()) {
stype = ((ComplexType) parentType).getSimpleContentType();
}
if (stype != null && !stype.isNamespaceSensitive()) {
// Can't validate namespace-sensitive content statically
ValidationFailure err = stype.validateContent(
this.getUnicodeStringValue(), null, env.getConfiguration().getConversionRules());
if (err != null) {
throw err.makeException();
}
return;
}
}
if (parentType instanceof ComplexType &&
!((ComplexType) parentType).isSimpleContent() &&
!((ComplexType) parentType).isMixedContent() &&
!Whitespace.isAllWhite(this.getUnicodeStringValue())) {
XPathException err = new XPathException("Complex type " + parentType.getDescription() +
" does not allow text content " +
Err.wrap(this.getUnicodeStringValue()));
err.setIsTypeError(true);
throw err;
}
}
/**
* Check that the value can be handled in SaxonJS
* @throws XPathException if it can't be handled in SaxonJS
*/
public void checkValidInJavascript() throws XPathException {
// default - no action
}
/**
* Calling this method on a ConversionResult returns the AtomicValue that results
* from the conversion if the conversion was successful, and throws a ValidationException
* explaining the conversion error otherwise.
* Use this method if you are calling a conversion method that returns a ConversionResult,
* and if you want to throw an exception if the conversion fails.
*
* @return the atomic value that results from the conversion if the conversion was successful
*/
/*@NotNull*/
@Override
public AtomicValue asAtomic() {
return this;
}
/**
* Get string value.
*/
public String toString() {
return getStringValue();
//throw new UnsupportedOperationException();
//return typeLabel + "(\"" + getStringValueCS() + "\")";
}
@Override
public String toShortString() {
return show();
}
/**
* Display the value for diagnostics. In general show() for an atomic value displays the value as it would be
* written in XPath: that is, as a literal if available, or as a call on a constructor function
* otherwise.
* @return a string representation of this value
*/
public String show() {
return typeLabel + "(\"" + this.getUnicodeStringValue() + "\")";
}
/**
* Get an iterator over all the items in the sequence
*
* @return an iterator over all the items
*/
@Override
public SingleAtomicIterator iterate() {
return (SingleAtomicIterator)SingleAtomicIterator.makeIterator(this);
}
/**
* Returns a Java iterator over the atomic sequence.
*
* @return an Iterator.
*/
@Override
public Iterator iterator() {
return new MonoIterator<>(this);
}
/**
* Get the genre of this item
*
* @return the genre: specifically, {@link Genre#ATOMIC};
*/
@Override
public final Genre getGenre() {
return Genre.ATOMIC;
}
}