/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2014, Wolfgang M. Meier (meier@ifs.tu-darmstadt.de)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Id$
*/
package org.exist.xquery.value;
import org.exist.collections.Collection;
import org.exist.dom.persistent.DocumentSet;
import org.exist.dom.persistent.EmptyNodeSet;
import org.exist.dom.persistent.NodeHandle;
import org.exist.dom.persistent.NodeProxy;
import org.exist.numbering.NodeId;
import org.exist.xquery.Cardinality;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* An abstract implementation of {@link org.exist.xquery.value.Sequence} with
* default implementations for some methods.
*/
public abstract class AbstractSequence implements Sequence {
/**
* To retain compatibility with eXist versions before september 20th 2005 ,
* for conversion to boolean;
*
* @see http://cvs.sourceforge.net/viewcvs.py/exist/eXist-1.0/src/org/exist/xquery/value/AbstractSequence.java?r1=1.11&r2=1.12
*/
private static final boolean OLD_EXIST_VERSION_COMPATIBILITY = false;
protected boolean isEmpty = true;
protected boolean hasOne = false;
public int getCardinality() {
if (isEmpty()) {
return Cardinality.EMPTY;
}
if (hasOne()) {
return Cardinality.EXACTLY_ONE;
}
if (hasMany()) {
return Cardinality.ONE_OR_MORE;
}
throw new IllegalArgumentException("Illegal argument");
}
public AtomicValue convertTo(int requiredType) throws XPathException {
final Item first = itemAt(0);
if (Type.subTypeOf(first.getType(), Type.ATOMIC)) {
return first.convertTo(requiredType);
} else
//TODO : clean atomization
{
return new StringValue(first.getStringValue()).convertTo(requiredType);
}
}
public boolean hasMany() {
return !isEmpty() && !hasOne();
}
@Override
public Sequence tail() throws XPathException {
final ValueSequence tmp = new ValueSequence(getItemCount() - 1);
Item item;
final SequenceIterator iterator = iterate();
iterator.nextItem();
while (iterator.hasNext()) {
item = iterator.nextItem();
tmp.add(item);
}
return tmp;
}
public String getStringValue() throws XPathException {
if (isEmpty()) {
return "";
}
final Item first = iterate().nextItem();
return first.getStringValue();
}
public String toString() {
try {
final StringBuilder buf = new StringBuilder();
buf.append("(");
boolean gotOne = false;
for (final SequenceIterator i = iterate(); i.hasNext(); ) {
if (gotOne) {
buf.append(", ");
}
buf.append(i.nextItem());
gotOne = true;
}
buf.append(")");
return buf.toString();
} catch (final XPathException e) {
return "toString() fails: " + e.getMessage();
}
}
public void addAll(Sequence other) throws XPathException {
for (final SequenceIterator i = other.iterate(); i.hasNext(); )
add(i.nextItem());
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Sequence#getDocumentSet()
*/
public DocumentSet getDocumentSet() {
return DocumentSet.EMPTY_DOCUMENT_SET;
}
public Iterator<Collection> getCollectionIterator() {
return EmptyNodeSet.EMPTY_COLLECTION_ITERATOR;
}
@Override
public void nodeMoved(NodeId oldNodeId, NodeHandle newNode) {
//Nothing to do
}
/**
* See
* <a <href="http://www.w3.org/TR/xquery/#id-ebv">2.4.3 Effective Boolean Value</a>
*
* @see org.exist.xquery.value.Sequence#effectiveBooleanValue()
*/
public boolean effectiveBooleanValue() throws XPathException {
if (isEmpty()) {
return false;
}
final Item first = itemAt(0);
//If its operand is a sequence whose first item is a node, fn:boolean returns true.
if (Type.subTypeOf(first.getType(), Type.NODE)) {
return true;
}
if (hasMany()) {
if (OLD_EXIST_VERSION_COMPATIBILITY) {
return true;
} else {
throw new XPathException(
"err:FORG0006: effectiveBooleanValue: first item of '" +
(toString().length() < 20 ? toString() : toString().substring(0, 20) + "...") +
"' is not a node, and sequence length > 1");
}
}
//From now, we'll work with singletons...
//Not sure about this one : does it mean than any singleton, including false() and 0 will return true ?
if (OLD_EXIST_VERSION_COMPATIBILITY) {
return true;
} else {
return ((AtomicValue) first).effectiveBooleanValue();
}
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Sequence#conversionPreference(java.lang.Class)
*/
public int conversionPreference(Class<?> javaClass) {
if (javaClass.isAssignableFrom(Sequence.class)) {
return 0;
} else if (javaClass.isAssignableFrom(List.class) || javaClass.isArray()) {
return 1;
} else if (javaClass == Object.class) {
return 20;
}
if (!isEmpty()) {
return itemAt(0).conversionPreference(javaClass);
}
return Integer.MAX_VALUE;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Sequence#toJavaObject(java.lang.Class)
*/
@Override
public <T> T toJavaObject(final Class<T> target) throws XPathException {
if (Sequence.class.isAssignableFrom(target)) {
return (T) this;
} else if (target.isArray()) {
final Class<?> componentType = target.getComponentType();
// assume single-dimensional, then double-check that instance really matches desired type
final Object array = Array.newInstance(componentType, getItemCount());
if (!target.isInstance(array)) {
return null;
}
int index = 0;
for (final SequenceIterator i = iterate(); i.hasNext(); index++) {
final Item item = i.nextItem();
final Object obj = item.toJavaObject(componentType);
Array.set(array, index, obj);
}
return (T) array;
} else if (target.isAssignableFrom(List.class)) {
final List<Item> l = new ArrayList<>(getItemCount());
for (final SequenceIterator i = iterate(); i.hasNext(); ) {
l.add(i.nextItem());
}
return (T) l;
}
if (!isEmpty()) {
return itemAt(0).toJavaObject(target);
}
return null;
}
public void clearContext(int contextId) throws XPathException {
Item next;
for (final SequenceIterator i = unorderedIterator(); i.hasNext(); ) {
next = i.nextItem();
if (next instanceof NodeProxy) {
((NodeProxy) next).clearContext(contextId);
}
}
}
public void setSelfAsContext(int contextId) throws XPathException {
Item next;
NodeValue node;
for (final SequenceIterator i = unorderedIterator(); i.hasNext(); ) {
next = i.nextItem();
if (Type.subTypeOf(next.getType(), Type.NODE)) {
node = (NodeValue) next;
node.addContextNode(contextId, node);
}
}
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Sequence#isCached()
*/
public boolean isCached() {
// always return false by default
return false;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Sequence#setIsCached(boolean)
*/
public void setIsCached(boolean cached) {
// ignore by default
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Sequence#isPersistentSet()
*/
public boolean isPersistentSet() {
// always return false by default
return false;
}
public boolean isCacheable() {
return false;
}
public int getState() {
return 0;
}
public boolean hasChanged(int previousState) {
return true;
}
@Override
public void destroy(XQueryContext context, Sequence contextSequence) {
// do nothing by default
}
}