/******************************************************************************* * Copyright (c) 2016 Zend Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Zend Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.dltk.internal.core.index.lucene; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.BDV_DOC; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.BDV_ELEMENT_NAME; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.BDV_METADATA; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.BDV_PARENT; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.BDV_PATH; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.BDV_QUALIFIER; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.F_CC_NAME; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.F_ELEMENT_NAME_LC; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.F_PARENT; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.F_PATH; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.F_QUALIFIER; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.NDV_FLAGS; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.NDV_LENGTH; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.NDV_NAME_LENGTH; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.NDV_NAME_OFFSET; import static org.eclipse.dltk.internal.core.index.lucene.IndexFields.NDV_OFFSET; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Collector; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.LeafCollector; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.SearcherManager; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.BytesRef; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.dltk.core.ScriptModelUtil; import org.eclipse.dltk.core.index2.search.ISearchEngineExtension; import org.eclipse.dltk.core.index2.search.ISearchRequestor; import org.eclipse.dltk.core.search.IDLTKSearchScope; import org.eclipse.dltk.internal.core.search.DLTKSearchScope; /** * Lucene based implementation for DLTK search engine. * * @author Michal Niewrzal, Bartlomiej Laczkowski */ @SuppressWarnings("restriction") public class LuceneSearchEngine implements ISearchEngineExtension { private static final class SearchScope { static List<String> getContainers(IDLTKSearchScope scope) { List<String> containers = new ArrayList<>(); for (IPath path : scope.enclosingProjectsAndZips()) { containers.add(path.toString()); } return containers; } static List<String> getScripts(IDLTKSearchScope scope) { List<String> scripts = new ArrayList<>(); if (scope instanceof DLTKSearchScope) { String[] relativePaths = ((DLTKSearchScope) scope) .getRelativePaths(); String[] fileExtensions = ScriptModelUtil .getFileExtensions(scope.getLanguageToolkit()); for (String relativePath : relativePaths) { if (relativePath.length() > 0) { if (fileExtensions != null) { boolean isScriptFile = false; for (String ext : fileExtensions) { if (relativePath.endsWith("." + ext)) { //$NON-NLS-1$ isScriptFile = true; break; } } if (!isScriptFile) { break; } } scripts.add(relativePath); } } } return scripts; } } private static final class ResultsCollector implements Collector { private static final String[] NUMERIC_FIELDS = new String[] { NDV_OFFSET, NDV_LENGTH, NDV_FLAGS, NDV_NAME_OFFSET, NDV_NAME_LENGTH }; private static final String[] BINARY_FIELDS = new String[] { BDV_PATH, BDV_ELEMENT_NAME, BDV_QUALIFIER, BDV_PARENT, BDV_METADATA, BDV_DOC }; private Map<String, NumericDocValues> fDocNumericValues; private Map<String, BinaryDocValues> fDocBinaryValues; private String fContainer; private int fElementType; private List<SearchMatch> fResult; public ResultsCollector(String container, int elementType, List<SearchMatch> result) { this.fContainer = container; this.fElementType = elementType; this.fResult = result; } @Override public boolean needsScores() { return true; } @Override public LeafCollector getLeafCollector(final LeafReaderContext context) throws IOException { final LeafReader reader = context.reader(); fDocNumericValues = new HashMap<>(); for (String field : NUMERIC_FIELDS) { NumericDocValues docValues = reader.getNumericDocValues(field); if (docValues != null) { fDocNumericValues.put(field, docValues); } } fDocBinaryValues = new HashMap<>(); for (String field : BINARY_FIELDS) { BinaryDocValues docValues = reader.getBinaryDocValues(field); if (docValues != null) { fDocBinaryValues.put(field, docValues); } } return new LeafCollector() { @Override public void setScorer(Scorer scorer) throws IOException { // ignore } @Override public void collect(int docId) throws IOException { addResult(docId); } }; } private void addResult(int docId) { fResult.add(new SearchMatch(fContainer, fElementType, getNumericValue(NDV_OFFSET, docId), getNumericValue(NDV_LENGTH, docId), getNumericValue(NDV_NAME_OFFSET, docId), getNumericValue(NDV_NAME_LENGTH, docId), getNumericValue(NDV_FLAGS, docId), getStringValue(BDV_ELEMENT_NAME, docId), getStringValue(BDV_PATH, docId), getStringValue(BDV_PARENT, docId), getStringValue(BDV_QUALIFIER, docId), getStringValue(BDV_DOC, docId), getStringValue(BDV_METADATA, docId))); } private int getNumericValue(String field, int docId) { NumericDocValues docValues = fDocNumericValues.get(field); if (docValues != null) { return (int) docValues.get(docId); } return 0; } private String getStringValue(String field, int docId) { BinaryDocValues docValues = fDocBinaryValues.get(field); if (docValues != null) { BytesRef bytesRef = docValues.get(docId); if (bytesRef.length > 0) return bytesRef.utf8ToString(); } return null; } } @Override public void search(int elementType, String qualifier, String elementName, int trueFlags, int falseFlags, int limit, SearchFor searchFor, MatchRule matchRule, IDLTKSearchScope scope, ISearchRequestor requestor, IProgressMonitor monitor) { search(elementType, qualifier, elementName, null, trueFlags, falseFlags, limit, searchFor, matchRule, scope, requestor, monitor); } @Override public void search(int elementType, String qualifier, String elementName, String parent, int trueFlags, int falseFlags, int limit, SearchFor searchFor, MatchRule matchRule, IDLTKSearchScope scope, ISearchRequestor requestor, IProgressMonitor monitor) { boolean searchForDecls = searchFor == SearchFor.DECLARATIONS || searchFor == SearchFor.ALL_OCCURRENCES; boolean searchForRefs = searchFor == SearchFor.REFERENCES || searchFor == SearchFor.ALL_OCCURRENCES; if (searchForRefs) { doSearch(elementType, qualifier, elementName, parent, trueFlags, falseFlags, limit, true, matchRule, scope, requestor, monitor); } if (searchForDecls) { doSearch(elementType, qualifier, elementName, parent, trueFlags, falseFlags, limit, false, matchRule, scope, requestor, monitor); } } private Query createQuery(final String elementName, final String qualifier, final String parent, final int trueFlags, final int falseFlags, final boolean searchForRefs, MatchRule matchRule, IDLTKSearchScope scope) { BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder(); List<String> scripts = SearchScope.getScripts(scope); if (!scripts.isEmpty()) { BooleanQuery.Builder scriptQueryBuilder = new BooleanQuery.Builder(); for (String script : scripts) { scriptQueryBuilder.add(new TermQuery(new Term(F_PATH, script)), Occur.FILTER); } queryBuilder.add(scriptQueryBuilder.build(), Occur.FILTER); } if (elementName != null && !elementName.isEmpty()) { String elementNameLC = elementName.toLowerCase(); Query nameQuery = null; Term nameCaseInsensitiveTerm = new Term(F_ELEMENT_NAME_LC, elementNameLC); if (matchRule == MatchRule.PREFIX) { nameQuery = new PrefixQuery(nameCaseInsensitiveTerm); } else if (matchRule == MatchRule.EXACT) { nameQuery = new TermQuery(nameCaseInsensitiveTerm); } else if (matchRule == MatchRule.CAMEL_CASE) { nameQuery = new PrefixQuery(new Term(F_CC_NAME, elementName)); } else if (matchRule == MatchRule.PATTERN) { nameQuery = new WildcardQuery(nameCaseInsensitiveTerm); } else { throw new UnsupportedOperationException(); } if (nameQuery != null) { queryBuilder.add(nameQuery, Occur.FILTER); } } if (qualifier != null && !qualifier.isEmpty()) { queryBuilder.add(new TermQuery(new Term(F_QUALIFIER, qualifier)), Occur.FILTER); } if (parent != null && !parent.isEmpty()) { queryBuilder.add(new TermQuery(new Term(F_PARENT, parent)), Occur.FILTER); } if (trueFlags != 0 || falseFlags != 0) { queryBuilder.add(new BitFlagsQuery(trueFlags, falseFlags), Occur.FILTER); } BooleanQuery query = queryBuilder.build(); return query.clauses().isEmpty() ? null : query; } private void doSearch(final int elementType, String qualifier, String elementName, String parent, final int trueFlags, final int falseFlags, int limit, final boolean searchForRefs, MatchRule matchRule, IDLTKSearchScope scope, ISearchRequestor requestor, IProgressMonitor monitor) { Query query = createQuery(elementName, qualifier, parent, trueFlags, falseFlags, searchForRefs, matchRule, scope); IndexSearcher indexSearcher = null; final SearchMatchHandler searchMatchHandler = new SearchMatchHandler( scope, requestor); List<SearchMatch> results = new ArrayList<>(); for (String container : SearchScope.getContainers(scope)) { SearcherManager searcherManager = LuceneManager.INSTANCE .findIndexSearcher(container, searchForRefs ? IndexType.REFERENCES : IndexType.DECLARATIONS, elementType); try { indexSearcher = searcherManager.acquire(); ResultsCollector collector = new ResultsCollector(container, elementType, results); if (query != null) { indexSearcher.search(query, collector); } else { indexSearcher.search(new MatchAllDocsQuery(), collector); } } catch (IOException e) { Logger.logException(e); } finally { if (indexSearcher != null) { try { searcherManager.release(indexSearcher); } catch (IOException e) { Logger.logException(e); } } } } // Pass results to entity handler for (SearchMatch result : results) { searchMatchHandler.handle(result, searchForRefs); } } }