net.sf.saxon.ma.map.RangeKey Maven / Gradle / Ivy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 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.ma.map;
import net.sf.saxon.expr.OperandRole;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.expr.sort.AtomicMatchKey;
import net.sf.saxon.expr.sort.CodepointMatchKey;
import net.sf.saxon.functions.Count;
import net.sf.saxon.om.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.AtomicIterator;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.iter.UnfailingIterator;
import net.sf.saxon.type.AtomicType;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.UType;
import net.sf.saxon.value.*;
import net.sf.saxon.value.StringValue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
/**
* This class implements an XPath map item as a view of an XSLT key index. It is possible to specify a minimum
* and maximum key value to be included in the map, and keys are returned in sorted order.
*
* At present range keys are only available for string-valued keys using the Unicode codepoint collating sequence.
*/
public class RangeKey implements MapItem {
private CodepointMatchKey min;
private CodepointMatchKey max;
private XPathContext context;
private TreeMap> index;
public RangeKey(String min, String max, XPathContext context, TreeMap> index) throws XPathException {
this.min = min == null ? null : new CodepointMatchKey(min);
this.max = max == null ? null : new CodepointMatchKey(max);
this.index = index;
this.context = context;
}
/**
* Ask whether this function item is an array
*
* @return false (it is not an array)
*/
public boolean isArray() {
return false;
}
/**
* Ask whether this function item is a map
*
* @return true (it is a map)
*/
public boolean isMap() {
return true;
}
/**
* Get the roles of the arguments, for the purposes of streaming
*
* @return an array of OperandRole objects, one for each argument
*/
public OperandRole[] getOperandRoles() {
return new OperandRole[]{OperandRole.SINGLE_ATOMIC};
}
/**
* Atomize the item.
*
* @return the result of atomization
* @throws net.sf.saxon.trans.XPathException
* if atomization is not allowed
* for this kind of item
*/
public AtomicSequence atomize() throws XPathException {
throw new XPathException("Maps cannot be atomized", "FOTY0013");
}
/**
* Get an entry from the Map
*
*
* @param key the value of the key
* @return the value associated with the given key, or null if the key is not present in the map
*/
public Sequence get(AtomicValue key) {
CodepointMatchKey k = new CodepointMatchKey(key.getStringValue());
if ((min == null || min.compareTo(k) <= 0) &&
(max == null || max.compareTo(k) >= 0)) {
List nodes = index.get(k);
if (nodes == null) {
return EmptySequence.getInstance();
} else {
return SequenceExtent.makeSequenceExtent(nodes);
}
}
return EmptySequence.getInstance();
}
/**
* Get the size of the map
*
* @return the number of keys/entries present in this map
*/
public int size() {
try {
return Count.count(keys());
} catch (XPathException err) {
return 0;
}
}
/**
* Ask whether the map is empty
*
* @return true if and only if the size of the map is zero
*/
public boolean isEmpty() {
return keys().next() == null;
}
/**
* Get the set of all key values in the map.
*
* @return a set containing all the key values present in the map.
*/
public AtomicIterator keys() {
return new RangeKeyIterator();
}
// /**
// * Ask whether the map includes calendar values with a timezone, calendar values without a timezone, or neither.
// * Maps are not allowed to mix keys that have a timezone and keys that do not. Here "calendar values" means any
// * of the types dateTime, date, time, gYear, gYearMonth, gMonth, gMonthDay, or gDay
// * @return the time zone status of the map contents: always {@link TimeZoneStatus#CONTAINS_NO_CALENDAR_VALUES}
// */
//
// public TimeZoneStatus getTimeZoneStatus() {
// return TimeZoneStatus.CONTAINS_NO_CALENDAR_VALUES;
// }
/**
* Get the set of all key-value pairs in the map
*
* @return an iterator over the key-value pairs
*/
public Iterator iterator() {
List list = new ArrayList();
AtomicIterator keys = keys();
AtomicValue key;
while ((key = keys.next()) != null) {
list.add(new KeyValuePair(key, get(key)));
}
return list.iterator();
}
/**
* Remove an entry from the map
*
* @param key the key of the entry to be removed
* @return a new map in which the requested entry has been removed; or this map
* unchanged if the specified key was not present
* @throws net.sf.saxon.trans.XPathException
* if a dynamic error occurs
*/
public MapItem remove(AtomicValue key) throws XPathException {
HashTrieMap copy = HashTrieMap.copy(this, context);
return copy.remove(key);
}
/**
* Get the lowest common item type of the keys in the map
*
* @return the most specific type to which all the keys belong. If the map is
* empty, return AnyAtomicType by convention
*/
public AtomicType getKeyType() {
return BuiltInAtomicType.STRING;
}
/**
* Get the lowest common item type of the keys in the map
*
* @return the most specific type to which all the keys belong. If the map is
* empty, return ErrorType.getInstance() (the type with no instances)
*/
public UType getKeyUType() {
return UType.STRING;
}
/**
* Get the lowest common sequence type of all the values in the map
*
* @return the most specific sequence type to which all the values belong. If the map
* is empty, return ANY_SEQUENCE by convention
*/
public SequenceType getValueType() {
return SequenceType.NODE_SEQUENCE;
}
/**
* Get the item type of the function item
*
* @return the function item's type
*/
public MapType getFunctionItemType() {
return new MapType(BuiltInAtomicType.STRING, SequenceType.NODE_SEQUENCE);
}
/**
* Get the name of the function, or null if it is anonymous
*
* @return the function name, or null for an anonymous inline function
*/
public StructuredQName getFunctionName() {
return null;
}
/**
* Get a description of this function for use in error messages. For named functions, the description
* is the function name (as a lexical QName). For others, it might be, for example, "inline function",
* or "partially-applied ends-with function".
*
* @return a description of the function for use in error messages
*/
public String getDescription() {
return "range key";
}
/**
* Get the arity of the function
*
* @return the number of arguments in the function signature
*/
public int getArity() {
return 1;
}
/**
* Invoke the function
*
* @param context the XPath dynamic evaluation context
* @param args the actual arguments to be supplied
* @return the result of invoking the function
* @throws net.sf.saxon.trans.XPathException
* if a dynamic error occurs within the function
*/
public Sequence call(XPathContext context, Sequence[] args) throws XPathException {
return get((AtomicValue) args[0].head());
}
/**
* Test whether this FunctionItem is deep-equal to another function item,
* under the rules of the deep-equal function
*
* @param other the other function item
* @param context the dynamic evaluation context
* @param comparer the object to perform the comparison
* @param flags options for how the comparison is performed
* @return true if the two function items are deep-equal
* @throws net.sf.saxon.trans.XPathException
* if the comparison cannot be performed
*/
public boolean deepEquals(Function other, XPathContext context, AtomicComparer comparer, int flags) throws XPathException {
if (other instanceof RangeKey) {
RangeKey rk = (RangeKey) other;
return min.equals(rk.min) && max.equals(rk.max) && index.equals(rk.index);
} else {
return false;
}
}
/**
* Get the n'th item in the value, counting from 0
*
* @param n the index of the required item, with 0 representing the first item in the sequence
* @return the n'th item if it exists, or null otherwise
*/
public Function itemAt(int n) {
return this;
}
/**
* Get a subsequence of the value
*
* @param start the index of the first item to be included in the result, counting from zero.
* A negative value is taken as zero. If the value is beyond the end of the sequence, an empty
* sequence is returned
* @param length the number of items to be included in the result. Specify Integer.MAX_VALUE to
* get the subsequence up to the end of the base sequence. If the value is negative, an empty sequence
* is returned. If the value goes off the end of the sequence, the result returns items up to the end
* of the sequence
* @return the required subsequence. If min is
*/
public GroundedValue subsequence(int start, int length) {
if (start <= 0 && length >= 1) {
return this;
} else {
return EmptySequence.getInstance();
}
}
/**
* Get the size of the value (the number of items)
*
* @return the number of items in the sequence
*/
public int getLength() {
return 1;
}
/**
* Get the effective boolean value of this sequence
*
* @return the effective boolean value
* @throws net.sf.saxon.trans.XPathException
* if the sequence has no effective boolean value (for example a sequence of two integers)
*/
public boolean effectiveBooleanValue() throws XPathException {
throw new XPathException("The effective boolean value of a map is not defined");
}
/**
* Reduce the sequence to its simplest form. If the value is an empty sequence, the result will be
* EmptySequence.getInstance(). If the value is a single atomic value, the result will be an instance
* of AtomicValue. If the value is a single item of any other kind, the result will be an instance
* of SingletonItem. Otherwise, the result will typically be unchanged.
*
* @return the simplified sequence
*/
public GroundedValue reduce() {
return this;
}
/**
* Get the value of the item as a string. For nodes, this is the string value of the
* node as defined in the XPath 2.0 data model, except that all nodes are treated as being
* untyped: it is not an error to get the string value of a node with a complex type.
* For atomic values, the method returns the result of casting the atomic value to a string.
*
* If the calling code can handle any CharSequence, the method {@link #getStringValueCS} should
* be used. If the caller requires a string, this method is preferred.
*
* @return the string value of the item
* @throws UnsupportedOperationException if the item is a function item (an unchecked exception
* is used here to avoid introducing exception handling to a large number of paths where it is not
* needed)
* @see #getStringValueCS
* @since 8.4
*/
public String getStringValue() {
throw new UnsupportedOperationException("Cannot get the string value of a map");
}
/**
* Get the string value of the item as a CharSequence. This is in some cases more efficient than
* the version of the method that returns a String. The method satisfies the rule that
* X.getStringValueCS().toString()
returns a string that is equal to
* X.getStringValue()
.
*
* Note that two CharSequence values of different types should not be compared using equals(), and
* for the same reason they should not be used as a key in a hash table.
*
* If the calling code can handle any CharSequence, this method should
* be used. If the caller requires a string, the {@link #getStringValue} method is preferred.
*
* @return the string value of the item
* @throws UnsupportedOperationException if the item is a function item (an unchecked exception
* is used here to avoid introducing exception handling to a large number of paths where it is not
* needed)
* @see #getStringValue
* @since 8.4
*/
public CharSequence getStringValueCS() {
return getStringValue();
}
/**
* Get the first item in the sequence.
*
* @return the first item in the sequence if there is one, or null if the sequence
* is empty
*/
public Function head() {
return this;
}
/**
* Get an iterator over all the items in the sequence
*
* @return an iterator over all the items
*/
public UnfailingIterator iterate() {
return SingletonIterator.makeIterator(this);
}
/**
* Output information about this function item to the diagnostic explain() output
*/
public void export(ExpressionPresenter out) throws XPathException {
out.startElement("range-key-map");
out.emitAttribute("size", size() + "");
out.endElement();
}
public boolean isTrustedResultType() {
return false;
}
private class RangeKeyIterator implements AtomicIterator {
int pos = 0;
CodepointMatchKey curr = null;
CodepointMatchKey top;
public RangeKeyIterator() {
top = (CodepointMatchKey) (max == null ? index.lastKey() : index.floorKey(max));
}
/**
* Get the next item in the sequence.
*
* @return the next Item. If there are no more nodes, return null.
*/
public StringValue next() {
if (pos <= 0) {
if (pos < 0) {
return null;
}
if (min == null) {
curr = (CodepointMatchKey) index.firstKey();
} else {
curr = (CodepointMatchKey) index.ceilingKey(min);
if(curr != null && max != null && curr.compareTo(max) > 0) {
curr = null;
}
}
} else if (curr.equals(top)) {
curr = null;
} else {
curr = (CodepointMatchKey) index.higherKey(curr);
}
if (curr == null) {
pos = -1;
return null;
} else {
pos++;
return new StringValue(curr.toString());
}
}
/**
* Get another iterator over the same sequence of items, positioned at the
* start of the sequence. It must be possible to call this method at any time, whether
* none, some, or all of the items in the original iterator have been read. The method
* is non-destructive: it does not change the state of the original iterator.
*
* @return a new iterator over the same sequence
*/
public AtomicIterator getAnother() {
return new RangeKeyIterator();
}
/**
* Close the iterator. This indicates to the supplier of the data that the client
* does not require any more items to be delivered by the iterator. This may enable the
* supplier to release resources. After calling close(), no further calls on the
* iterator should be made; if further calls are made, the effect of such calls is undefined.
*
* (Currently, closing an iterator is important only when the data is being "pushed" in
* another thread. Closing the iterator terminates that thread and means that it needs to do
* no additional work. Indeed, failing to close the iterator may cause the push thread to hang
* waiting for the buffer to be emptied.)
*
* @since 9.1
*/
public void close() {
//
}
/**
* Get properties of this iterator, as a bit-significant integer.
*
* @return the properties of this iterator. This will be some combination of
* properties such as {@link #GROUNDED}, {@link #LAST_POSITION_FINDER},
* and {@link #LOOKAHEAD}. It is always
* acceptable to return the value zero, indicating that there are no known special properties.
* It is acceptable for the properties of the iterator to change depending on its state.
* @since 8.6
*/
public int getProperties() {
return 0;
}
}
}
// Copyright (c) 2012 Saxonica Limited. All rights reserved.
© 2015 - 2025 Weber Informatics LLC | Privacy Policy