/** * Copyright (c) 2015 by Brainwy Software Ltda. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.python.pydev.analysis.additionalinfo; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.zip.ZipFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.python.pydev.core.ModulesKey; import org.python.pydev.core.ModulesKeyForZip; import org.python.pydev.core.cache.CompleteIndexKey; import org.python.pydev.core.cache.DiskCache; import org.python.pydev.core.log.Log; import org.python.pydev.editor.codecompletion.revisited.javaintegration.ModulesKeyForJava; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.shared_core.index.IndexApi; import org.python.pydev.shared_core.index.IndexApi.DocumentInfo; import org.python.pydev.shared_core.index.IndexApi.IDocumentsVisitor; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.structure.OrderedMap; import org.python.pydev.shared_core.utils.Timer; import org.python.pydev.shared_ui.utils.AsynchronousProgressMonitorWrapper; import com.python.pydev.analysis.system_info_builder.InterpreterInfoBuilder; public class ReferenceSearchesLucene implements IReferenceSearches { private static final Object lock = new Object(); private static final Map<File, IndexApi> indexDirToApi = new HashMap<File, IndexApi>(); public static void disposeAll() { synchronized (lock) { try { Set<Entry<File, IndexApi>> entrySet = indexDirToApi.entrySet(); for (Entry<File, IndexApi> entry : entrySet) { try { entry.getValue().dispose(); } catch (Exception e) { Log.log(e); } } } finally { indexDirToApi.clear(); } } } private static final boolean DEBUG = false; private WeakReference<AbstractAdditionalDependencyInfo> abstractAdditionalDependencyInfo; private IndexApi indexApi; public ReferenceSearchesLucene(AbstractAdditionalDependencyInfo abstractAdditionalDependencyInfo) { this.abstractAdditionalDependencyInfo = new WeakReference<>(abstractAdditionalDependencyInfo); } @Override public void dispose() { if (indexApi != null) { indexApi = null; } } @Override public synchronized List<ModulesKey> search(IProject project, final OrderedMap<String, Set<String>> fieldNameToValues, IProgressMonitor monitor) throws OperationCanceledException { try { if (!(monitor instanceof AsynchronousProgressMonitorWrapper)) { monitor = new AsynchronousProgressMonitorWrapper(monitor); } return internalSearch(project, fieldNameToValues, monitor); } finally { monitor.done(); } } private final Map<IProject, Long> projectToLastMtime = new HashMap<>(); private synchronized List<ModulesKey> internalSearch(IProject project, final OrderedMap<String, Set<String>> fieldNameToValues, IProgressMonitor monitor) throws OperationCanceledException { final List<ModulesKey> ret = new ArrayList<ModulesKey>(); PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { Log.log("Project :" + project + " does not have Python nature configured."); return ret; } // Make sure that its information is synchronized. AbstractAdditionalDependencyInfo abstractAdditionalDependencyInfo = this.abstractAdditionalDependencyInfo.get(); if (abstractAdditionalDependencyInfo == null) { Log.log("AbstractAdditionalDependencyInfo already collected!"); return ret; } Long lastMtime = projectToLastMtime.get(project); if (lastMtime == null) { lastMtime = 0L; } long currMtime = nature.getMtime(); if (lastMtime != currMtime) { projectToLastMtime.put(project, currMtime); Timer timer = null; if (DEBUG) { System.out.println("Curr mtime: " + currMtime + " last time: " + lastMtime); System.out.println("Start sync: " + project); timer = new Timer(); } new InterpreterInfoBuilder().syncInfoToPythonPath(monitor, nature); if (DEBUG) { timer.printDiff("Sync time"); } } boolean mustCommitChange = false; final String name = "Search modules with token in: " + abstractAdditionalDependencyInfo.getUIRepresentation(); monitor.beginTask(name, 7); monitor.setTaskName(name); DiskCache completeIndex = abstractAdditionalDependencyInfo.completeIndex; // Note: we should be able to deal with entries already deleted! boolean applyAllDeletes = false; if (indexApi == null) { String folderToPersist = completeIndex.getFolderToPersist(); synchronized (lock) { File indexDir = new File(folderToPersist, "lc"); indexApi = indexDirToApi.get(indexDir); if (indexApi == null) { try { indexApi = new IndexApi(indexDir, applyAllDeletes); indexDirToApi.put(indexDir, indexApi); } catch (Exception e) { Log.log(e); return ret; } } } } synchronized (indexApi.getLock()) { final Map<ModulesKey, CompleteIndexKey> indexMap = new HashMap<>(); // Key to CompleteIndexKey (has modified time). IDocumentsVisitor visitor = new IDocumentsVisitor() { @Override public void visit(DocumentInfo documentInfo) { ModulesKey keyFromIO = ModulesKey.fromIO(documentInfo.get(FIELD_MODULES_KEY_IO)); String modifiedTime = documentInfo.get(FIELD_MODIFIED_TIME); indexMap.put(keyFromIO, new CompleteIndexKey(keyFromIO, Long.parseLong(modifiedTime))); } }; try { indexApi.visitAllDocs(visitor, FIELD_MODULES_KEY_IO, FIELD_MODIFIED_TIME); } catch (IOException e) { Log.log(e); } incrementAndCheckProgress("Visited current index", monitor); Set<CompleteIndexKey> docsToRemove = new HashSet<>(); Set<CompleteIndexKey> modulesToAdd = new HashSet<>(); Map<File, Set<CompleteIndexKey>> zipModulesToAdd = new HashMap<>(); // Wait for the integrity check before getting the keys! abstractAdditionalDependencyInfo.waitForIntegrityCheck(); final Map<CompleteIndexKey, CompleteIndexKey> currentKeys = completeIndex.keys(); // Step 1: remove entries which were in the index but are already removed // from the modules (or have a different time). for (Entry<ModulesKey, CompleteIndexKey> entryInIndex : indexMap.entrySet()) { CompleteIndexKey indexModule = entryInIndex.getValue(); CompleteIndexKey currentModule = currentKeys.get(indexModule); if (currentModule == null || currentModule.key == null || currentModule.key.file == null) { docsToRemove.add(indexModule); } else { // exists, but we also need to check the modified time boolean changed = currentModule.lastModified != indexModule.lastModified; if (!changed) { ModulesKey keyCurrentModule = currentModule.key; ModulesKey keyIndexModule = indexModule.key; boolean currentIsZip = keyCurrentModule instanceof ModulesKeyForZip; boolean indexIsZip = keyIndexModule instanceof ModulesKeyForZip; changed = currentIsZip != indexIsZip; if (!changed) { changed = !currentModule.key.file.equals(indexModule.key.file); } } if (changed) { // remove and add docsToRemove.add(indexModule); add(modulesToAdd, zipModulesToAdd, currentModule); } } } // --- Progress incrementAndCheckProgress("Updating for removal", monitor); // Step 2: add new entries in current and not in the index for (Entry<CompleteIndexKey, CompleteIndexKey> currentEntry : currentKeys.entrySet()) { CompleteIndexKey completeIndexKey = currentEntry.getValue(); if (!indexMap.containsKey(completeIndexKey.key)) { ModulesKey modulesKey = completeIndexKey.key; if (modulesKey instanceof ModulesKeyForJava || modulesKey.file == null || !modulesKey.file.isFile()) { //ignore this one (we can't do anything with it). continue; } if (modulesKey instanceof ModulesKeyForZip) { ModulesKeyForZip modulesKeyForZip = (ModulesKeyForZip) modulesKey; if (!modulesKeyForZip.isFile) { continue; // Ignore folders in zips (happens for jython folders which may not have an __init__.py) } } add(modulesToAdd, zipModulesToAdd, completeIndexKey); } } // --- Progress incrementAndCheckProgress("Updating for addition", monitor); Map<String, Collection<String>> fieldToValuesToRemove = new HashMap<>(); Collection<String> lstToRemove = new ArrayList<>(docsToRemove.size()); FastStringBuffer tempBuf = new FastStringBuffer(); for (Iterator<CompleteIndexKey> it = docsToRemove.iterator(); it.hasNext();) { it.next().key.toIO(tempBuf.clear()); lstToRemove.add(tempBuf.toString()); } incrementAndCheckProgress("Removing outdated entries", monitor); if (lstToRemove.size() > 0) { fieldToValuesToRemove.put(FIELD_MODULES_KEY_IO, lstToRemove); try { mustCommitChange = true; if (DEBUG) { System.out.println("Removing: " + fieldToValuesToRemove); } indexApi.removeDocs(fieldToValuesToRemove); } catch (IOException e) { Log.log(e); } } incrementAndCheckProgress("Indexing new entries", monitor); if (modulesToAdd.size() > 0) { mustCommitChange = true; for (CompleteIndexKey key : modulesToAdd) { File f = key.key.file; if (f.exists()) { if (DEBUG) { System.out.println("Indexing: " + f); } try (BufferedReader reader = new BufferedReader(new FileReader(f));) { indexApi.index(createFieldsToIndex(key, tempBuf), reader, FIELD_CONTENTS); } catch (Exception e) { Log.log(e); } } } } Set<Entry<File, Set<CompleteIndexKey>>> entrySet = zipModulesToAdd.entrySet(); for (Entry<File, Set<CompleteIndexKey>> entry : entrySet) { File f = entry.getKey(); if (f.exists()) { try (ZipFile zipFile = new ZipFile(f, ZipFile.OPEN_READ);) { Set<CompleteIndexKey> value = entry.getValue(); for (CompleteIndexKey completeIndexKey2 : value) { ModulesKeyForZip forZip = (ModulesKeyForZip) completeIndexKey2.key; try (InputStream inputStream = zipFile .getInputStream(zipFile.getEntry(forZip.zipModulePath));) { InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); mustCommitChange = true; if (DEBUG) { System.out.println("Indexing: " + completeIndexKey2); } indexApi.index(createFieldsToIndex(completeIndexKey2, tempBuf), reader, FIELD_CONTENTS); } } } catch (Exception e) { Log.log(e); } } } incrementAndCheckProgress("Committing result", monitor); if (mustCommitChange) { if (DEBUG) { System.out.println("Commit result"); } try { indexApi.commit(); } catch (IOException e) { Log.log(e); } } // Ok, things should be in-place at this point... let's actually do the search now incrementAndCheckProgress("Searching index", monitor); try { if (DEBUG) { System.out.println("Searching: " + fieldNameToValues); } visitor = new IDocumentsVisitor() { @Override public void visit(DocumentInfo documentInfo) { try { String modKey = documentInfo.get(FIELD_MODULES_KEY_IO); String modTime = documentInfo.get(FIELD_MODIFIED_TIME); if (modKey != null && modTime != null) { ModulesKey fromIO = ModulesKey.fromIO(modKey); CompleteIndexKey existing = currentKeys.get(new CompleteIndexKey(fromIO)); // Deal with deleted entries still hanging around. if (existing != null && existing.lastModified == Long.parseLong(modTime)) { // Ok, we have a match! ret.add(existing.key); } } } catch (Exception e) { Log.log(e); } } }; indexApi.searchWildcard(fieldNameToValues, applyAllDeletes, visitor, null, FIELD_MODULES_KEY_IO, FIELD_MODIFIED_TIME); } catch (Exception e) { Log.log(e); } } return ret; } private void incrementAndCheckProgress(String msg, IProgressMonitor monitor) throws OperationCanceledException { // monitor.setTaskName(msg); monitor.worked(1); if (monitor.isCanceled()) { throw new OperationCanceledException(); } } public Map<String, String> createFieldsToIndex(CompleteIndexKey key, FastStringBuffer buf) { key.key.toIO(buf.clear()); Map<String, String> fieldsToIndex = new HashMap<>(); fieldsToIndex.put(FIELD_MODULES_KEY_IO, buf.toString()); fieldsToIndex.put(FIELD_MODULE_NAME, key.key.name); fieldsToIndex.put(FIELD_MODIFIED_TIME, String.valueOf(key.lastModified)); return fieldsToIndex; } public void add(Set<CompleteIndexKey> modulesToAdd, Map<File, Set<CompleteIndexKey>> zipModulesToAdd, CompleteIndexKey currentModule) { if (currentModule.key instanceof ModulesKeyForZip) { Set<CompleteIndexKey> set = zipModulesToAdd.get(currentModule.key.file); if (set == null) { set = new HashSet<>(); zipModulesToAdd.put(currentModule.key.file, set); } set.add(currentModule); } else { modulesToAdd.add(currentModule); } } }