blob: 938caac0b010d542b0a65d872e0d43d3e5b93ba3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.parsing.impl.indexing;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.text.MessageFormat;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.modules.parsing.impl.indexing.lucene.DocumentBasedIndexManager;
import org.netbeans.modules.parsing.lucene.support.Convertor;
import org.netbeans.modules.parsing.lucene.support.DocumentIndex;
import org.netbeans.modules.parsing.lucene.support.DocumentIndexCache;
import org.netbeans.modules.parsing.lucene.support.IndexDocument;
import org.netbeans.modules.parsing.spi.indexing.Indexable;
import org.openide.util.Pair;
import org.openide.util.Parameters;
import org.openide.util.BaseUtilities;
/**
*
* @author vita
* @author Tomas Zezula
*/
//@NotThreadSafe
public final class ClusteredIndexables {
public static final String FIELD_PRIMARY_KEY = "_sn"; //NOI18N
public static final String DELETE = "ci-delete-set"; //NOI18N
public static final String INDEX = "ci-index-set"; //NOI18N
// -----------------------------------------------------------------------
// Public implementation
// -----------------------------------------------------------------------
/**
* Creates new ClusteredIndexables
* @param indexables, requires a list with fast {@link List#get(int)} as it heavily calls it.
*/
public ClusteredIndexables(@NonNull final List<Indexable> indexables) {
Parameters.notNull("indexables", indexables); //NOI18N
this.indexables = indexables;
this.sorted = new BitSet(indexables.size());
}
@NonNull
public Iterable<Indexable> getIndexablesFor(@NullAllowed String mimeType) {
if (mimeType == null) {
mimeType = ALL_MIME_TYPES;
}
if (mimeType.length() == 0) {
return new AllIndexables();
}
BitSet cluster = mimeTypeClusters.get(mimeType);
if (cluster == null) {
cluster = new BitSet();
// pick the indexables with the given mime type and add them to the cluster
for (int i = sorted.nextClearBit(0); i < indexables.size(); i = sorted.nextClearBit(i+1)) {
final Indexable indexable = indexables.get(i);
if (SPIAccessor.getInstance().isTypeOf(indexable, mimeType)) {
cluster.set(i);
sorted.set(i);
}
}
mimeTypeClusters.put(mimeType, cluster);
}
return new BitSetIterable(cluster);
}
@NonNull
public static AttachableDocumentIndexCache createDocumentIndexCache() {
return new DocumentIndexCacheImpl();
}
@NonNull
public static IndexDocument createDocument(@NonNull final String primaryKey) {
Parameters.notNull("primaryKey", primaryKey); //NOI18N
return new MemIndexDocument(primaryKey);
}
public static interface AttachableDocumentIndexCache extends DocumentIndexCache.WithCustomIndexDocument {
void attach(@NonNull final String mode, @NonNull final ClusteredIndexables ci);
void detach();
}
// -----------------------------------------------------------------------
// Private implementation
// -----------------------------------------------------------------------
private static final Logger LOG = Logger.getLogger(ClusteredIndexables.class.getName());
private static final String ALL_MIME_TYPES = ""; //NOI18N
private static final String PROP_CACHE_HEAP_RATIO = "ClusteredIndexables.cacheHeapRatio"; //NOI18N
private static final double DEFAULT_CACHE_HEAP_RATIO = 0.1;
private static final long DATA_CACHE_SIZE = (long)
(Runtime.getRuntime().maxMemory() * getCacheHeapRatio());
private final List<Indexable> indexables;
private final BitSet sorted;
private final Map<String, BitSet> mimeTypeClusters = new HashMap<String, BitSet>();
private IndexedIterator<Indexable> currentIt;
@NonNull
private Indexable get(final int index) {
return indexables.get(index);
}
private int current() {
final IndexedIterator tmpIt = currentIt;
return tmpIt == null ? -1 : tmpIt.index();
}
private static double getCacheHeapRatio() {
final String sval = System.getProperty(PROP_CACHE_HEAP_RATIO);
if (sval != null) {
try {
final double val = Double.valueOf(sval);
if (val < 0.05 || val > 1.0) {
throw new NumberFormatException();
}
return val;
} catch (NumberFormatException nfe) {
LOG.log(
Level.INFO,
"Invalid value of {0} property: {1}", //NOI18N
new Object[] {
PROP_CACHE_HEAP_RATIO,
sval
});
}
}
return DEFAULT_CACHE_HEAP_RATIO;
}
private static interface IndexedIterator<T> extends Iterator<T> {
int index();
}
//<editor-fold defaultstate="collapsed" desc="All Indexables">
private static final class AllIndexablesIt implements IndexedIterator<Indexable> {
private final Iterator<? extends Indexable> delegate;
private int index = -1;
AllIndexablesIt(Iterator<? extends Indexable> delegate) {
this.delegate = delegate;
}
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public Indexable next() {
final Indexable res = delegate.next();
index++;
return res;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Immutable type"); //NOI18N
}
@Override
public int index() {
return index;
}
}
private final class AllIndexables implements Iterable<Indexable> {
@Override
public Iterator<Indexable> iterator() {
return ClusteredIndexables.this.currentIt = new AllIndexablesIt(indexables.iterator());
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="BitSet Based Indexables">
private final class BitSetIterator implements IndexedIterator<Indexable> {
private final BitSet bs;
private int index;
BitSetIterator(@NonNull final BitSet bs) {
this.bs = bs;
this.index = -1;
}
@Override
public boolean hasNext() {
return bs.nextSetBit(index + 1) >= 0;
}
@Override
public Indexable next() {
int tmp = bs.nextSetBit(index + 1);
if (tmp < 0) {
throw new NoSuchElementException();
}
index = tmp;
return indexables.get(tmp);
}
@Override
public void remove() {
throw new UnsupportedOperationException("Immutable type"); //NOI18N
}
public int index() {
return index;
}
}
private final class BitSetIterable implements Iterable<Indexable> {
private final BitSet bs;
BitSetIterable(@NonNull final BitSet bs) {
this.bs = bs;
}
@Override
@NonNull
public Iterator<Indexable> iterator() {
return ClusteredIndexables.this.currentIt = new BitSetIterator(bs);
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="DocumentIndexCache Implementation">
private static final class DocumentIndexCacheImpl implements AttachableDocumentIndexCache {
private static final Convertor<IndexDocument, Document> ADD_CONVERTOR =
new Convertor<IndexDocument, Document>() {
@NonNull
@Override
public Document convert(@NonNull final IndexDocument doc) {
final ReusableIndexDocument rdoc = (ReusableIndexDocument) doc;
return rdoc.doc;
}
};
private ClusteredIndexables deleteIndexables;
private ClusteredIndexables indexIndexables;
private BitSet deleteFromDeleted;
private BitSet deleteFromIndex;
private DocumentStore toAdd;
private List<String> toDeleteOutOfOrder;
private Reference<Collection[]> dataRef;
private volatile Pair<Long,StackTraceElement[]> attachDeleteStackTrace;
private volatile Pair<Long,StackTraceElement[]> attachIndexStackTrace;
private volatile Pair<Long,StackTraceElement[]> detachDeleteStackTrace;
private volatile Pair<Long,StackTraceElement[]> detachIndexStackTrace;
private DocumentIndexCacheImpl() {}
@Override
public void attach(
@NonNull final String mode,
@NonNull final ClusteredIndexables ci) {
Parameters.notNull("mode", mode); //NOI18N
Parameters.notNull("ci", ci); //NOI18N
if (TransientUpdateSupport.isTransientUpdate()) {
return;
}
if (DELETE.equals(mode)) {
ensureNotReBound(this.deleteIndexables, ci);
if (!ci.equals(this.deleteIndexables)) {
this.deleteIndexables = ci;
attachDeleteStackTrace = Pair.<Long,StackTraceElement[]>of(
System.nanoTime(),Thread.currentThread().getStackTrace());
detachDeleteStackTrace = null;
}
} else if (INDEX.equals(mode)) {
ensureNotReBound(this.indexIndexables, ci);
if (!ci.equals(this.indexIndexables)) {
this.indexIndexables = ci;
attachIndexStackTrace = Pair.<Long,StackTraceElement[]>of(
System.nanoTime(),Thread.currentThread().getStackTrace());
detachIndexStackTrace = null;
}
} else {
throw new IllegalArgumentException(mode);
}
}
@Override
public void detach() {
if (TransientUpdateSupport.isTransientUpdate()) {
return;
}
detachDeleteStackTrace = detachIndexStackTrace = Pair.<Long,StackTraceElement[]>of(
System.nanoTime(),Thread.currentThread().getStackTrace());
clear();
this.deleteIndexables = null;
this.indexIndexables = null;
}
@Override
public boolean addDocument(IndexDocument document) {
if (!(document instanceof MemIndexDocument)) {
throw new IllegalArgumentException(document.getClass().getName());
}
boolean shouldFlush = init();
handleDelete(
indexIndexables,
deleteFromIndex,
toDeleteOutOfOrder,
document.getPrimaryKey());
shouldFlush |= toAdd.addDocument(document);
return shouldFlush;
}
@Override
public boolean removeDocument(String primaryKey) {
final boolean shouldFlush = init();
handleDelete(
deleteIndexables,
deleteFromDeleted,
toDeleteOutOfOrder,
primaryKey);
return shouldFlush;
}
@Override
public void clear() {
toAdd = null;
toDeleteOutOfOrder = null;
deleteFromDeleted = null;
deleteFromIndex = null;
dataRef = null;
}
@Override
public Collection<? extends String> getRemovedKeys() {
return toDeleteOutOfOrder != null ?
new RemovedCollection (
toDeleteOutOfOrder,
deleteIndexables,
deleteFromDeleted,
indexIndexables,
deleteFromIndex,
attachDeleteStackTrace,
attachIndexStackTrace,
detachDeleteStackTrace,
detachIndexStackTrace) :
Collections.<String>emptySet();
}
@Override
public Collection<? extends IndexDocument> getAddedDocuments() {
return toAdd != null ? toAdd : Collections.<IndexDocument>emptySet();
}
@Override
public Convertor<IndexDocument, Document> createAddConvertor() {
return ADD_CONVERTOR;
}
@Override
public Convertor<Document, IndexDocument> createQueryConvertor() {
return null;
}
private static void ensureNotReBound(
@NullAllowed final ClusteredIndexables oldCi,
@NonNull final ClusteredIndexables newCi) {
if (oldCi != null && !oldCi.equals(newCi)) {
throw new IllegalStateException(
String.format(
"Cannot bind to ClusteredIndexables(%d), already bound to ClusteredIndexables(%d)", //NOI18N
System.identityHashCode(newCi),
System.identityHashCode(oldCi)
));
}
}
private static void handleDelete(
@NullAllowed ClusteredIndexables ci,
@NonNull BitSet bs,
@NonNull List<? super String> toDelete,
@NonNull String primaryKey) {
final int index = isCurrent(ci, primaryKey);
if (index >= 0) {
bs.set(index);
} else {
toDelete.add(primaryKey);
}
}
private static int isCurrent(
@NullAllowed final ClusteredIndexables ci,
@NonNull final String primaryKey) {
if (ci == null) {
return -1;
}
final int currentIndex = ci.current();
if (currentIndex == -1) {
return -1;
}
final Indexable currentIndexable = ci.get(currentIndex);
if (primaryKey.equals(currentIndexable.getRelativePath())) {
return currentIndex;
}
return -1;
}
private boolean init() {
if (toAdd == null || toDeleteOutOfOrder == null) {
assert toAdd == null &&
toDeleteOutOfOrder == null &&
deleteFromDeleted == null &&
deleteFromIndex == null;
assert dataRef == null;
toAdd = new DocumentStore(DATA_CACHE_SIZE);
toDeleteOutOfOrder = new ArrayList<String>();
deleteFromDeleted = new BitSet();
deleteFromIndex = new BitSet();
dataRef = new ClearReference(
new Collection[] {toAdd, toDeleteOutOfOrder},
this);
}
return dataRef.get() == null;
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Flushing Soft Reference">
private static final class ClearReference extends SoftReference<Collection[]> implements Runnable, Callable<Void> {
private final DocumentIndexCacheImpl owner;
private final AtomicInteger state = new AtomicInteger();
public ClearReference(
@NonNull final Collection[] data,
@NonNull final DocumentIndexCacheImpl owner) {
super(data, BaseUtilities.activeReferenceQueue());
Parameters.notNull("data", data); //NOI18N
Parameters.notNull("owner", owner); //NOI18N
this.owner = owner;
}
@Override
public void run() {
if (!state.compareAndSet(0, 1)) {
throw new IllegalStateException(Integer.toString(state.get()));
}
InjectedTasksSupport.enqueueTask(this);
LOG.log(
Level.FINEST,
"Reference Task Enqueued for: {0}", //NOI18N
owner);
}
@Override
public Void call () throws Exception {
if (!state.compareAndSet(1, 2)) {
throw new IllegalStateException(Integer.toString(state.get()));
}
final DocumentIndex.Transactional txIndex = DocumentBasedIndexManager.getDefault().getIndex(owner);
if (txIndex != null) {
txIndex.txStore();
}
LOG.log(
Level.FINEST,
"Reference Task Executed for: {0}", //NOI18N
owner);
return null;
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Removed Keys Collection">
private static class RemovedCollection extends AbstractCollection<String> {
private final List<? extends String> outOfOrder;
private final ClusteredIndexables deleteIndexables;
private final BitSet deleteFromDeleted;
private final ClusteredIndexables indexIndexables;
private final BitSet deleteFromIndex;
private final Pair<Long,StackTraceElement[]> attachDeleteStackTrace;
private final Pair<Long,StackTraceElement[]> attachIndexStackTrace;
private final Pair<Long, StackTraceElement[]> detachDeleteStackTrace;
private final Pair<Long, StackTraceElement[]> detachIndexStackTrace;
RemovedCollection(
@NonNull final List<? extends String> outOfOrder,
@NullAllowed final ClusteredIndexables deleteIndexables,
@NonNull final BitSet deleteFromDeleted,
@NullAllowed final ClusteredIndexables indexIndexables,
@NonNull final BitSet deleteFromIndex,
@NullAllowed final Pair<Long,StackTraceElement[]> attachDeleteStackTrace,
@NullAllowed final Pair<Long, StackTraceElement[]> attachIndexStackTrace,
@NullAllowed final Pair<Long, StackTraceElement[]> detachDeleteStackTrace,
@NullAllowed final Pair<Long, StackTraceElement[]> detachIndexStackTrace) {
assert outOfOrder != null;
assert deleteFromDeleted != null;
assert deleteFromIndex != null;
this.outOfOrder = outOfOrder;
this.deleteIndexables = deleteIndexables;
this.deleteFromDeleted = deleteFromDeleted;
this.indexIndexables = indexIndexables;
this.deleteFromIndex = deleteFromIndex;
this.attachDeleteStackTrace = attachDeleteStackTrace;
this.attachIndexStackTrace = attachIndexStackTrace;
this.detachDeleteStackTrace = detachDeleteStackTrace;
this.detachIndexStackTrace = detachIndexStackTrace;
}
@Override
public Iterator<String> iterator() {
return new It(
outOfOrder.iterator(),
deleteIndexables,
deleteFromDeleted,
indexIndexables,
deleteFromIndex,
attachDeleteStackTrace,
attachIndexStackTrace,
detachDeleteStackTrace,
detachIndexStackTrace);
}
@Override
public int size() {
return outOfOrder.size() + deleteFromDeleted.cardinality() + deleteFromIndex.cardinality();
}
@Override
public boolean isEmpty() {
return outOfOrder.isEmpty() && deleteFromDeleted.isEmpty() && deleteFromIndex.isEmpty();
}
private static class It implements Iterator<String> {
private final Iterator<? extends String> outOfOrderIt;
private final ClusteredIndexables deleteIndexables;
private final BitSet deleteFromDeleted;
private final ClusteredIndexables indexIndexables;
private final BitSet deleteFromIndex;
private int state;
private int index;
private String current;
private final Pair<Long,StackTraceElement[]> attachDeleteStackTrace;
private final Pair<Long,StackTraceElement[]> attachIndexStackTrace;
private final Pair<Long, StackTraceElement[]> detachDeleteStackTrace;
private final Pair<Long, StackTraceElement[]> detachIndexStackTrace;
It(
@NonNull final Iterator<? extends String> outOfOrderIt,
@NullAllowed final ClusteredIndexables deleteIndexables,
@NonNull final BitSet deleteFromDeleted,
@NullAllowed final ClusteredIndexables indexIndexables,
@NonNull final BitSet deleteFromIndex,
@NullAllowed final Pair<Long,StackTraceElement[]> attachDeleteStackTrace,
@NullAllowed final Pair<Long, StackTraceElement[]> attachIndexStackTrace,
@NullAllowed final Pair<Long, StackTraceElement[]> detachDeleteStackTrace,
@NullAllowed final Pair<Long, StackTraceElement[]> detachIndexStackTrace) {
this.outOfOrderIt = outOfOrderIt;
this.deleteIndexables = deleteIndexables;
this.deleteFromDeleted = deleteFromDeleted;
this.indexIndexables = indexIndexables;
this.deleteFromIndex = deleteFromIndex;
this.attachDeleteStackTrace = attachDeleteStackTrace;
this.attachIndexStackTrace = attachIndexStackTrace;
this.detachDeleteStackTrace = detachDeleteStackTrace;
this.detachIndexStackTrace = detachIndexStackTrace;
}
@Override
@SuppressWarnings("fallthrough")
public boolean hasNext() {
if (current != null) {
return true;
}
switch (state) {
case 0:
if (outOfOrderIt.hasNext()) {
current = outOfOrderIt.next();
return true;
} else {
index = -1;
state = 1;
}
case 1:
index = deleteFromDeleted.nextSetBit(index+1);
if (index >=0) {
if (deleteIndexables == null) {
throwIllegalState(
"No deleteIndexables", //NOI18N
attachDeleteStackTrace,
detachDeleteStackTrace);
}
try {
final Indexable file = deleteIndexables.get(index);
current = file.getRelativePath();
return true;
} catch (IndexOutOfBoundsException e) {
throwIllegalState(
"Wrong deleteIndexables", //NOI18N
attachDeleteStackTrace,
detachDeleteStackTrace);
}
} else {
index = -1;
state = 2;
}
case 2:
index = deleteFromIndex.nextSetBit(index+1);
if (index >= 0) {
if (indexIndexables == null) {
throwIllegalState(
"No indexIndexables", //NOI18N
attachIndexStackTrace,
detachIndexStackTrace);
}
try {
final Indexable file = indexIndexables.get(index);
current = file.getRelativePath();
return true;
} catch (IndexOutOfBoundsException e) {
throwIllegalState(
"Wrong indexIndexables", //NOI18N
attachIndexStackTrace,
detachIndexStackTrace);
}
} else {
index = -1;
state = 3;
}
default:
return false;
}
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
final String res = current;
assert res != null;
current = null;
return res;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Immutable collection"); //NOI18N
}
private static void throwIllegalState(
@NonNull final String reason,
@NullAllowed final Pair<Long,StackTraceElement[]> attach,
@NullAllowed final Pair<Long,StackTraceElement[]> detach) {
throw new IllegalStateException(
MessageFormat.format(
"{0} : Attached at: {1} by: {2}, Detached at: {3} by: {4}", //NOI18N
reason,
attach == null ? null : attach.first(),
attach == null ? null : Arrays.asList(attach.second()),
detach == null ? null : detach.first(),
detach == null ? null : Arrays.asList(detach.second())));
}
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Input IndexDocument (inserted into cache)">
private static final class MemIndexDocument implements IndexDocument {
private static final String[] EMPTY = new String[0];
private final List<Fieldable> fields = new ArrayList<Fieldable>();
boolean consumed;
MemIndexDocument(@NonNull final String primaryKey) {
Parameters.notNull("primaryKey", primaryKey); //NOI18N
fields.add(sourceNameField(primaryKey));
}
public List<Fieldable> getFields() {
return fields;
}
@Override
public String getPrimaryKey() {
return getValue(FIELD_PRIMARY_KEY);
}
@Override
public void addPair(String key, String value, boolean searchable, boolean stored) {
if (consumed) {
throw new IllegalStateException("Modifying Document after adding it into index."); //NOI18N
}
final Field field = new Field (key, value,
stored ? Field.Store.YES : Field.Store.NO,
searchable ? Field.Index.NOT_ANALYZED_NO_NORMS : Field.Index.NO);
fields.add (field);
}
@Override
public String getValue(String key) {
for (Fieldable field : fields) {
if (field.name().equals(key)) {
return field.stringValue();
}
}
return null;
}
@Override
public String[] getValues(String key) {
final List<String> result = new ArrayList<String>();
for (Fieldable field : fields) {
if (field.name().equals(key)) {
result.add(field.stringValue());
}
}
return result.toArray(result.isEmpty() ? EMPTY : new String[result.size()]);
}
private Fieldable sourceNameField(@NonNull String primaryKey) {
return new Field(FIELD_PRIMARY_KEY, primaryKey, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Output IndexDocument (output of the cache)">
//@NotThreadSafe
private static final class ReusableIndexDocument implements IndexDocument {
private final Document doc = new Document();
ReusableIndexDocument() {
}
ReusableIndexDocument(@NonNull final MemIndexDocument memDoc) {
Parameters.notNull("memDoc", memDoc); //NOI18N
for (Fieldable field : memDoc.getFields()) {
doc.add(field);
}
}
@Override
public String getPrimaryKey() {
return doc.get(FIELD_PRIMARY_KEY);
}
@Override
public String getValue(String key) {
return doc.get(key);
}
@Override
public String[] getValues(String key) {
return doc.getValues(key);
}
@Override
public void addPair(String key, String value, boolean searchable, boolean stored) {
doc.add(new Field (
key,
value,
stored ? Field.Store.YES : Field.Store.NO,
searchable ? Field.Index.NOT_ANALYZED_NO_NORMS : Field.Index.NO));
}
void clear() {
doc.getFields().clear();
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Added IndexDocuments Collection (optimized for high number of fields).">
/*test*/ static final class DocumentStore extends AbstractCollection<IndexDocument>{
private static final int INITIAL_DOC_COUNT = 100;
private static final int INITIAL_DATA_SIZE = 1<<10;
private final AtomicReference<Thread> ownerThread;
private final long dataCacheSize;
private final Map<String,Integer> fieldNames;
private int[] docs;
private char[] data;
private int nameIndex;
private int docsPointer;
private int dataPointer;
private int size;
private MemIndexDocument overflowDocument;
DocumentStore(final long dataCacheSize) {
this.ownerThread = new AtomicReference<Thread>();
this.dataCacheSize = dataCacheSize;
this.fieldNames = new LinkedHashMap<String, Integer>();
this.docs = new int[INITIAL_DOC_COUNT];
this.data = new char[INITIAL_DATA_SIZE];
LOG.log(
Level.FINE,
"DocumentStore flush size: {0}", //NOI18N
dataCacheSize);
}
@Override
public boolean add(@NonNull final IndexDocument doc) {
addDocument(doc, true);
return true;
}
boolean addDocument(@NonNull final IndexDocument doc) {
return addDocument(doc, false);
}
private boolean addDocument(
@NonNull final IndexDocument doc,
final boolean compat) {
assert sameThread();
boolean res = false;
if (!(doc instanceof MemIndexDocument)) {
throw new IllegalArgumentException();
}
final MemIndexDocument mdoc = (MemIndexDocument)doc;
final int oldDocsPointer = docsPointer;
final int oldDataPointer = dataPointer;
for (Fieldable fld : mdoc.getFields()) {
final String fldName = fld.name();
final boolean stored = fld.isStored();
final boolean indexed = fld.isIndexed();
final String fldValue = fld.stringValue();
int index;
Integer indexBoxed = fieldNames.get(fldName);
if (indexBoxed == null) {
index = nameIndex++;
fieldNames.put(fldName, index);
} else {
index = indexBoxed;
}
index = (index << 3) | (stored ? 4 : 0) | (indexed ? 2 : 0) | 1;
if (docs.length < docsPointer + 2) {
docs = Arrays.copyOf(docs, docs.length << 1);
}
docs[docsPointer] = index;
docs[docsPointer + 1] = dataPointer;
docsPointer += 2;
if (data.length < dataPointer + fldValue.length()) {
final int newDataLength = newLength(data.length,dataPointer + fldValue.length());
res = newDataLength<<1 > dataCacheSize;
if (res && !compat) {
rollBack(oldDocsPointer, oldDataPointer, newDataLength, mdoc);
return res;
}
try {
LOG.log(
Level.FINEST,
"alloc"); //NOI18N
data = Arrays.copyOf(data, newDataLength);
} catch (OutOfMemoryError ooe) {
if (compat) {
throw ooe;
} else {
rollBack(oldDocsPointer, oldDataPointer, newDataLength, mdoc);
return true;
}
}
LOG.log(
Level.FINE,
"New data size: {0}", //NOI18N
new Object[] {
data.length
});
}
fldValue.getChars(0, fldValue.length(), data, dataPointer);
dataPointer += fldValue.length();
}
if (docs.length < docsPointer + 1) {
docs = Arrays.copyOf(docs, docs.length << 1);
}
docs[docsPointer++] = 0;
size++;
mdoc.consumed = true;
return res;
}
@Override
public Iterator<IndexDocument> iterator() {
return new It();
}
@Override
public void clear() {
assert sameThread();
fieldNames.clear();
docs = new int[INITIAL_DOC_COUNT];
data = new char[INITIAL_DATA_SIZE];
docsPointer = 0;
dataPointer = 0;
nameIndex = 0;
size = 0;
}
@Override
public int size() {
return size + (overflowDocument == null ? 0 : 1);
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("Remove not supported."); //NOI18N
}
private static int newLength(
int currentLength,
final int minimalLength) {
do {
currentLength <<= 1;
} while (currentLength < minimalLength);
return currentLength;
}
/**
* Rolls back last document causing overflow and hold the reference to it for iterator.
*/
private void rollBack(
final int oldDocsPointer,
final int oldDataPointer,
final int newDataLength,
@NonNull final MemIndexDocument mdoc) {
Parameters.notNull("mdoc", mdoc); //NOI18N
assert overflowDocument == null;
overflowDocument = mdoc;
docsPointer = oldDocsPointer;
dataPointer = oldDataPointer;
LOG.log(
Level.FINE,
"Data size ({0}bytes) overflow -> flush", //NOI18N
new Object[] {
newDataLength,
});
}
private boolean sameThread() {
final Thread me = Thread.currentThread();
Thread t = ownerThread.get();
if (t == null) {
if (ownerThread.compareAndSet(null, me)) {
return true;
} else {
t = ownerThread.get();
}
}
return me.equals(t);
}
//<editor-fold defaultstate="collapsed" desc="Added IndexDocuments Iterator">
private class It implements Iterator<IndexDocument> {
private int cur = 0;
private final List<String> names;
private final ReusableIndexDocument doc;
It() {
names = new ArrayList<String>(fieldNames.keySet());
doc = new ReusableIndexDocument();
}
@Override
public boolean hasNext() {
return cur<docsPointer || overflowDocument != null;
}
@Override
public IndexDocument next() {
assert sameThread();
if (cur<docsPointer) {
doc.clear();
int nameIndex;
while ((nameIndex=docs[cur++]) != 0) {
final boolean stored = (nameIndex & 4) == 4;
final boolean indexed = (nameIndex & 2) == 2;
nameIndex >>>= 3;
final int dataStart = docs[cur++];
final int dataEnd = docs[cur] != 0 ?
docs[cur+1] :
cur+1 == docsPointer ?
dataPointer :
docs[cur+2];
final String value = new String (data,dataStart, dataEnd - dataStart);
doc.addPair(
names.get(nameIndex),
value,
indexed,
stored);
}
return doc;
} else if(overflowDocument != null) {
final IndexDocument res = new ReusableIndexDocument(overflowDocument);
overflowDocument = null;
return res;
} else {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
}
}
//</editor-fold>
}
//</editor-fold>
}