/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2014 The eXist Project
* http://exist-db.org
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.dom.persistent;
import org.exist.xquery.Constants;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Type;
import org.w3c.dom.Node;
/**
* Removes duplication between NewArrayNodeSet
* and ExtArrayNodeSet
*
* @author Adam Retter <adam@exist-db.org>
*/
public abstract class AbstractArrayNodeSet extends AbstractNodeSet implements DocumentSet {
protected static final int INITIAL_SIZE = 64;
protected int size = 0;
protected boolean isSorted = false;
protected boolean hasOne = false;
protected int state = 0;
private NodeProxy lastAdded = null;
// used to keep track of the type of added items.
protected int itemType = Type.ANY_TYPE;
/**
* Reset the ArrayNodeSet so that it
* may be reused
*/
public abstract void reset();
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean hasOne() {
return hasOne;
}
@Override
public void add(final NodeProxy proxy) {
add(proxy, Constants.NO_SIZE_HINT);
}
/**
* Add a new node to the set. If a new array of nodes has to be allocated
* for the document, use the sizeHint parameter to determine the size of
* the newly allocated array. This will overwrite the default array size.
*
* If the size hint is correct, no further reallocations will be required.
*/
@Override
public void add(final NodeProxy proxy, final int sizeHint) {
if(size > 0) {
if(hasOne) {
if(isSorted) {
this.hasOne = get(proxy) != null;
} else {
this.hasOne = lastAdded == null || lastAdded.compareTo(proxy) == 0;
}
}
} else {
this.hasOne = true;
}
addInternal(proxy, sizeHint);
this.isSorted = false;
setHasChanged();
checkItemType(proxy.getType());
this.lastAdded = proxy;
}
/**
* Just add the node to this set
* all of the checks have been
* done in @see AbstractArrayNodeSet#add(NodeProxy, int)
*/
protected abstract void addInternal(final NodeProxy proxy, final int sizeHint);
@Override
public void addAll(final NodeSet other) {
if(other.isEmpty()) {
return;
} else if(other.hasOne()) {
add((NodeProxy) other.itemAt(0));
} else {
for(final NodeProxy node : other) {
add(node);
}
}
}
/**
* The method <code>getItemType</code>
*
* @return an <code>int</code> value
*/
@Override
public int getItemType() {
return itemType;
}
private void checkItemType(final int type) {
if(itemType == Type.NODE || itemType == type) {
return;
}
if(itemType == Type.ANY_TYPE) {
itemType = type;
} else {
itemType = Type.NODE;
}
}
private void setHasChanged() {
this.state = (state == Integer.MAX_VALUE ? 0 : state + 1);
}
@Override
public int getLength() {
if(!isSorted()) {
// sort to remove duplicates
sort();
}
return size;
}
@Override
public int getItemCount() {
return getLength();
}
@Override
public Node item(final int pos) {
sortInDocumentOrder();
final NodeProxy p = get(pos);
return p == null ? null : p.getNode();
}
@Override
public Item itemAt(final int pos) {
sortInDocumentOrder();
return get(pos);
}
@Override
public NodeSet selectParentChild(final NodeSet al, final int mode,
final int contextId) {
sort();
if(al instanceof VirtualNodeSet) {
return super.selectParentChild(al, mode, contextId);
}
return getDescendantsInSet(al, true, false, mode, contextId, true);
}
@Override
public NodeSet selectAncestorDescendant(final NodeSet al, final int mode,
final boolean includeSelf, int contextId, boolean copyMatches) {
sort();
if(al instanceof VirtualNodeSet) {
return super.selectAncestorDescendant(al, mode, includeSelf,
contextId, copyMatches);
}
return getDescendantsInSet(al, false, includeSelf, mode, contextId, copyMatches);
}
@Override
public NodeSet selectAncestors(final NodeSet al, final boolean includeSelf, final int contextId) {
sort();
return super.selectAncestors(al, includeSelf, contextId);
}
protected abstract NodeSet getDescendantsInSet(final NodeSet al,
final boolean childOnly, final boolean includeSelf, final int mode,
final int contextId, final boolean copyMatches);
@Override
public DocumentSet getDocumentSet() {
return this;
}
protected boolean isSorted() {
return isSorted;
}
/**
* Remove all duplicate nodes, but merge their
* contexts.
*/
public void mergeDuplicates() {
sort(true);
}
/**
* Sorts the nodes in the set
* into document order.
*
* Same as calling @see #sort(false)
*/
public void sortInDocumentOrder() {
sort(false);
}
/**
* Sorts the nodes in the set
* without merging their contexts.
*
* Same as calling @see #sort(false)
*/
public void sort() {
sort(false);
}
public abstract void sort(final boolean mergeContexts);
@Override
public boolean hasChanged(final int previousState) {
return state != previousState;
}
@Override
public int getState() {
return state;
}
@Override
public boolean isCacheable() {
return true;
}
@Override
public String toString() {
return "ArrayNodeSet#" + super.toString();
}
}