/** * 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 Jun 2, 2005 * * @author Fabio Zadrozny */ package org.python.pydev.plugin.nature; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; 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.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.ICodeCompletionASTManager; import org.python.pydev.core.IInterpreterInfo; import org.python.pydev.core.IInterpreterManager; import org.python.pydev.core.IModulesManager; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.IPythonPathNature; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.PropertiesHelper; import org.python.pydev.core.PythonNatureWithoutProjectException; import org.python.pydev.core.docutils.StringSubstitution; import org.python.pydev.core.log.Log; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.shared_core.SharedCorePlugin; 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; /** * @author Fabio Zadrozny */ public class PythonPathNature implements IPythonPathNature { private volatile IProject fProject; private volatile PythonNature fNature; /** * This is the property that has the python path - associated with the project. */ private static QualifiedName projectSourcePathQualifiedName = null; static QualifiedName getProjectSourcePathQualifiedName() { if (projectSourcePathQualifiedName == null) { projectSourcePathQualifiedName = new QualifiedName(PydevPlugin.getPluginID(), "PROJECT_SOURCE_PATH"); } return projectSourcePathQualifiedName; } /** * This is the property that has the external python path - associated with the project. */ private static QualifiedName projectExternalSourcePathQualifiedName = null; static QualifiedName getProjectExternalSourcePathQualifiedName() { if (projectExternalSourcePathQualifiedName == null) { projectExternalSourcePathQualifiedName = new QualifiedName(PydevPlugin.getPluginID(), "PROJECT_EXTERNAL_SOURCE_PATH"); } return projectExternalSourcePathQualifiedName; } /** * This is the property that has the external python path - associated with the project. */ private static QualifiedName projectVariableSubstitutionQualifiedName = null; static QualifiedName getProjectVariableSubstitutionQualifiedName() { if (projectVariableSubstitutionQualifiedName == null) { projectVariableSubstitutionQualifiedName = new QualifiedName(PydevPlugin.getPluginID(), "PROJECT_VARIABLE_SUBSTITUTION"); } return projectVariableSubstitutionQualifiedName; } @Override public void setProject(IProject project, IPythonNature nature) { this.fProject = project; this.fNature = (PythonNature) nature; } @Override public IPythonNature getNature() { return this.fNature; } private boolean waited = false; /** * Returns a list of paths with the complete pythonpath for this nature. * * This includes the pythonpath for the project, all the referenced projects and the * system. */ @Override public List<String> getCompleteProjectPythonPath(IInterpreterInfo interpreter, IInterpreterManager manager) { IModulesManager projectModulesManager = getProjectModulesManager(); if (projectModulesManager == null) { if (!waited) { waited = true; for (int i = 0; i < 10 && projectModulesManager == null; i++) { //We may get into a race condition, so, try to see if we can get it. try { Thread.sleep(100); } catch (InterruptedException e) { //OK } projectModulesManager = getProjectModulesManager(); } } } if (projectModulesManager == null) { return null; } return projectModulesManager.getCompletePythonPath(interpreter, manager); } private IModulesManager getProjectModulesManager() { IPythonNature nature = fNature; if (nature == null) { return null; } ICodeCompletionASTManager astManager = nature.getAstManager(); if (astManager == null) { // AST manager might not be yet available // Code completion job is scheduled to be run return null; } return astManager.getModulesManager(); } /** * @return the project pythonpath with complete paths in the filesystem. */ @Override public String getOnlyProjectPythonPathStr(boolean addExternal) throws CoreException { String source = null; String external = null; String contributed = null; IProject project = fProject; PythonNature nature = fNature; if (project == null || nature == null) { return ""; } //Substitute with variables! StringSubstitution stringSubstitution = new StringSubstitution(nature); source = (String) getProjectSourcePath(true, stringSubstitution, RETURN_STRING_WITH_SEPARATOR); if (addExternal) { external = getProjectExternalSourcePath(true, stringSubstitution); } contributed = stringSubstitution.performPythonpathStringSubstitution(getContributedSourcePath(project)); if (source == null) { source = ""; } //we have to work on this one to resolve to full files, as what is stored is the position //relative to the project location List<String> strings = StringUtils.splitAndRemoveEmptyTrimmed(source, '|'); FastStringBuffer buf = new FastStringBuffer(); for (String currentPath : strings) { if (currentPath.trim().length() > 0) { IPath p = new Path(currentPath); if (SharedCorePlugin.inTestMode()) { //in tests buf.append(currentPath); buf.append("|"); continue; } boolean found = false; p = p.removeFirstSegments(1); //The first segment should always be the project (historically it's this way, but having it relative would be nicer!?!). IResource r = project.findMember(p); if (r == null) { r = project.getFolder(p); } if (r != null) { IPath location = r.getLocation(); if (location != null) { found = true; buf.append(FileUtils.getFileAbsolutePath(location.toFile())); buf.append("|"); } } if (!found) { Log.log(IStatus.WARNING, "Unable to find the path " + currentPath + " in the project were it's \n" + "added as a source folder for pydev (project: " + project.getName() + ") member:" + r, null); } } } if (external == null) { external = ""; } return buf.append("|").append(external).append("|").append(contributed).toString(); } /** * Similar to the getOnlyProjectPythonPathStr method above but only for source files (not contributed nor external) * and return IResources (zip files or folders). */ @Override public Set<IResource> getProjectSourcePathFolderSet() throws CoreException { String source = null; IProject project = fProject; PythonNature nature = fNature; Set<IResource> ret = new HashSet<>(); if (project == null || nature == null) { return ret; } //Substitute with variables! StringSubstitution stringSubstitution = new StringSubstitution(nature); source = (String) getProjectSourcePath(true, stringSubstitution, RETURN_STRING_WITH_SEPARATOR); if (source == null) { return ret; } //we have to work on this one to resolve to full files, as what is stored is the position //relative to the project location List<String> strings = StringUtils.splitAndRemoveEmptyTrimmed(source, '|'); for (String currentPath : strings) { if (currentPath.trim().length() > 0) { IPath p = new Path(currentPath); p = p.removeFirstSegments(1); //The first segment should always be the project (historically it's this way, but having it relative would be nicer!?!). IResource r = project.findMember(p); if (r == null) { r = project.getFolder(p); } if (r != null && r.exists()) { ret.add(r); } } } return ret; } /** * Gets the source path contributed by plugins. * * See: http://sourceforge.net/tracker/index.php?func=detail&aid=1988084&group_id=85796&atid=577329 * * @throws CoreException */ @SuppressWarnings("unchecked") private String getContributedSourcePath(IProject project) throws CoreException { FastStringBuffer buff = new FastStringBuffer(); List<IPythonPathContributor> contributors = ExtensionHelper .getParticipants("org.python.pydev.pydev_pythonpath_contrib"); for (IPythonPathContributor contributor : contributors) { String additionalPythonPath = contributor.getAdditionalPythonPath(project); if (additionalPythonPath != null && additionalPythonPath.trim().length() > 0) { if (buff.length() > 0) { buff.append("|"); } buff.append(additionalPythonPath.trim()); } } return buff.toString(); } @Override public void setProjectSourcePath(String newSourcePath) throws CoreException { PythonNature nature = fNature; if (nature != null) { nature.getStore().setPathProperty(PythonPathNature.getProjectSourcePathQualifiedName(), newSourcePath); } } @Override public void setProjectExternalSourcePath(String newExternalSourcePath) throws CoreException { PythonNature nature = fNature; if (nature != null) { nature.getStore().setPathProperty(PythonPathNature.getProjectExternalSourcePathQualifiedName(), newExternalSourcePath); } } @Override public void setVariableSubstitution(Map<String, String> variableSubstitution) throws CoreException { PythonNature nature = fNature; if (nature != null) { nature.getStore().setMapProperty(PythonPathNature.getProjectVariableSubstitutionQualifiedName(), variableSubstitution); } } @Override public void clearCaches() { } @Override public Set<String> getProjectSourcePathSet(boolean replace) throws CoreException { String projectSourcePath; PythonNature nature = fNature; if (nature == null) { return new HashSet<String>(); } projectSourcePath = getProjectSourcePath(replace); return new HashSet<String>(StringUtils.splitAndRemoveEmptyTrimmed(projectSourcePath, '|')); } @Override public String getProjectSourcePath(boolean replace) throws CoreException { return (String) getProjectSourcePath(replace, null, RETURN_STRING_WITH_SEPARATOR); } @Override @SuppressWarnings("unchecked") public OrderedMap<String, String> getProjectSourcePathResolvedToUnresolvedMap() throws CoreException { return (OrderedMap<String, String>) getProjectSourcePath(true, null, RETURN_MAP_RESOLVED_TO_UNRESOLVED); } private static final int RETURN_STRING_WITH_SEPARATOR = 1; private static final int RETURN_MAP_RESOLVED_TO_UNRESOLVED = 2; /** * Function which can take care of getting the paths just for the project (i.e.: without external * source folders). * * @param replace used only if returnType == RETURN_STRING_WITH_SEPARATOR. * * @param substitution the object which will do the string substitutions (only internally used as an optimization as * creating the instance may be expensive, so, if some other place already creates it, it can be passed along). * * @param returnType if RETURN_STRING_WITH_SEPARATOR returns a string using '|' as the separator. * If RETURN_MAP_RESOLVED_TO_UNRESOLVED returns a map which points from the paths resolved to the maps unresolved. */ private Object getProjectSourcePath(boolean replace, StringSubstitution substitution, int returnType) throws CoreException { String projectSourcePath; boolean restore = false; IProject project = fProject; PythonNature nature = fNature; if (project == null || nature == null) { if (returnType == RETURN_STRING_WITH_SEPARATOR) { return ""; } else if (returnType == RETURN_MAP_RESOLVED_TO_UNRESOLVED) { return new OrderedMap<String, String>(); } else { throw new AssertionError("Unexpected return: " + returnType); } } projectSourcePath = nature.getStore().getPathProperty(PythonPathNature.getProjectSourcePathQualifiedName()); if (projectSourcePath == null) { //has not been set if (returnType == RETURN_STRING_WITH_SEPARATOR) { return ""; } else if (returnType == RETURN_MAP_RESOLVED_TO_UNRESOLVED) { return new OrderedMap<String, String>(); } else { throw new AssertionError("Unexpected return: " + returnType); } } if (replace && substitution == null) { substitution = new StringSubstitution(fNature); } //we have to validate it, because as we store the values relative to the workspace, and not to the //project, the path may become invalid (in which case we have to make it compatible again). StringBuffer buffer = new StringBuffer(); List<String> paths = StringUtils.splitAndRemoveEmptyTrimmed(projectSourcePath, '|'); IPath projectPath = project.getFullPath(); for (String path : paths) { if (path.trim().length() > 0) { if (path.indexOf("${") != -1) { //Account for the string substitution. buffer.append(path); } else { IPath p = new Path(path); if (p.isEmpty()) { continue; //go to the next... } if (projectPath != null && !projectPath.isPrefixOf(p)) { p = p.removeFirstSegments(1); p = projectPath.append(p); restore = true; } buffer.append(p.toString()); } buffer.append("|"); } } //it was wrong and has just been fixed if (restore) { projectSourcePath = buffer.toString(); setProjectSourcePath(projectSourcePath); if (nature != null) { //yeap, everything has to be done from scratch, as all the filesystem paths have just //been turned to dust! nature.rebuildPath(); } } if (returnType == RETURN_STRING_WITH_SEPARATOR) { return trimAndReplaceVariablesIfNeeded(replace, projectSourcePath, nature, substitution); } else if (returnType == RETURN_MAP_RESOLVED_TO_UNRESOLVED) { String ret = StringUtils.leftAndRightTrim(projectSourcePath, '|'); OrderedMap<String, String> map = new OrderedMap<String, String>(); List<String> unresolvedVars = StringUtils.splitAndRemoveEmptyTrimmed(ret, '|'); //Always resolves here! List<String> resolved = StringUtils.splitAndRemoveEmptyTrimmed( substitution.performPythonpathStringSubstitution(ret), '|'); int size = unresolvedVars.size(); if (size != resolved.size()) { throw new AssertionError("Error: expected same size from:\n" + unresolvedVars + "\nand\n" + resolved); } for (int i = 0; i < size; i++) { String un = unresolvedVars.get(i); String res = resolved.get(i); map.put(res, un); } return map; } else { throw new AssertionError("Unexpected return: " + returnType); } } /** * Replaces the variables if needed. */ private String trimAndReplaceVariablesIfNeeded(boolean replace, String projectSourcePath, PythonNature nature, StringSubstitution substitution) throws CoreException { String ret = StringUtils.leftAndRightTrim(projectSourcePath, '|'); if (replace) { ret = substitution.performPythonpathStringSubstitution(ret); } return ret; } @Override public String getProjectExternalSourcePath(boolean replace) throws CoreException { return getProjectExternalSourcePath(replace, null); } private String getProjectExternalSourcePath(boolean replace, StringSubstitution substitution) throws CoreException { String extPath; PythonNature nature = fNature; if (nature == null) { return ""; } //no need to validate because those are always 'file-system' related extPath = nature.getStore().getPathProperty(PythonPathNature.getProjectExternalSourcePathQualifiedName()); if (extPath == null) { extPath = ""; } if (replace && substitution == null) { substitution = new StringSubstitution(fNature); } return trimAndReplaceVariablesIfNeeded(replace, extPath, nature, substitution); } @Override public List<String> getProjectExternalSourcePathAsList(boolean replaceVariables) throws CoreException { String projectExternalSourcePath = getProjectExternalSourcePath(replaceVariables); List<String> externalPaths = StringUtils.splitAndRemoveEmptyTrimmed(projectExternalSourcePath, '|'); return externalPaths; } @Override public Map<String, String> getVariableSubstitution() throws CoreException, MisconfigurationException, PythonNatureWithoutProjectException { return getVariableSubstitution(true); } /** * Returns the variables in the python nature and in the interpreter. */ @Override public Map<String, String> getVariableSubstitution(boolean addInterpreterInfoSubstitutions) throws CoreException, MisconfigurationException, PythonNatureWithoutProjectException { PythonNature nature = this.fNature; if (nature == null) { return new HashMap<String, String>(); } Map<String, String> variableSubstitution; if (addInterpreterInfoSubstitutions) { IInterpreterInfo info = nature.getProjectInterpreter(); Properties stringSubstitutionVariables = info.getStringSubstitutionVariables(); if (stringSubstitutionVariables == null) { variableSubstitution = new HashMap<String, String>(); } else { variableSubstitution = PropertiesHelper.createMapFromProperties(stringSubstitutionVariables); } } else { variableSubstitution = new HashMap<String, String>(); } //no need to validate because those are always 'file-system' related Map<String, String> variableSubstitution2 = nature.getStore().getMapProperty( PythonPathNature.getProjectVariableSubstitutionQualifiedName()); if (variableSubstitution2 != null) { if (variableSubstitution != null) { variableSubstitution.putAll(variableSubstitution2); } else { variableSubstitution = variableSubstitution2; } } //never return null! if (variableSubstitution == null) { variableSubstitution = new HashMap<String, String>(); } return variableSubstitution; } }