package org.exist.fluent;
import java.util.Date;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import org.exist.dom.NodeProxy;
import org.exist.storage.DBBroker;
import org.exist.storage.serializers.Serializer;
import org.exist.xquery.Constants;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.*;
import org.xml.sax.SAXException;
/**
* An XML item in the database. While most often used to represent XML
* elements, it can also stand in for any DOM node or an atomic value. However, it
* is not used to represent entire XML documents (see {@link org.exist.fluent.XMLDocument}).
* Not all operations are valid in all cases.
*
* @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
* @version $Revision: 1.13 $ ($Date: 2006/04/14 04:12:04 $)
*/
public class Item extends Resource {
protected final org.exist.xquery.value.Item item;
Item() {
super(null, null);
this.item = null;
}
Item(org.exist.xquery.value.Item item, NamespaceMap namespaceBindings, Database db) {
super(namespaceBindings, db);
// the item should've been tracked (Database.trackNode) before getting here!
this.item = item;
}
/**
* Return this item cast as a node.
*
* @return this item cast as a node
* @throws DatabaseException if this item is not a node
*/
public Node node() {
throw new DatabaseException("this item is not a node: " + this);
}
@Override public boolean equals(Object o) {
if (!(o instanceof Item)) return false;
Item that = (Item) o;
if (this.item == that.item) return true;
if (this.item instanceof AtomicValue && that.item instanceof AtomicValue) {
AtomicValue thisValue = (AtomicValue) this.item, thatValue = (AtomicValue) that.item;
try {
return
thisValue.getType() == thatValue.getType()
&& thisValue.compareTo(null, Constants.EQ, thatValue);
} catch (XPathException e) {
// fall through
}
}
return false;
}
/**
* The hash code computation can be expensive, and the hash codes may not be very well distributed.
* You probably shouldn't use items in situations where they might get hashed.
*/
@Override public int hashCode() {
if (item instanceof AtomicValue) {
AtomicValue value = (AtomicValue) item;
try {
return value.getType() ^ value.getStringValue().hashCode();
} catch (XPathException e) {
return value.getType();
}
} else {
return item.hashCode();
}
}
/**
* Return the type of this item, e.g. element() or xs:string.
*
* @return the type of this item
*/
public String type() {
return Type.getTypeName(item.getType());
}
/**
* Return whether this item really exists. Examples of items that don't exist even though they have an object
* representing them include virtual placeholders returned for an optional query that didn't select an item, and
* items that were deleted from the database after being selected.
*
* @return <code>true</code> if the item exists, <code>false</code> otherwise
*/
public boolean extant() {
return true;
}
@Override Sequence convertToSequence() {
return item.toSequence();
}
/**
* Return a singleton item list consisting of this item.
*
* @return an item list that contains only this item
*/
public ItemList toItemList() {
return new ItemList(convertToSequence(), namespaceBindings.extend(), db);
}
/**
* Atomize this item and return the result. This is useful if you don't know if you're
* holding a node or an atomic item, and want to ensure it's an atomic value without
* potentially losing its type by converting it to a string.
*
* @return the atomized value of this item; may be this item itself if it's already atomic
*/
public Item toAtomicItem() {
try {
org.exist.xquery.value.Item atomizedItem = item.atomize();
return atomizedItem == item ? this : new Item(atomizedItem, namespaceBindings.extend(), db);
} catch (XPathException e) {
throw new DatabaseException("unable to atomize item", e);
}
}
/**
* @return the string value of this item if atomic, or the concatenation of its text content if a node
*/
public String value() {
try {
return item.getStringValue();
} catch (XPathException e) {
throw new DatabaseException(e);
}
}
/**
* @param defaultValue the default value to return if the item is null (unbound)
* @return the string value of this item if atomic, or the concatenation of its text content if a node,
* or the given default value if this is a null item
*/
public String valueWithDefault(String defaultValue) {
return value();
}
/**
* Return the converted boolean value following XQuery / XPath conversion rules.
* For numeric values, return false iff the value is 0. For strings, return true if
* the value is 'true' or '1' and false if the value is 'false' or '0', fail otherwise. For
* nodes, return the conversion of the effective string value.
*
* @return the boolean value of the item
* @throws DatabaseException if the conversion failed
*/
public boolean booleanValue() {
try {
return ((Boolean) item.toJavaObject(Boolean.class)).booleanValue();
} catch (XPathException e) {
throw new DatabaseException(e);
}
}
/**
* Return the int value of this item. For numeric atomic values, truncate to an int;
* for other values, request a conversion of the effective string value (which may fail).
* If the value is out of range for ints, return the smallest or largest int, as appropriate.
* If you think overflow may be a problem, check for these values.
*
* @return the int value of this item
* @throws DatabaseException if the conversion failed
*/
public int intValue() {
try {
return ((Integer) item.toJavaObject(Integer.class)).intValue();
} catch (XPathException e) {
throw new DatabaseException(e);
}
}
/**
* Return the long value of this item. For numeric atomic values, truncate to a long;
* for other values, request a conversion of the effective string value (which may fail).
* If the value is out of range for longs, return the smallest or largest long, as appropriate.
* If you think overflow may be a problem, check for these values.
*
* @return the long value of this item
* @throws DatabaseException if the conversion failed
*/
public long longValue() {
try {
return ((Long) item.toJavaObject(Long.class)).longValue();
} catch (XPathException e) {
throw new DatabaseException(e);
}
}
/**
* Return the double value of this item. For numeric atomic values, truncate to a double;
* for other values, request a conversion of the effective string value (which may fail).
* If the value is out of range for doubles, return positive or negative infinity, as appropriate.
* If you think overflow may be a problem, check for these values.
*
* @return the double value of this item
* @throws DatabaseException if the conversion failed
*/
public double doubleValue() {
try {
return ((Double) item.toJavaObject(Double.class)).doubleValue();
} catch (XPathException e) {
throw new DatabaseException(e);
}
}
/**
* Return the duration value of this item by parsing its string representation as a duration.
*
* @return the duration value of this item
* @throws DatabaseException if the conversion failed
*/
public Duration durationValue() {
try {
return DataUtils.datatypeFactory().newDuration(value());
} catch (IllegalArgumentException e) {
throw new DatabaseException(e);
}
}
/**
* Return the XML date/time value of this item by parsing its string representation.
*
* @return the XML date/time value of this item
* @throws DatabaseException if the conversion failed
*/
public XMLGregorianCalendar dateTimeValue() {
try {
return DataUtils.datatypeFactory().newXMLGregorianCalendar(value());
} catch (IllegalArgumentException e) {
throw new DatabaseException(e);
}
}
/**
* Return the <code>java.util.Date</code> value of this item by parsing its string
* representation as an XML date/time value, then converting to a Java date.
*
* @return the Java time instant value of this item
* @throws DatabaseException if the conversion failed
*/
public Date instantValue() {
return DataUtils.toDate(dateTimeValue());
}
/**
* Return the comparable value of this item, if available. The resulting object will
* directly compare the XQuery value without converting to a string first, unless the
* item is untyped and atomic, in which case it will be cast to a string. Items of
* different types will compare in an arbitrary but stable order. Nodes are never
* comparable.
*
* @return the comparable value of this item
* @throws DatabaseException if this item is not comparable
*/
@SuppressWarnings("unchecked")
public Comparable<Object> comparableValue() {
try {
return item.getType() == Type.UNTYPED_ATOMIC ? (Comparable) item.convertTo(Type.STRING) : (Comparable) item;
} catch (XPathException e) {
throw new DatabaseException("unable to convert to comparable value", e);
}
}
/**
* Return the string representation of this item. If the item is atomic, return its string
* value. If it is a node, serialize it to a string.
*
* @return the string representation of this item
*/
@Override
public String toString() {
if (item instanceof AtomicValue) {
return value();
}
assert item instanceof NodeValue;
DBBroker broker = null;
try {
broker = db.acquireBroker();
Serializer serializer = broker.getSerializer();
if (item instanceof NodeProxy) {
NodeProxy proxy = (NodeProxy) item;
if (proxy.isDocument()) {
return serializer.serialize(proxy.getDocument());
}
}
return serializer.serialize((NodeValue) item);
} catch (SAXException e) {
throw new DatabaseException(e);
} finally {
db.releaseBroker(broker);
}
}
/**
* A null item, used as a placeholder where an actual <code>null</code> would be inappropriate.
* REMEMBER to duplicate all these methods in Node.NULL as well!
*/
static final Item NULL = new Item() {
@Override public boolean booleanValue() {return false;}
@Override public int intValue() {return 0;}
@Override public long longValue() {return 0L;}
@Override public double doubleValue() {return 0.0;}
@Override public Duration durationValue() {return null;}
@Override public XMLGregorianCalendar dateTimeValue() {return null;}
@Override public Date instantValue() {return null;}
@Override public Node node() {return Node.NULL;}
@Override public boolean extant() {return false;}
@Override public QueryService query() {return QueryService.NULL;}
@Override public String value() {return null;}
@Override public String valueWithDefault(String defaultValue) {return defaultValue;}
@Override public String toString() {return "NULL item";}
@Override Sequence convertToSequence() {return Sequence.EMPTY_SEQUENCE;}
};
}