/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. 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.
*/
/*
* Created on Nov 12, 2004
*
* @author Fabio Zadrozny
*/
package org.python.pydev.editor.codecompletion.revisited;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.ui.ide.IDE;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.IPythonPathNature;
import org.python.pydev.core.ModulesKey;
import org.python.pydev.core.ModulesKeyForZip;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.codecompletion.IPythonModuleResolver;
import org.python.pydev.editor.codecompletion.revisited.ModulesFoundStructure.ZipContents;
import org.python.pydev.plugin.PyStructureConfigHelpers;
import org.python.pydev.plugin.nature.IPythonPathHelper;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.OrderedMap;
import org.python.pydev.ui.filetypes.FileTypesPreferencesPage;
import org.python.pydev.utils.PyFileListing;
import org.python.pydev.utils.PyFileListing.PyFileInfo;
/**
* This is not a singleton because we may have a different pythonpath for each project (even though
* we have a default one as the original pythonpath).
*
* @author Fabio Zadrozny
*/
public final class PythonPathHelper implements IPythonPathHelper {
/**
* This is a list of Files containing the pythonpath. It's always an immutable list. The instance must
* be changed to change the pythonpath.
*/
private volatile List<String> pythonpath = Collections.unmodifiableList(new ArrayList<String>());
private List<IPath> searchPaths = Collections.unmodifiableList(new ArrayList<IPath>());
/**
* The array of module resolvers from all org.python.pydev.pydev_python_module_resolver extensions.
* Initialized lazily by {@link getPythonModuleResolvers}.
*/
private transient IPythonModuleResolver[] pythonModuleResolvers;
private final Object pythonModuleResolversLock = new Object();
/**
* Returns the default path given from the string.
* @param str
* @return a trimmed string with all the '\' converted to '/'
*/
public static String getDefaultPathStr(String str) {
//this check is no longer done... could result in other problems
// if(acceptPoint == false && str.indexOf(".") == 0){ //cannot start with a dot
// throw new RuntimeException("The pythonpath can only have absolute paths (cannot start with '.', therefore, the path: '"+str+"' is not valid.");
// }
return StringUtils.replaceAllSlashes(str.trim());
}
public PythonPathHelper() {
}
/**
* This method returns all modules that can be obtained from a root File.
* @param monitor keep track of progress (and cancel)
* @return the listing with valid module files considering that root is a root path in the pythonpath.
* May return null if the passed file does not exist or is not a directory (e.g.: zip file)
*/
public static PyFileListing getModulesBelow(File root, IProgressMonitor monitor) {
if (!root.exists()) {
return null;
}
if (root.isDirectory()) {
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isFile()) {
return isValidFileMod(FileUtils.getFileAbsolutePath(pathname));
} else if (pathname.isDirectory()) {
return isFolderWithInit(pathname);
} else {
return false;
}
}
};
return PyFileListing.getPyFilesBelow(root, filter, monitor, true);
}
return null;
}
/**
* @param root the zip file to analyze
* @param monitor the monitor, to keep track of what is happening
* @return a list with the name of the found modules in the jar
*/
protected static ModulesFoundStructure.ZipContents getFromZip(File root, IProgressMonitor monitor) {
String fileName = root.getName();
if (root.isFile() && FileTypesPreferencesPage.isValidZipFile(fileName)) { //ok, it may be a jar file, so let's get its contents and get the available modules
//the major difference from handling jars from regular python files is that we don't have to check for __init__.py files
ModulesFoundStructure.ZipContents zipContents = new ModulesFoundStructure.ZipContents(root);
//by default it's a zip (for python) -- may change if a .class is found.
zipContents.zipContentsType = ZipContents.ZIP_CONTENTS_TYPE_PY_ZIP;
try {
String zipFileName = root.getName();
ZipFile zipFile = new ZipFile(root);
try {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
int i = 0;
FastStringBuffer buffer = new FastStringBuffer();
//ok, now that we have the zip entries, let's map them to modules
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (!entry.isDirectory()) {
if (isValidFileMod(name) || name.endsWith(".class")) {
if (name.endsWith(".class")) {
zipContents.zipContentsType = ZipContents.ZIP_CONTENTS_TYPE_JAR;
}
//it is a valid python file
if (i % 15 == 0) {
if (monitor.isCanceled()) {
return null;
}
buffer.clear();
monitor.setTaskName(buffer.append("Found in ").append(zipFileName)
.append(" module ").append(name).toString());
monitor.worked(1);
}
if (isValidInitFile(name)) {
zipContents.pyInitFilesLowerWithoutExtension.add(StringUtils.stripExtension(name)
.toLowerCase());
}
zipContents.pyFilesLowerToRegular.put(name.toLowerCase(), name);
}
} else { //!isDirectory
zipContents.pyfoldersLower.add(name.toLowerCase());
}
i++;
}
} finally {
zipFile.close();
}
//now, on to actually filling the structure if we have a zip file (just add the ones that are actually under
//the pythonpath)
zipContents.consolidatePythonpathInfo(monitor);
return zipContents;
} catch (Exception e) {
//that's ok, it is probably not a zip file after all...
Log.log(e);
}
}
return null;
}
/**
* @return if the path passed belongs to a valid python source file (checks for the extension)
*/
public static boolean isValidSourceFile(String path) {
return isValidSourceFile(path, FileTypesPreferencesPage.getDottedValidSourceFiles());
}
public static boolean isValidSourceFile(String path, String[] dottedValidSourceFiles) {
int len = dottedValidSourceFiles.length;
for (int i = 0; i < len; i++) {
if (path.endsWith(dottedValidSourceFiles[i])) {
return true;
}
}
if (path.endsWith(".pypredef")) {
return true;
}
return false;
}
/**
* @return whether an IFile is a valid source file given its extension
*/
public static boolean isValidSourceFile(IFile file) {
String ext = file.getFileExtension();
if (ext == null) { // no extension
return false;
}
ext = ext.toLowerCase();
String[] validSourceFiles = FileTypesPreferencesPage.getValidSourceFiles();
int len = validSourceFiles.length;
for (int i = 0; i < len; i++) {
String end = validSourceFiles[i];
if (ext.equals(end)) {
return true;
}
}
if (ext.equals(".pypredef")) {
return true;
}
return false;
}
/**
* @return if the paths maps to a valid python module (depending on its extension).
*/
public static boolean isValidFileMod(String path) {
boolean ret = false;
if (isValidSourceFile(path)) {
ret = true;
} else if (FileTypesPreferencesPage.isValidDll(path)) {
ret = true;
}
return ret;
}
/**
* Resolves an absolute file system location of a module to its name, scoped to the paths in
* {@link #getPythonpath()}.
*
* @param absoluteModuleLocation the location of the module. Only for directories, or .py, .pyd,
* .dll, .so, .pyo files.
* @return a dot-separated qualified name of the Python module that the file or folder should
* represent. E.g.: {@code compiler.ast}.
*/
@Override
public String resolveModule(String absoluteModuleLocation, IProject project) {
return resolveModule(absoluteModuleLocation, false, getPythonpath(), project);
}
/**
* Resolves an absolute file system location of a a module to its name, scoped to the paths in
* {@link #getPythonpath()} and in context to a given project.
*
* @param absoluteModuleLocation the location of the module. Only for directories, or .py, .pyd,
* .dll, .so, .pyo files.
* @param requireFileToExist if {@code true}, requires the path to exist on the filesystem.
* @param project the project context in which the module resolution is being performed.
* If resolution is to be done without respect to a project, then {@code null}.
* @return a dot-separated qualified name of the Python module that the file or folder should
* represent. E.g.: {@code compiler.ast}.
*/
public String resolveModule(String absoluteModuleLocation, final boolean requireFileToExist,
IProject project) {
return resolveModule(absoluteModuleLocation, requireFileToExist, getPythonpath(), project);
}
/**
* Resolves an absolute file system location of a module to its name, scoped to the paths in
* the search locations and in context to a given project.
*
* @param absoluteModuleLocation the location of the module. Only for directories, or .py, .pyd,
* .dll, .so, .pyo files.
* @param requireFileToExist if {@code true}, requires the path to exist on the filesystem.
* @param baseLocations the locations relative to which to resolve the Python module.
* @param project the project context in which the module resolution is being performed.
* Can be {@code null} if resolution should to be done without respect to a project.
* @return a dot-separated qualified name of the Python module that the file or folder should
* represent. E.g.: {@code compiler.ast}.
*/
public String resolveModule(String absoluteModuleLocation, final boolean requireFileToExist,
List<String> baseLocations, IProject project) {
IPath modulePath = Path.fromOSString(absoluteModuleLocation);
if (requireFileToExist && !modulePath.toFile().exists()) {
return null;
}
// Try to consult each of the resolvers:
IPythonModuleResolver[] pythonModuleResolvers = getPythonModuleResolvers();
if (pythonModuleResolvers.length > 0) {
List<IPath> convertedBasePaths = new ArrayList<>();
for (String searchPath : baseLocations) {
convertedBasePaths.add(Path.fromOSString(searchPath));
}
for (IPythonModuleResolver resolver : pythonModuleResolvers) {
String resolved = resolver.resolveModule(project, modulePath, convertedBasePaths);
if (resolved == null) {
// The null string represents delegation to the next resolver.
continue;
}
if (resolved.isEmpty()) {
// The empty string represents resolution failure.
return null;
}
return resolved;
}
}
// If all of the resolvers have delegated, then go forward with the default behavior.
absoluteModuleLocation = FileUtils.getFileAbsolutePath(absoluteModuleLocation);
absoluteModuleLocation = getDefaultPathStr(absoluteModuleLocation);
String fullPathWithoutExtension;
if (isValidSourceFile(absoluteModuleLocation) || FileTypesPreferencesPage.isValidDll(absoluteModuleLocation)) {
fullPathWithoutExtension = FullRepIterable.headAndTail(absoluteModuleLocation)[0];
} else {
fullPathWithoutExtension = absoluteModuleLocation;
}
final File moduleFile = new File(absoluteModuleLocation);
boolean isFile = moduleFile.isFile();
//go through our pythonpath and check the beginning
for (String pathEntry : baseLocations) {
String element = getDefaultPathStr(pathEntry);
if (absoluteModuleLocation.startsWith(element)) {
int len = element.length();
String s = absoluteModuleLocation.substring(len);
String sWithoutExtension = fullPathWithoutExtension.substring(len);
if (s.startsWith("/")) {
s = s.substring(1);
}
if (sWithoutExtension.startsWith("/")) {
sWithoutExtension = sWithoutExtension.substring(1);
}
if (!isValidModuleLastPart(sWithoutExtension)) {
continue;
}
s = s.replaceAll("/", ".");
if (s.indexOf(".") != -1) {
File root = new File(element);
if (root.exists() == false) {
continue;
}
final List<String> temp = StringUtils.dotSplit(s);
String[] modulesParts = temp.toArray(new String[temp.size()]);
//this means that more than 1 module is specified, so, in order to get it,
//we have to go and see if all the folders to that module have __init__.py in it...
if (modulesParts.length > 1 && isFile) {
String[] t = Arrays.copyOf(modulesParts, modulesParts.length - 1);
t[t.length - 1] = t[t.length - 1] + "." + modulesParts[modulesParts.length - 1];
modulesParts = t;
}
//here, in modulesParts, we have something like
//["compiler", "ast.py"] - if file
//["pywin","debugger"] - if folder
//
//root starts with the pythonpath folder that starts with the same
//chars as the full path passed in.
boolean isValid = true;
for (int i = 0; i < modulesParts.length && root != null; i++) {
root = new File(FileUtils.getFileAbsolutePath(root) + "/" + modulesParts[i]);
//check if file is in root...
if (isValidFileMod(modulesParts[i])) {
if (root.exists() && root.isFile()) {
break;
}
} else {
//this part is a folder part... check if it is a valid module (has init).
if (isFolderWithInit(root) == false) {
isValid = false;
break;
}
//go on and check the next part.
}
}
if (isValid) {
if (isFile) {
s = stripExtension(s);
} else if (moduleFile.exists() == false) {
//ok, it does not exist, so isFile will not work, let's just check if it is
//a valid module (ends with .py or .pyw) and if it is, strip the extension
if (isValidFileMod(s)) {
s = stripExtension(s);
}
}
return s;
}
} else {
//simple part, we don't have to go into subfolders to check validity...
if (!isFile && moduleFile.isDirectory() && isFolderWithInit(moduleFile) == false) {
return null;
}
return s;
}
}
}
//ok, it was not found in any existing way, so, if we don't require the file to exist, let's just do some simpler search and get the
//first match (if any)... this is useful if the file we are looking for has just been deleted
if (!requireFileToExist) {
//we have to remove the last part (.py, .pyc, .pyw)
for (String element : baseLocations) {
element = getDefaultPathStr(element);
if (fullPathWithoutExtension.startsWith(element)) {
String s = fullPathWithoutExtension.substring(element.length());
if (s.startsWith("/")) {
s = s.substring(1);
}
if (!isValidModuleLastPart(s)) {
continue;
}
s = s.replaceAll("/", ".");
return s;
}
}
}
return null;
}
/**
* Note that this function is not completely safe...beware when using it.
* @param s
* @return
*/
public static String stripExtension(String s) {
if (s != null) {
return StringUtils.stripExtension(s);
}
return null;
}
/**
* @param root this is the folder we're checking
* @return true if it is a folder with an __init__ python file
*/
public static boolean isFolderWithInit(File root) {
return getFolderInit(root) != null;
}
/**
* @param root this is the folder we're checking
* @return true if it is a folder with an __init__ python file
*/
public static File getFolderInit(File root) {
// Checking for existence of a specific file is much faster than listing a directory!
String[] validInitFiles = FileTypesPreferencesPage.getValidInitFiles();
int len = validInitFiles.length;
for (int i = 0; i < len; i++) {
String init = validInitFiles[i];
File f = new File(root, init);
if (f.exists()) {
return f;
}
}
return null;
}
/**
* @param root this is the folder we're checking
* @return true if it is a folder with an __init__ python file
*/
public static IFile getFolderInit(IContainer root) {
// Checking for existence of a specific file is much faster than listing a directory!
String[] validInitFiles = FileTypesPreferencesPage.getValidInitFiles();
int len = validInitFiles.length;
for (int i = 0; i < len; i++) {
String init = validInitFiles[i];
IFile f = root.getFile(new Path(init));
if (f.exists()) {
return f;
}
}
return null;
}
/**
* @param item the file we want to check
* @return true if the file is a valid __init__ file
*/
public static boolean isValidInitFile(String path) {
String[] validInitFiles = FileTypesPreferencesPage.getValidInitFiles();
int len = validInitFiles.length;
for (int i = 0; i < len; i++) {
String init = validInitFiles[i];
if (path.endsWith(init)) {
int index = (path.length() - init.length()) - 1;
if (index >= 0) {
//if the char before exists and is not a separator, it's not a valid
//__init__ file.
char c = (path.charAt(index));
if (c != '/' && c != '\\') {
return false;
}
}
return true;
}
}
return false;
}
/**
* @param s
* @return
*/
public static boolean isValidModuleLastPart(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '-' || c == ' ' || c == '.' || c == '+') {
return false;
}
}
return true;
}
public void setPythonPath(List<String> newPythonpath) {
this.pythonpath = Collections.unmodifiableList(new ArrayList<String>(newPythonpath));
this.fixSearchPaths();
}
/**
* Sets searchPaths (a list of the pythonpath search directories as {@link IPath}s).
*/
private void fixSearchPaths() {
List<String> pathStrings = getPythonpath();
ArrayList<IPath> searchPaths = new ArrayList<>(pathStrings.size());
for (String searchPath : pathStrings) {
searchPaths.add(Path.fromOSString(searchPath));
}
this.searchPaths = Collections.unmodifiableList(searchPaths);
}
/**
* @param string with paths separated by |
* @return
*/
@Override
public void setPythonPath(String string) {
setPythonPath(parsePythonPathFromStr(string, new ArrayList<String>()));
}
/**
* @param string this is the string that has the pythonpath (separated by |)
* @param lPath OUT: this list is filled with the pythonpath (if null an ArrayList is created to fill the pythonpath).
* @return
*/
public static List<String> parsePythonPathFromStr(String string, List<String> lPath) {
if (lPath == null) {
lPath = new ArrayList<String>();
}
String[] strings = string.split("\\|");
for (int i = 0; i < strings.length; i++) {
String defaultPathStr = getDefaultPathStr(strings[i]);
if (defaultPathStr != null && defaultPathStr.trim().length() > 0) {
File file = new File(defaultPathStr);
if (file.exists()) {
//we have to get it with the appropriate cases and in a canonical form
String path = FileUtils.getFileAbsolutePath(file);
lPath.add(path);
} else {
lPath.add(defaultPathStr);
}
}
}
return lPath;
}
/**
* @return a list with the pythonpath, such that each element of the list is a part of
* the pythonpath
* @note returns a list that's not modifiable!
*/
@Override
public List<String> getPythonpath() {
return pythonpath;
}
/**
* Collects the Python modules.
*/
@Override
public ModulesFoundStructure getModulesFoundStructure(IProgressMonitor monitor) {
return getModulesFoundStructure(null, monitor);
}
/**
* Collects the Python modules.
* <p>
* Plugins that extend the {@code org.python.pydev.pydev_python_module_resolver} extension point
* can extend the behavior of this method. If no such extension exists, the default behavior
* is to recursively traverse the directories in the PYTHONPATH.
*
* @param project the project scope, can be {@code null} to represent a system-wide collection.
* @param monitor a project monitor, can be {@code null}.
* @return a {@link ModulesFoundStructure} containing the encountered modules.
*/
public ModulesFoundStructure getModulesFoundStructure(IProject project,
IProgressMonitor monitor) {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
IPythonModuleResolver[] pythonModuleResolvers = getPythonModuleResolvers();
if (pythonModuleResolvers.length > 0) {
List<IPath> searchPaths = this.searchPaths;
for (IPythonModuleResolver finder : pythonModuleResolvers) {
Collection<IPath> modulesAndZips = finder.findAllModules(project, monitor);
if (modulesAndZips == null) {
continue;
}
ModulesFoundStructure modulesFoundStructure = new ModulesFoundStructure();
for (IPath moduleOrZip : modulesAndZips) {
File moduleOrZipFile = moduleOrZip.toFile();
if (FileTypesPreferencesPage.isValidZipFile(moduleOrZip.toOSString())) {
ModulesFoundStructure.ZipContents zipContents = getFromZip(moduleOrZipFile, monitor);
if (zipContents != null) {
modulesFoundStructure.zipContents.add(zipContents);
}
} else {
String qualifiedName = finder.resolveModule(project, moduleOrZip, searchPaths);
if (qualifiedName != null && !qualifiedName.isEmpty()) {
modulesFoundStructure.regularModules.put(moduleOrZipFile, qualifiedName);
}
}
}
return modulesFoundStructure;
}
}
// The default behavior is to recursively traverse the directories in the PYTHONPATH to
// collect all encountered Python modules.
ModulesFoundStructure ret = new ModulesFoundStructure();
List<String> pythonpathList = getPythonpath();
FastStringBuffer tempBuf = new FastStringBuffer();
for (Iterator<String> iter = pythonpathList.iterator(); iter.hasNext();) {
String element = iter.next();
if (monitor.isCanceled()) {
break;
}
//the slow part is getting the files... not much we can do (I think).
File root = new File(element);
PyFileListing below = getModulesBelow(root, monitor);
if (below != null) {
Iterator<PyFileInfo> e1 = below.getFoundPyFileInfos().iterator();
while (e1.hasNext()) {
PyFileInfo pyFileInfo = e1.next();
File file = pyFileInfo.getFile();
String modName = pyFileInfo.getModuleName(tempBuf);
if (isValidModuleLastPart(FullRepIterable.getLastPart(modName))) {
ret.regularModules.put(file, modName);
}
}
} else { //ok, it was null, so, maybe this is not a folder, but zip file with java classes...
ModulesFoundStructure.ZipContents zipContents = getFromZip(root, monitor);
if (zipContents != null) {
ret.zipContents.add(zipContents);
}
}
}
return ret;
}
/**
* @param workspaceMetadataFile
* @throws IOException
*/
public void loadFromFile(File pythonpatHelperFile) throws IOException {
String fileContents = FileUtils.getFileContents(pythonpatHelperFile);
if (fileContents == null || fileContents.trim().length() == 0) {
throw new IOException("No loaded contents from: " + pythonpatHelperFile);
}
setPythonPath(StringUtils.split(fileContents, '\n'));
}
/**
* @param pythonpatHelperFile
*/
public void saveToFile(File pythonpatHelperFile) {
FileUtils.writeStrToFile(StringUtils.join("\n", this.pythonpath),
pythonpatHelperFile);
}
public static boolean canAddAstInfoForSourceModule(ModulesKey key) {
if (key.file != null && key.file.exists()) {
if (PythonPathHelper.isValidSourceFile(key.file.getName())) {
return true;
}
boolean isZipModule = key instanceof ModulesKeyForZip;
if (isZipModule) {
ModulesKeyForZip modulesKeyForZip = (ModulesKeyForZip) key;
if (PythonPathHelper.isValidSourceFile(modulesKeyForZip.zipModulePath)) {
return true;
}
}
}
return false;
}
/**
* @return true if PyEdit.EDITOR_ID is set as the persistent property (only if the file does not have an extension).
*/
public static boolean markAsPyDevFileIfDetected(IFile file) {
String name = file.getName();
if (name == null || name.indexOf('.') != -1) {
return false;
}
String editorID;
try {
editorID = file.getPersistentProperty(IDE.EDITOR_KEY);
if (editorID == null) {
InputStream contents = file.getContents(true);
Reader inputStreamReader = new InputStreamReader(new BufferedInputStream(contents));
if (FileUtils.hasPythonShebang(inputStreamReader)) {
IDE.setDefaultEditor(file, PyEdit.EDITOR_ID);
return true;
}
} else {
return PyEdit.EDITOR_ID.equals(editorID);
}
} catch (Exception e) {
if (file.exists()) {
Log.log(e);
}
}
return false;
}
public static final int OPERATION_MOVE = 1;
public static final int OPERATION_COPY = 2;
public static final int OPERATION_DELETE = 3;
private static OrderedMap<String, String> getResourcePythonPathMap(
Map<IProject, OrderedMap<String, String>> projectSourcePathMapsCache, IResource resource) {
IProject project = resource.getProject();
OrderedMap<String, String> sourceMap = projectSourcePathMapsCache.get(project);
if (sourceMap == null) {
IPythonPathNature pythonPathNature = PythonNature.getPythonPathNature(project);
// Ignore resources that come from a non-Python project.
if (pythonPathNature == null) {
sourceMap = new OrderedMap<String, String>();
} else {
try {
sourceMap = pythonPathNature
.getProjectSourcePathResolvedToUnresolvedMap();
} catch (CoreException e) {
sourceMap = new OrderedMap<String, String>();
Log.log(e);
}
}
projectSourcePathMapsCache.put(project, sourceMap);
}
return sourceMap;
}
/**
* Helper to update the pythonpath when a copy, move or delete operation is done which could affect a source folder
* (so, should work when moving/copying/deleting the parent folder of a source folder for instance).
*
* Note that the destination may be null in a delete operation.
*/
public static void updatePyPath(IResource[] copiedResources, IContainer destination, int operation) {
try {
Map<IProject, OrderedMap<String, String>> projectSourcePathMapsCache = new HashMap<IProject, OrderedMap<String, String>>();
List<String> addToDestProjects = new ArrayList<String>();
//Step 1: remove source folders from the copied projects
HashSet<IProject> changed = new HashSet<IProject>();
for (IResource resource : copiedResources) {
if (!(resource instanceof IFolder)) {
continue;
}
OrderedMap<String, String> sourceMap = PythonPathHelper.getResourcePythonPathMap(
projectSourcePathMapsCache, resource);
Set<String> keySet = sourceMap.keySet();
for (Iterator<String> it = keySet.iterator(); it.hasNext();) {
String next = it.next();
IPath existingInPath = Path.fromPortableString(next);
if (resource.getFullPath().isPrefixOf(existingInPath)) {
if (operation == PythonPathHelper.OPERATION_MOVE
|| operation == PythonPathHelper.OPERATION_DELETE) {
it.remove(); //Remove from that project (but not on copy)
changed.add(resource.getProject());
}
if (operation == PythonPathHelper.OPERATION_COPY
|| operation == PythonPathHelper.OPERATION_MOVE) {
//Add to new project (but not on delete)
String addToNewProjectPath = destination
.getFullPath()
.append(existingInPath
.removeFirstSegments(resource.getFullPath().segmentCount() - 1))
.toPortableString();
addToDestProjects.add(addToNewProjectPath);
}
}
}
}
if (operation != PythonPathHelper.OPERATION_DELETE) {
//Step 2: add source folders to the project it was copied to
OrderedMap<String, String> destSourceMap = PythonPathHelper.getResourcePythonPathMap(
projectSourcePathMapsCache, destination);
// Get the PYTHONPATH of the destination project. It may be modified to include the pasted resources.
IProject destProject = destination.getProject();
for (String addToNewProjectPath : addToDestProjects) {
String destActualPath = PyStructureConfigHelpers.convertToProjectRelativePath(
destProject.getFullPath().toPortableString(),
addToNewProjectPath);
destSourceMap.put(addToNewProjectPath, destActualPath);
changed.add(destProject);
}
}
//Step 3: update the target project
for (IProject project : changed) {
OrderedMap<String, String> sourceMap = PythonPathHelper.getResourcePythonPathMap(
projectSourcePathMapsCache, project);
PythonNature nature = PythonNature.getPythonNature(project);
if (nature == null) {
continue; //don't change non-pydev projects
}
nature.getPythonPathNature().setProjectSourcePath(
StringUtils.join("|", sourceMap.values()));
nature.rebuildPath();
}
} catch (Exception e) {
Log.log(IStatus.ERROR, "Unexpected error setting project properties", e);
}
}
/**
* Returns an array of resolvers. If none can be found, returns an empty array.
*/
private IPythonModuleResolver[] getPythonModuleResolvers() {
// Make common case faster (unsynched).
if (pythonModuleResolvers != null) {
return pythonModuleResolvers;
}
synchronized (pythonModuleResolversLock) {
// Who knows if it was changed in the meanwhile?
if (pythonModuleResolvers != null) {
return pythonModuleResolvers;
}
ArrayList<IPythonModuleResolver> tempPythonModuleResolvers = new ArrayList<>();
@SuppressWarnings("unchecked")
List<Object> resolvers =
ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PYTHON_MODULE_RESOLVER);
for (Object resolver : resolvers) {
if (resolver instanceof IPythonModuleResolver) {
tempPythonModuleResolvers.add((IPythonModuleResolver) resolver);
}
}
pythonModuleResolvers = tempPythonModuleResolvers.toArray(new IPythonModuleResolver[0]);
return pythonModuleResolvers;
}
}
}