blob: 996b6e0c2860374fd5ae887a4350d0c489c3f6c7 [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.lucene;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.modules.parsing.lucene.support.Convertor;
import org.netbeans.modules.parsing.lucene.support.Index;
import org.netbeans.modules.parsing.lucene.support.StoppableConvertor;
import org.openide.util.Parameters;
/**
*
* @author Tomas Zezula
*/
public class MemoryIndex implements Index {
private final Analyzer analyzer;
private final ReentrantReadWriteLock lock;
//@GuardedBy("this")
private RAMDirectory dir;
//@GuardedBy("this")
private IndexReader cachedReader;
private MemoryIndex(@NonNull final Analyzer analyzer) {
assert analyzer != null;
this.analyzer = analyzer;
this.lock = new ReentrantReadWriteLock();
}
@NonNull
static MemoryIndex create(@NonNull Analyzer analyzer) {
return new MemoryIndex(analyzer);
}
@NonNull
@Override
public Status getStatus(boolean tryOpen) throws IOException {
return Status.VALID;
}
@Override
public <T> void query(
@NonNull Collection<? super T> result,
@NonNull Convertor<? super Document, T> convertor,
@NullAllowed FieldSelector selector,
@NullAllowed AtomicBoolean cancel,
@NonNull Query... queries) throws IOException, InterruptedException {
Parameters.notNull("queries", queries); //NOI18N
Parameters.notNull("convertor", convertor); //NOI18N
Parameters.notNull("result", result); //NOI18N
if (selector == null) {
selector = AllFieldsSelector.INSTANCE;
}
lock.readLock().lock();
try {
final IndexReader in = getReader();
if (in == null) {
return;
}
final BitSet bs = new BitSet(in.maxDoc());
final Collector c = new BitSetCollector(bs);
final Searcher searcher = new IndexSearcher(in);
try {
for (Query q : queries) {
if (cancel != null && cancel.get()) {
throw new InterruptedException ();
}
searcher.search(q, c);
}
} finally {
searcher.close();
}
for (int docNum = bs.nextSetBit(0); docNum >= 0; docNum = bs.nextSetBit(docNum+1)) {
if (cancel != null && cancel.get()) {
throw new InterruptedException ();
}
final Document doc = in.document(docNum, selector);
final T value = convertor.convert(doc);
if (value != null) {
result.add (value);
}
}
} finally {
lock.readLock().unlock();
}
}
@Override
public <S, T> void queryDocTerms(
@NonNull Map<? super T, Set<S>> result,
@NonNull Convertor<? super Document, T> convertor,
@NonNull Convertor<? super Term, S> termConvertor,
@NullAllowed FieldSelector selector,
@NullAllowed AtomicBoolean cancel,
@NonNull Query... queries) throws IOException, InterruptedException {
Parameters.notNull("result", result); //NOI18N
Parameters.notNull("convertor", convertor); //NOI18N
Parameters.notNull("termConvertor", termConvertor); //NOI18N
Parameters.notNull("queries", queries); //NOI18N
if (selector == null) {
selector = AllFieldsSelector.INSTANCE;
}
lock.readLock().lock();
try {
final IndexReader in = getReader();
if (in == null) {
return;
}
final BitSet bs = new BitSet(in.maxDoc());
final Collector c = new BitSetCollector(bs);
final Searcher searcher = new IndexSearcher(in);
final TermCollector termCollector = new TermCollector(c);
try {
for (Query q : queries) {
if (cancel != null && cancel.get()) {
throw new InterruptedException ();
}
if (q instanceof TermCollector.TermCollecting) {
((TermCollector.TermCollecting)q).attach(termCollector);
} else {
throw new IllegalArgumentException (
String.format("Query: %s does not implement TermCollecting", //NOI18N
q.getClass().getName()));
}
searcher.search(q, termCollector);
}
} finally {
searcher.close();
}
for (int docNum = bs.nextSetBit(0); docNum >= 0; docNum = bs.nextSetBit(docNum+1)) {
if (cancel != null && cancel.get()) {
throw new InterruptedException ();
}
final Document doc = in.document(docNum, selector);
final T value = convertor.convert(doc);
if (value != null) {
final Set<Term> terms = termCollector.get(docNum);
if (terms != null) {
result.put (value, convertTerms(termConvertor, terms));
}
}
}
} finally {
lock.readLock().unlock();
}
}
@Override
public <T> void queryTerms(
@NonNull Collection<? super T> result,
@NullAllowed Term start,
@NonNull StoppableConvertor<Term, T> filter,
@NullAllowed AtomicBoolean cancel) throws IOException, InterruptedException {
Parameters.notNull("result", result); //NOI18N
Parameters.notNull("filter", filter); //NOI18N
lock.readLock().lock();
try {
final IndexReader in = getReader();
if (in == null) {
return;
}
final TermEnum terms = start == null ? in.terms () : in.terms (start);
try {
do {
if (cancel != null && cancel.get()) {
throw new InterruptedException ();
}
final Term currentTerm = terms.term();
if (currentTerm != null) {
final T vote = filter.convert(currentTerm);
if (vote != null) {
result.add(vote);
}
}
} while (terms.next());
} catch (StoppableConvertor.Stop stop) {
//Stop iteration of TermEnum
} finally {
terms.close();
}
} finally {
lock.readLock().unlock();
}
}
@Override
public <S, T> void store(Collection<T> toAdd, Collection<S> toDelete, Convertor<? super T, ? extends Document> docConvertor, Convertor<? super S, ? extends Query> queryConvertor, boolean optimize) throws IOException {
lock.writeLock().lock();
try {
final IndexWriter out = getWriter();
try {
for (S td : toDelete) {
out.deleteDocuments(queryConvertor.convert(td));
}
if (toAdd.isEmpty()) {
return;
}
for (Iterator<T> it = toAdd.iterator(); it.hasNext();) {
T entry = it.next();
it.remove();
final Document doc = docConvertor.convert(entry);
out.addDocument(doc);
}
} finally {
try {
out.close();
} finally {
refreshReader();
}
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public void clear() throws IOException {
lock.writeLock().lock();
close();
try {
synchronized (MemoryIndex.this) {
if (dir != null) {
dir.close();
dir = null;
}
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public void close() throws IOException {
lock.writeLock().lock();
try {
synchronized (MemoryIndex.this) {
if (cachedReader != null) {
cachedReader.close();
cachedReader = null;
}
}
} finally {
lock.writeLock().unlock();
}
}
@CheckForNull
private synchronized IndexReader getReader() throws IOException {
if (cachedReader == null) {
try {
cachedReader = IndexReader.open(getDirectory(),true);
} catch (FileNotFoundException fnf) {
//pass - returns null
}
}
return cachedReader;
}
private synchronized void refreshReader() throws IOException {
assert lock.isWriteLockedByCurrentThread();
if (cachedReader != null) {
final IndexReader newReader = cachedReader.reopen();
if (newReader != cachedReader) {
cachedReader.close();
cachedReader = newReader;
}
}
}
private synchronized IndexWriter getWriter() throws IOException {
return new IndexWriter (getDirectory(), analyzer, IndexWriter.MaxFieldLength.LIMITED);
}
private synchronized Directory getDirectory() {
if (dir == null) {
dir = new RAMDirectory();
}
return dir;
}
private static <T> Set<T> convertTerms(final Convertor<? super Term, T> convertor, final Set<? extends Term> terms) {
final Set<T> result = new HashSet<T>(terms.size());
for (Term term : terms) {
result.add(convertor.convert(term));
}
return result;
}
}