/*
* eXist Open Source Native XML Database
* Copyright (C) 2000-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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.dom.persistent;
import net.jcip.annotations.NotThreadSafe;
import org.exist.collections.Collection;
import org.exist.numbering.NodeId;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.lock.Lock.LockMode;
import org.exist.util.LockException;
import org.exist.util.hashtable.Int2ObjectHashMap;
import org.exist.xmldb.XmldbURI;
import org.w3c.dom.Node;
import java.util.*;
/**
* Manages a set of documents.
*
* This class implements the NodeList interface for a collection of documents.
* It also contains methods to retrieve the collections these documents
* belong to.
*
* @author wolf
* @author aretter
*/
@NotThreadSafe
public class DefaultDocumentSet extends Int2ObjectHashMap implements MutableDocumentSet {
private final static int DEFAULT_SIZE = 29;
private final static double DEFAULT_GROWTH = 1.75;
private final BitSet docIds = new BitSet();
private final BitSet collectionIds = new BitSet();
private final Set<Collection> collections = new TreeSet<>();
public DefaultDocumentSet() {
super(DEFAULT_SIZE, DEFAULT_GROWTH);
}
public DefaultDocumentSet(final int initialSize) {
super(initialSize, DEFAULT_GROWTH);
}
@Override
public void clear() {
super.clear();
this.docIds.clear();
this.collectionIds.clear();
this.collections.clear();
}
@Override
public void add(final DocumentImpl doc) {
add(doc, true);
}
@Override
public void add(final DocumentImpl doc, final boolean checkDuplicates) {
final int docId = doc.getDocId();
if (checkDuplicates && contains(docId)) {
return;
}
docIds.set(docId);
put(docId, doc);
final Collection collection = doc.getCollection();
if (collection != null && !collectionIds.get(collection.getId())) {
collectionIds.set(collection.getId());
collections.add(collection);
}
}
public void add(final Node node) {
if (!(node instanceof DocumentImpl)) {
throw new IllegalArgumentException("wrong implementation");
}
add((DocumentImpl) node);
}
@Override
public void addAll(final DocumentSet other) {
for (final Iterator<DocumentImpl> i = other.getDocumentIterator(); i.hasNext(); ) {
add(i.next());
}
}
@Override
public void addCollection(final Collection collection) {
if (!collectionIds.get(collection.getId())) {
collectionIds.set(collection.getId());
collections.add(collection);
}
}
@SuppressWarnings("unchecked")
@Override
public Iterator<DocumentImpl> getDocumentIterator() {
return valueIterator();
}
@Override
public Iterator<Collection> getCollectionIterator() {
return collections.iterator();
}
@Override
public int getDocumentCount() {
return size();
}
public int getCollectionCount() {
return collections.size();
}
@Override
public DocumentImpl getDoc(final int docId) {
return (DocumentImpl)get(docId);
}
@Override
public XmldbURI[] getNames() {
final XmldbURI result[] = new XmldbURI[size()];
int j = 0;
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); j++) {
final DocumentImpl d = i.next();
result[j] = d.getFileURI();
}
Arrays.sort(result);
return result;
}
@Override
public DocumentSet intersection(final DocumentSet other) {
final DefaultDocumentSet r = new DefaultDocumentSet();
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl d = i.next();
if (other.contains(d.getDocId())) {
r.add(d);
}
}
for (final Iterator<DocumentImpl> i = other.getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl d = i.next();
if (contains(d.getDocId()) && (!r.contains(d.getDocId()))) {
r.add(d);
}
}
return r;
}
public DocumentSet union(final DocumentSet other) {
final DefaultDocumentSet result = new DefaultDocumentSet();
result.addAll(other);
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl d = i.next();
if (!result.contains(d.getDocId())) {
result.add(d);
}
}
return result;
}
@Override
public boolean contains(final DocumentSet other) {
if (other.getDocumentCount() > size()) {
return false;
}
if(other instanceof DefaultDocumentSet) {
// optimization for fast comparison when other is also a DefaultDocumentSet
final DefaultDocumentSet otherDDS = (DefaultDocumentSet)other;
final BitSet compare = new BitSet();
compare.or(docIds);
compare.and(otherDDS.docIds);
return compare.equals(otherDDS.docIds);
} else {
// otherwise, fallback to general comparison
final Iterator<DocumentImpl> otherDocumentIterator = other.getDocumentIterator();
while (otherDocumentIterator.hasNext()) {
final DocumentImpl otherDocument = otherDocumentIterator.next();
if (!contains(otherDocument.getDocId())) {
return false;
}
}
return true;
}
}
@Override
public boolean contains(final int id) {
return docIds.get(id);
}
@Override
public NodeSet docsToNodeSet() {
final NodeSet result = new NewArrayNodeSet();
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl doc = i.next();
if (doc.getResourceType() == DocumentImpl.XML_FILE) { // skip binary resources
result.add(new NodeProxy(doc, NodeId.DOCUMENT_NODE));
}
}
return result;
}
public int getMinDocId() {
int min = DocumentImpl.UNKNOWN_DOCUMENT_ID;
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl d = i.next();
if (min == DocumentImpl.UNKNOWN_DOCUMENT_ID) {
min = d.getDocId();
} else if (d.getDocId() < min) {
min = d.getDocId();
}
}
return min;
}
public int getMaxDocId() {
int max = DocumentImpl.UNKNOWN_DOCUMENT_ID;
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl d = i.next();
if (d.getDocId() > max) {
max = d.getDocId();
}
}
return max;
}
@Override
public boolean equalDocs(final DocumentSet other) {
if (this == other) {
// we are comparing the same objects
return true;
}
if (getDocumentCount() != other.getDocumentCount()) {
return false;
}
if(other instanceof DefaultDocumentSet) {
// optimization for fast comparison when other is also a DefaultDocumentSet
final DefaultDocumentSet otherDDS = (DefaultDocumentSet)other;
return docIds.equals(otherDDS.docIds);
} else {
// otherwise, fallback to general comparison
final Iterator<DocumentImpl> otherDocumentIterator = other.getDocumentIterator();
while (otherDocumentIterator.hasNext()) {
final DocumentImpl otherDocument = otherDocumentIterator.next();
if (!contains(otherDocument.getDocId())) {
return false;
}
}
return true;
}
}
@Override
public void lock(final DBBroker broker, final boolean exclusive, final boolean checkExisting) throws LockException {
//final Thread thread = Thread.currentThread();
for (int idx = 0; idx < tabSize; idx++) {
if (values[idx] == null || values[idx] == REMOVED) {
continue;
}
final DocumentImpl d = (DocumentImpl)values[idx];
final Lock dlock = d.getUpdateLock();
//if (checkExisting && dlock.hasLock(thread))
//continue;
if (exclusive) {
dlock.acquire(LockMode.WRITE_LOCK);
} else {
dlock.acquire(LockMode.READ_LOCK);
}
}
}
@Override
public void unlock(final boolean exclusive) {
final Thread thread = Thread.currentThread();
// NOTE: locks should be released in the reverse order that they were acquired
for (int idx = tabSize - 1; idx >= 0; idx--) {
if (values[idx] == null || values[idx] == REMOVED) {
continue;
}
final DocumentImpl d = (DocumentImpl)values[idx];
final Lock dlock = d.getUpdateLock();
if (exclusive) {
dlock.release(LockMode.WRITE_LOCK);
} else if (dlock.isLockedForRead(thread)) {
dlock.release(LockMode.READ_LOCK);
}
}
}
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
for (final Iterator<DocumentImpl> i = getDocumentIterator(); i.hasNext(); ) {
result.append(i.next());
if(i.hasNext()) {
result.append(", ");
}
}
return result.toString();
}
}