/** * Copyright (c) 2005-2011 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.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; 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.docutils.StringUtils; import org.python.pydev.core.log.Log; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.ui.filetypes.FileTypesPreferencesPage; import com.aptana.shared_core.io.FileUtils; import com.aptana.shared_core.string.FastStringBuffer; /** * @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; } public void setProject(IProject project, IPythonNature nature) { this.fProject = project; this.fNature = (PythonNature) nature; } 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. */ 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(); } private static volatile long doFullSynchAt = -1; private static final Map<String, Long> directMembersChecked = new HashMap<String, Long>(); /** * @return the project pythonpath with complete paths in the filesystem. */ 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 = getProjectSourcePath(true); if (addExternal) { external = getProjectExternalSourcePath(true); } 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(); IWorkspaceRoot root = null; ResourcesPlugin resourcesPlugin = ResourcesPlugin.getPlugin(); for (String currentPath : strings) { if (currentPath.trim().length() > 0) { IPath p = new Path(currentPath); if (resourcesPlugin == null) { //in tests buf.append(currentPath); buf.append("|"); continue; } if (root == null) { root = ResourcesPlugin.getWorkspace().getRoot(); } if (p.segmentCount() < 1) { Log.log("Found no segment in: " + currentPath + " for: " + project); continue; //No segment? Really weird! } //try to get relative to the workspace IContainer container = null; IResource r = null; try { r = root.findMember(p); } catch (Exception e) { Log.log(e); } if (!(r instanceof IContainer) && !(r instanceof IFile)) { //If we didn't find the file, let's try to sync things, as this can happen if the workspace //is still not properly synchronized. String firstSegment = p.segment(0); IResource firstSegmentResource = root.findMember(firstSegment); if (!(firstSegmentResource instanceof IContainer) && !(firstSegmentResource instanceof IFile)) { //we cannot even get the 1st part... let's do sync long currentTimeMillis = System.currentTimeMillis(); if (doFullSynchAt == -1 || currentTimeMillis > doFullSynchAt) { doFullSynchAt = currentTimeMillis + (60 * 2 * 1000); //do a full synch at most once every 2 minutes try { root.refreshLocal(p.segmentCount() + 1, null); } catch (CoreException e) { //ignore } } } else { Long doSynchAt = directMembersChecked.get(firstSegment); long currentTimeMillis = System.currentTimeMillis(); if (doSynchAt == null || currentTimeMillis > doFullSynchAt) { directMembersChecked.put(firstSegment, currentTimeMillis + (60 * 2 * 1000)); //OK, we can get to the 1st segment, so, let's do a refresh just from that point on, not in the whole workspace... try { firstSegmentResource.refreshLocal(p.segmentCount(), null); } catch (CoreException e) { //ignore } } } //Now, try to get it knowing that it's properly synched (it may still not be there, but at least we tried it) try { r = root.findMember(p); } catch (Exception e) { Log.log(e); } } if (r instanceof IContainer) { container = (IContainer) r; buf.append(FileUtils.getFileAbsolutePath(container.getLocation().toFile())); buf.append("|"); } else if (r instanceof IFile) { //zip/jar/egg file String extension = r.getFileExtension(); if (extension == null || FileTypesPreferencesPage.isValidZipFile("." + extension) == false) { Log.log("Error: the path " + currentPath + " is a file but is not a recognized zip file."); } else { buf.append(FileUtils.getFileAbsolutePath(r.getLocation().toFile())); buf.append("|"); } } else { //We're now always making sure that it's all synchronized, so, if we got here, it really doesn't exist (let's warn about it) //Not in workspace?... maybe it was removed, so, let the user know about it (and still add it to the pythonpath as is) 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); //No good: try to get it relative to the project String curr = currentPath; IPath path = new Path(curr.trim()); if (project.getFullPath().isPrefixOf(path)) { path = path.removeFirstSegments(1); if (FileTypesPreferencesPage.isValidZipFile(curr)) { r = project.getFile(path); } else { //get it relative to the project r = project.getFolder(path); } if (r != null) { buf.append(FileUtils.getFileAbsolutePath(r.getLocation().toFile())); buf.append("|"); continue; //Don't go on to append it relative to the workspace root. } } //Nothing worked: force it to be relative to the workspace. IPath rootLocation = root.getRawLocation(); //Note that this'll be cached for later use. buf.append(FileUtils.getFileAbsolutePath(rootLocation.append(currentPath.trim()).toFile())); buf.append("|"); } } } if (external == null) { external = ""; } return buf.append("|").append(external).append("|").append(contributed).toString(); } /** * 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(); } public void setProjectSourcePath(String newSourcePath) throws CoreException { PythonNature nature = fNature; if (nature != null) { nature.getStore().setPathProperty(PythonPathNature.getProjectSourcePathQualifiedName(), newSourcePath); } } public void setProjectExternalSourcePath(String newExternalSourcePath) throws CoreException { PythonNature nature = fNature; if (nature != null) { nature.getStore().setPathProperty(PythonPathNature.getProjectExternalSourcePathQualifiedName(), newExternalSourcePath); } } public void setVariableSubstitution(Map<String, String> variableSubstitution) throws CoreException { PythonNature nature = fNature; if (nature != null) { nature.getStore().setMapProperty(PythonPathNature.getProjectVariableSubstitutionQualifiedName(), variableSubstitution); } } public void clearCaches() { doFullSynchAt = -1; directMembersChecked.clear(); } 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, '|')); } public String getProjectSourcePath(boolean replace) throws CoreException { String projectSourcePath; boolean restore = false; IProject project = fProject; PythonNature nature = fNature; if (project == null || nature == null) { return ""; } projectSourcePath = nature.getStore().getPathProperty(PythonPathNature.getProjectSourcePathQualifiedName()); if (projectSourcePath == null) { //has not been set return ""; } //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(); } } return trimAndReplaceVariablesIfNeeded(replace, projectSourcePath, nature); } /** * Replaces the variables if needed. */ private String trimAndReplaceVariablesIfNeeded(boolean replace, String projectSourcePath, PythonNature nature) throws CoreException { String ret = StringUtils.leftAndRightTrim(projectSourcePath, '|'); if (replace) { StringSubstitution substitution = new StringSubstitution(nature); ret = substitution.performPythonpathStringSubstitution(ret); } return ret; } public String getProjectExternalSourcePath(boolean replace) 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 = ""; } return trimAndReplaceVariablesIfNeeded(replace, extPath, nature); } public List<String> getProjectExternalSourcePathAsList(boolean replaceVariables) throws CoreException { String projectExternalSourcePath = getProjectExternalSourcePath(replaceVariables); List<String> externalPaths = StringUtils.splitAndRemoveEmptyTrimmed(projectExternalSourcePath, '|'); return externalPaths; } public Map<String, String> getVariableSubstitution() throws CoreException, MisconfigurationException, PythonNatureWithoutProjectException { return getVariableSubstitution(true); } /** * Returns the variables in the python nature and in the interpreter. */ 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; } }