/* * eXist Open Source Native XML Database * Copyright (C) 2001-06, 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.dom.DocumentSet; import org.exist.dom.EmptyNodeSet; import org.exist.dom.NodeProxy; import org.exist.dom.StoredNode; import org.exist.numbering.NodeId; import org.exist.xquery.Cardinality; import org.exist.xquery.XPathException; 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; protected boolean hasOne; protected AbstractSequence() { isEmpty = true; hasOne = false; } public abstract int getItemType(); public abstract SequenceIterator iterate() throws XPathException; public abstract SequenceIterator unorderedIterator() throws XPathException; public abstract int getItemCount(); 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 { Item first = itemAt(0); if(Type.subTypeOf(first.getType(), Type.ATOMIC)) return ((AtomicValue)first).convertTo(requiredType); else //TODO : clean atomization return new StringValue(first.getStringValue()).convertTo(requiredType); } public abstract boolean isEmpty(); public abstract boolean hasOne(); public boolean hasMany() { return !isEmpty() && !hasOne(); } public String getStringValue() throws XPathException { if(isEmpty()) return ""; Item first = iterate().nextItem(); return first.getStringValue(); } public String toString() { try { StringBuilder buf = new StringBuilder(); buf.append("("); boolean gotOne = false; for(SequenceIterator i = iterate(); i.hasNext(); ) { if (gotOne) buf.append(", "); buf.append(i.nextItem()); gotOne = true; } buf.append(")"); return buf.toString(); } catch (XPathException e) { return "toString() fails: " + e.getMessage(); } } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#add(org.exist.xquery.value.Item) */ public abstract void add(Item item) throws XPathException; public void addAll(Sequence other) throws XPathException { for(SequenceIterator i = other.iterate(); i.hasNext(); ) add(i.nextItem()); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#itemAt(int) */ public abstract Item itemAt(int pos); /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#getDocumentSet() */ public DocumentSet getDocumentSet() { return DocumentSet.EMPTY_DOCUMENT_SET; } public Iterator getCollectionIterator() { return EmptyNodeSet.EMPTY_ITERATOR; } public void nodeMoved(NodeId oldNodeId, StoredNode newNode) { } /** 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; 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( "error 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(); /* // If its operand is a singleton value of type xs:string, xs:anyURI, xs:untypedAtomic, //or a type derived from one of these, fn:boolean returns false if the operand value has zero length; otherwise it returns true. if(first instanceof StringValue) return ((StringValue)first).effectiveBooleanValue(); else if(first instanceof AnyURIValue) return ((AnyURIValue)first).effectiveBooleanValue(); else if(first instanceof UntypedAtomicValue) return ((UntypedAtomicValue)first).effectiveBooleanValue(); //If its operand is a singleton value of type xs:boolean or derived from xs:boolean, //fn:boolean returns the value of its operand unchanged. else if(first instanceof BooleanValue) return ((BooleanValue)first).getValue(); //If its operand is a singleton value of any numeric type or derived from a numeric type, //fn:boolean returns false if the operand value is NaN or is numerically equal to zero; //otherwise it returns true. else if(first instanceof NumericValue) return ((NumericValue)first).effectiveBooleanValue(); else { if (OLD_EXIST_VERSION_COMPATIBILITY) return true; // In all other cases, fn:boolean raises a type error [err:FORG0006]. throw new XPathException( "error FORG0006: effectiveBooleanValue: sequence of length 1, " + "but not castable to a number or Boolean"); } */ } /* (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) */ public Object toJavaObject(Class target) throws XPathException { if(Sequence.class.isAssignableFrom(target)) { return this; } else if(target.isArray()) { Class componentType = target.getComponentType(); // assume single-dimensional, then double-check that instance really matches desired type Object array = Array.newInstance(componentType, getItemCount()); if (!target.isInstance(array)) return null; int index = 0; for(SequenceIterator i = iterate(); i.hasNext(); index++) { Item item = i.nextItem(); Object obj = item.toJavaObject(componentType); Array.set(array, index, obj); } return array; } else if(target.isAssignableFrom(List.class)) { List l = new ArrayList(getItemCount()); for(SequenceIterator i = iterate(); i.hasNext(); ) { l.add(i.nextItem()); } return l; } if(!isEmpty()) return itemAt(0).toJavaObject(target); return null; } public void clearContext(int contextId) throws XPathException { Item next; for (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 (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; } }