/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 The eXist Project * http://exist-db.org * http://exist.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.xquery.value; import org.exist.dom.ExtArrayNodeSet; import org.exist.dom.NodeProxy; import org.exist.dom.NodeSet; import org.exist.xquery.GroupSpec; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.util.ExpressionDumper; /** * A sequence that containts items of one group specified by the group specs of * an "group by" clause. Used by * {@link org.exist.xquery.value.GroupedValueSequenceTable}. * * This class is based on {@link org.exist.xquery.value.OrderedValueSequence}. * * WARNING : don't use except for group by clause * * @author Boris Verhaegen */ public class GroupedValueSequence extends AbstractSequence { private Entry[] items = null; private int count = 0; //grouping keys values of this group private GroupSpec groupSpecs[]; private Sequence groupKey; private XQueryContext context; private int groupKeyLength; // used to keep track of the type of added items. private int itemType = Type.ANY_TYPE; public GroupedValueSequence(GroupSpec groupSpecs[], int size, Sequence keySequence, XQueryContext aContext) { this.groupSpecs = groupSpecs; this.items = new Entry[size]; this.groupKey = keySequence; this.context = aContext; this.groupKeyLength = groupKey.getItemCount(); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#iterate() */ public SequenceIterator iterate() throws XPathException { return new GroupedValueSequenceIterator(); } /* (non-Javadoc) * @see org.exist.xquery.value.AbstractSequence#unorderedIterator() */ public SequenceIterator unorderedIterator() throws XPathException { return new GroupedValueSequenceIterator(); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#getLength() */ public int getItemCount() { return (items == null) ? 0 : count; } public Sequence getGroupKey() { return this.groupKey; } public boolean isEmpty() { return isEmpty; } public boolean hasOne() { return hasOne; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#add(org.exist.xquery.value.Item) */ public void add(Item item) throws XPathException { if (hasOne) hasOne = false; if (isEmpty) hasOne = true; isEmpty = false; if(count == items.length) { Entry newItems[] = new Entry[count * 2]; System.arraycopy(items, 0, newItems, 0, count); items = newItems; } items[count++] = new Entry(item); checkItemType(item.getType()); } /* (non-Javadoc) * @see org.exist.xquery.value.AbstractSequence#addAll(org.exist.xquery.value.Sequence) */ public void addAll(Sequence other) throws XPathException { if(other.hasOne()) add(other.itemAt(0)); else if(!other.isEmpty()) { for(SequenceIterator i = other.iterate(); i.hasNext(); ) { Item next = i.nextItem(); if(next != null) add(next); } } } public Item itemAt(int pos) { if(items != null && pos > -1 && pos < count) return items[pos].item; else return null; } private void checkItemType(int type) { if(itemType == Type.NODE || itemType == type) return; if(itemType == Type.ANY_TYPE) itemType = type; else itemType = Type.NODE; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#getItemType() */ public int getItemType() { return itemType; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#toNodeSet() */ public NodeSet toNodeSet() throws XPathException { // for this method to work, all items have to be nodes if(itemType != Type.ANY_TYPE && Type.subTypeOf(itemType, Type.NODE)) { NodeSet set = new ExtArrayNodeSet(); //We can't make it from an ExtArrayNodeSet (probably because it is sorted ?) //NodeSet set = new ArraySet(100); for (int i = 0; i < this.count; i++) { NodeValue v = null; Entry temp = items[i]; v = (NodeValue)temp.item; if(v.getImplementationType() != NodeValue.PERSISTENT_NODE) { set.add((NodeProxy)v); } else { set.add((NodeProxy)v); } } return set; } else throw new XPathException("Type error: the sequence cannot be converted into" + " a node set. Item type is " + Type.getTypeName(itemType)); } public MemoryNodeSet toMemNodeSet() throws XPathException { if(count == 0) return MemoryNodeSet.EMPTY; if(itemType == Type.ANY_TYPE || !Type.subTypeOf(itemType, Type.NODE)) { throw new XPathException("Type error: the sequence cannot be converted into" + " a node set. Item type is " + Type.getTypeName(itemType)); } NodeValue v; for (int i = 0; i <= count; i++) { v = (NodeValue)items[i]; if(v.getImplementationType() == NodeValue.PERSISTENT_NODE) return null; } return new ValueSequence(this); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#removeDuplicates() */ public void removeDuplicates() { // TODO: is this ever relevant? } private class Entry implements Comparable { Item item; AtomicValue values[]; public Entry(Item item) throws XPathException { this.item = item; values = new AtomicValue[groupSpecs.length]; for(int i = 0; i < groupSpecs.length; i++) { Sequence seq = groupSpecs[i].getGroupExpression().eval(null); values[i] = AtomicValue.EMPTY_VALUE; //TODO : get rid of getLength() if(seq.hasOne()) { values[i] = seq.itemAt(0).atomize(); } else if(seq.hasMany()) throw new XPathException("expected a single value for group by expression " + ExpressionDumper.dump(groupSpecs[i].getGroupExpression()) + " ; found: " + seq.getItemCount()); } } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Object o){ Entry other = (Entry)o; int cmp = 0; AtomicValue a, b; for(int i = 0; i < values.length; i++) { a = values[i]; b = other.values[i]; if(a == AtomicValue.EMPTY_VALUE && b != AtomicValue.EMPTY_VALUE) { cmp = 1; } else{ cmp = a.compareTo(b); } } return cmp; } } private class GroupedValueSequenceIterator implements SequenceIterator { int pos = 0; /* (non-Javadoc) * @see org.exist.xquery.value.SequenceIterator#hasNext() */ public boolean hasNext() { return pos < count; } /* (non-Javadoc) * @see org.exist.xquery.value.SequenceIterator#nextItem() */ public Item nextItem() { if(pos < count) { return items[pos++].item; } return null; } } }