/** * 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 Mar 11, 2004 * * @author Fabio Zadrozny * @author atotic */ package org.python.pydev.plugin.nature; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.internal.resources.Project; import org.eclipse.core.internal.resources.ProjectInfo; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IProjectNature; 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.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.part.FileEditorInput; import org.python.pydev.builder.PyDevBuilderPrefPage; 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.IModule; import org.python.pydev.core.IModulesManager; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.IPythonPathNature; import org.python.pydev.core.IToken; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.ProjectMisconfiguredException; import org.python.pydev.core.PythonNatureWithoutProjectException; import org.python.pydev.core.docutils.StringUtils; import org.python.pydev.core.log.Log; import org.python.pydev.editor.codecompletion.revisited.ASTManager; import org.python.pydev.editor.codecompletion.revisited.ModulesManager; import org.python.pydev.editor.codecompletion.revisited.ProjectModulesManager; import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper; import org.python.pydev.navigator.elements.ProjectConfigError; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.ui.interpreters.IInterpreterObserver; import org.python.pydev.utils.JobProgressComunicator; import com.aptana.shared_core.structure.Tuple; /** * PythonNature is currently used as a marker class. * * When python nature is present, project gets extra properties. Project gets assigned python nature when: - a python file is edited - a * python project wizard is created * * */ public class PythonNature extends AbstractPythonNature implements IPythonNature { /** * Contains a list with the natures created. */ private final static List<WeakReference<PythonNature>> createdNatures = new ArrayList<WeakReference<PythonNature>>(); /** * @return the natures that were created. */ public static List<PythonNature> getInitializedPythonNatures() { ArrayList<PythonNature> ret = new ArrayList<PythonNature>(); synchronized (createdNatures) { for (Iterator<WeakReference<PythonNature>> it = createdNatures.iterator(); it.hasNext();) { PythonNature pythonNature = it.next().get(); if (pythonNature == null) { it.remove(); } else if (pythonNature.getProject() != null) { ret.add(pythonNature); } } } return ret; } /** * Constructor * * Adds the nature to the list of created natures. */ public PythonNature() { synchronized (createdNatures) { createdNatures.add(new WeakReference<PythonNature>(this)); } } private final Object initLock = new Object(); /** * This is the job that is used to rebuild the python nature modules. * * @author Fabio */ protected class RebuildPythonNatureModules extends Job { protected RebuildPythonNatureModules() { super("Python Nature: rebuilding modules"); } @SuppressWarnings("unchecked") protected IStatus run(IProgressMonitor monitor) { String paths; try { paths = pythonPathNature.getOnlyProjectPythonPathStr(true); } catch (CoreException e1) { Log.log(e1); return Status.OK_STATUS; } try { if (monitor.isCanceled()) { return Status.OK_STATUS; } final JobProgressComunicator jobProgressComunicator = new JobProgressComunicator(monitor, "Rebuilding modules", IProgressMonitor.UNKNOWN, this); final PythonNature nature = PythonNature.this; try { ICodeCompletionASTManager tempAstManager = astManager; if (tempAstManager == null) { tempAstManager = new ASTManager(); } if (monitor.isCanceled()) { return Status.OK_STATUS; } synchronized (tempAstManager.getLock()) { astManager = tempAstManager; tempAstManager.setProject(getProject(), nature, false); //it is a new manager, so, remove all deltas //begins task automatically tempAstManager.changePythonPath(paths, project, jobProgressComunicator); if (monitor.isCanceled()) { return Status.OK_STATUS; } saveAstManager(); List<IInterpreterObserver> participants = ExtensionHelper .getParticipants(ExtensionHelper.PYDEV_INTERPRETER_OBSERVER); for (IInterpreterObserver observer : participants) { if (monitor.isCanceled()) { return Status.OK_STATUS; } try { observer.notifyProjectPythonpathRestored(nature, jobProgressComunicator); } catch (Exception e) { //let's keep it safe Log.log(e); } } } } catch (Throwable e) { Log.log(e); } if (monitor.isCanceled()) { return Status.OK_STATUS; } PythonNatureListenersManager.notifyPythonPathRebuilt(project, nature); //end task jobProgressComunicator.done(); } catch (Exception e) { Log.log(e); } return Status.OK_STATUS; } } /** * This is the nature ID */ public static final String PYTHON_NATURE_ID = "org.python.pydev.pythonNature"; /** * Nature ID for projects that are django configured (it's here because the icon managing * needs this information). */ public static final String DJANGO_NATURE_ID = "org.python.pydev.django.djangoNature"; /** * This is the nature name */ public static final String PYTHON_NATURE_NAME = "pythonNature"; /** * Builder id for pydev (code completion todo and others) */ public static final String BUILDER_ID = "org.python.pydev.PyDevBuilder"; /** * Project associated with this nature. */ private IProject project; /** * This is the completions cache for the nature represented by this object (it is associated with a project). */ private ICodeCompletionASTManager astManager; /** * We have to know if it has already been initialized. */ private boolean initialized; /** * Manages pythonpath things */ private final IPythonPathNature pythonPathNature = new PythonPathNature(); /** * Used to actually store settings for the pythonpath */ private final IPythonNatureStore pythonNatureStore = new PythonNatureStore(); /** * constant that stores the name of the python version we are using for the project with this nature */ private static QualifiedName pythonProjectVersion = null; static QualifiedName getPythonProjectVersionQualifiedName() { if (pythonProjectVersion == null) { //we need to do this because the plugin ID may not be known on 'static' time pythonProjectVersion = new QualifiedName(PydevPlugin.getPluginID(), "PYTHON_PROJECT_VERSION"); } return pythonProjectVersion; } /** * constant that stores the name of the python version we are using for the project with this nature */ private static QualifiedName pythonProjectInterpreter = null; static QualifiedName getPythonProjectInterpreterQualifiedName() { if (pythonProjectInterpreter == null) { //we need to do this because the plugin ID may not be known on 'static' time pythonProjectInterpreter = new QualifiedName(PydevPlugin.getPluginID(), "PYTHON_PROJECT_INTERPRETER"); } return pythonProjectInterpreter; } public boolean isResourceInPythonpathProjectSources(IResource resource, boolean addExternal) throws MisconfigurationException, CoreException { String resourceOSString = PydevPlugin.getIResourceOSString(resource); if (resourceOSString == null) { return false; } return isResourceInPythonpathProjectSources(resourceOSString, addExternal); } public boolean isResourceInPythonpathProjectSources(String absPath, boolean addExternal) throws MisconfigurationException, CoreException { return resolveModuleOnlyInProjectSources(absPath, addExternal) != null; } public String resolveModuleOnlyInProjectSources(IResource fileAbsolutePath, boolean addExternal) throws CoreException, MisconfigurationException { String resourceOSString = PydevPlugin.getIResourceOSString(fileAbsolutePath); if (resourceOSString == null) { return null; } return resolveModuleOnlyInProjectSources(resourceOSString, addExternal); } /** * This method is called only when the project has the nature added.. * * @see org.eclipse.core.resources.IProjectNature#configure() */ public void configure() throws CoreException { } /** * @see org.eclipse.core.resources.IProjectNature#deconfigure() */ public void deconfigure() throws CoreException { } /** * Returns the project * * @see org.eclipse.core.resources.IProjectNature#getProject() */ public IProject getProject() { return project; } /** * Sets this nature's project - called from the eclipse platform. * * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject) */ public synchronized void setProject(final IProject project) { getStore().setProject(project); this.project = project; this.pythonPathNature.setProject(project, this); if (project != null) { //call initialize always - let it do the control. init(null, null, null, new NullProgressMonitor(), null, null); } else { this.clearCaches(false); } } public static IPythonNature addNature(IEditorInput element) { if (element instanceof FileEditorInput) { IFile file = (IFile) ((FileEditorInput) element).getAdapter(IFile.class); if (file != null) { try { return PythonNature.addNature(file.getProject(), null, null, null, null, null, null); } catch (CoreException e) { Log.log(e); } } } return null; } /** * Utility routine to remove a PythonNature from a project. */ public static synchronized void removeNature(IProject project, IProgressMonitor monitor) throws CoreException { if (monitor == null) { monitor = new NullProgressMonitor(); } PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { return; } try { //we have to set the nature store to stop listening changes to .pydevproject nature.pythonNatureStore.setProject(null); } catch (Exception e) { Log.log(e); } try { //we have to remove the project from the pythonpath nature too... nature.pythonPathNature.setProject(null, null); } catch (Exception e) { Log.log(e); } //notify listeners that the pythonpath nature is now empty for this project try { PythonNatureListenersManager.notifyPythonPathRebuilt(project, null); } catch (Exception e) { Log.log(e); } try { //actually remove the pydev configurations IResource member = project.findMember(".pydevproject"); if (member != null) { member.delete(true, null); } } catch (CoreException e) { Log.log(e); } //and finally... remove the nature IProjectDescription description = project.getDescription(); List<String> natures = new ArrayList<String>(Arrays.asList(description.getNatureIds())); natures.remove(PYTHON_NATURE_ID); description.setNatureIds(natures.toArray(new String[natures.size()])); project.setDescription(description, monitor); } /** * Lock to access the map below. */ private final static Object mapLock = new Object(); /** * If some project has a value here, we're already in the process of adding a nature to it. */ private final static Map<IProject, Object> mapLockAddNature = new HashMap<IProject, Object>(); /** * Utility routine to add PythonNature to the project * * @param projectPythonpath: @see {@link IPythonPathNature#setProjectSourcePath(String)} */ public static IPythonNature addNature( //Only synchronized internally! IProject project, IProgressMonitor monitor, String version, String projectPythonpath, String externalProjectPythonpath, String projectInterpreter, Map<String, String> variableSubstitution) throws CoreException { if (project == null || !project.isOpen()) { return null; } if (project.hasNature(PYTHON_NATURE_ID)) { //Return if it already has the nature configured. return getPythonNature(project); } boolean alreadyLocked = false; synchronized (mapLock) { if (mapLockAddNature.get(project) == null) { mapLockAddNature.put(project, new Object()); } else { alreadyLocked = true; } } if (alreadyLocked) { //Ok, there's some execution path already adding the nature. Let's simply wait a bit here and return //the nature that's there (this way we avoid any possible deadlock) -- in the worse case, null //will be returned here, but this is a part of the protocol anyways. //Done because of: Deadlock acquiring PythonNature -- at setDescription() //https://sourceforge.net/tracker/?func=detail&aid=3478567&group_id=85796&atid=577329 try { Thread.sleep(50); } catch (InterruptedException e) { //ignore } return getPythonNature(project); } else { IProjectDescription desc = project.getDescription(); if (monitor == null) { monitor = new NullProgressMonitor(); } if (projectInterpreter == null) { projectInterpreter = IPythonNature.DEFAULT_INTERPRETER; } try { //Lock only for the project and add the nature (at this point we know it hasn't been added). String[] natures = desc.getNatureIds(); String[] newNatures = new String[natures.length + 1]; System.arraycopy(natures, 0, newNatures, 0, natures.length); newNatures[natures.length] = PYTHON_NATURE_ID; desc.setNatureIds(newNatures); //add the builder. It is used for pylint, pychecker, code completion, etc. ICommand[] commands = desc.getBuildSpec(); //now, add the builder if it still hasn't been added. if (hasBuilder(commands) == false && PyDevBuilderPrefPage.usePydevBuilders()) { ICommand command = desc.newCommand(); command.setBuilderName(BUILDER_ID); ICommand[] newCommands = new ICommand[commands.length + 1]; System.arraycopy(commands, 0, newCommands, 1, commands.length); newCommands[0] = command; desc.setBuildSpec(newCommands); } project.setDescription(desc, monitor); IProjectNature n = getPythonNature(project); if (n instanceof PythonNature) { PythonNature nature = (PythonNature) n; //call initialize always - let it do the control. nature.init(version, projectPythonpath, externalProjectPythonpath, monitor, projectInterpreter, variableSubstitution); return nature; } } finally { synchronized (mapLock) { mapLockAddNature.remove(project); } } } return null; } /** * Utility to know if the pydev builder is in one of the commands passed. * * @param commands */ private static boolean hasBuilder(ICommand[] commands) { for (int i = 0; i < commands.length; i++) { if (commands[i].getBuilderName().equals(BUILDER_ID)) { return true; } } return false; } /** * Initializes the python nature if it still has not been for this session. * * Actions includes restoring the dump from the code completion cache * @param projectPythonpath this is the project python path to be used (may be null) -- if not null, this nature is being created * @param version this is the version (project type) to be used (may be null) -- if not null, this nature is being created * @param monitor * @param interpreter */ @SuppressWarnings("unchecked") private void init(String version, String projectPythonpath, String externalProjectPythonpath, IProgressMonitor monitor, String interpreter, Map<String, String> variableSubstitution) { //if some information is passed, restore it (even if it was already initialized) boolean updatePaths = version != null || projectPythonpath != null || externalProjectPythonpath != null || variableSubstitution != null || interpreter != null; if (updatePaths) { this.getStore().startInit(); try { if (variableSubstitution != null) { this.getPythonPathNature().setVariableSubstitution(variableSubstitution); } if (projectPythonpath != null) { this.getPythonPathNature().setProjectSourcePath(projectPythonpath); } if (externalProjectPythonpath != null) { this.getPythonPathNature().setProjectExternalSourcePath(externalProjectPythonpath); } if (version != null || interpreter != null) { this.setVersion(version, interpreter); } } catch (CoreException e) { Log.log(e); } finally { this.getStore().endInit(); } } else { //Change: 1.3.10: it could be reloaded more than once... (when it shouldn't) if (astManager != null) { return; //already initialized... } } synchronized (initLock) { if (initialized && !updatePaths) { return; } initialized = true; } if (updatePaths) { //If updating the paths, rebuild and return (don't try to load an existing ast manager //and restore anything already there) rebuildPath(); return; } if (monitor.isCanceled()) { checkPythonPathHelperPathsJob.schedule(500); return; } //Change: 1.3.10: no longer in a Job... should already be called in a job if that's needed. try { File astOutputFile = getAstOutputFile(); if (astOutputFile == null) { Log.log(IStatus.INFO, "Not saving ast manager for: " + this.project + ". No write area available.", null); return; //The project was deleted } astManager = ASTManager.loadFromFile(astOutputFile); if (astManager != null) { synchronized (astManager.getLock()) { astManager.setProject(getProject(), this, true); // this is the project related to it, restore the deltas (we may have some crash) //just a little validation so that we restore the needed info if we did not get the modules if (astManager.getModulesManager().getOnlyDirectModules().length < 15) { astManager = null; } if (astManager != null) { List<IInterpreterObserver> participants = ExtensionHelper .getParticipants(ExtensionHelper.PYDEV_INTERPRETER_OBSERVER); for (IInterpreterObserver observer : participants) { try { observer.notifyNatureRecreated(this, monitor); } catch (Exception e) { //let's not fail because of other plugins Log.log(e); } } } } } } catch (Exception e) { //Log.logInfo("Info: Rebuilding internal caches for: "+this.project, e); astManager = null; } //errors can happen when restoring it if (astManager == null) { try { rebuildPath(); } catch (Exception e) { Log.log(e); } } else { checkPythonPathHelperPathsJob.schedule(500); } } private final Job checkPythonPathHelperPathsJob = new Job("Check restored pythonpath") { @Override protected IStatus run(IProgressMonitor monitor) { try { if (astManager != null) { String pythonpath = pythonPathNature.getOnlyProjectPythonPathStr(true); PythonPathHelper pythonPathHelper = (PythonPathHelper) astManager.getModulesManager() .getPythonPathHelper(); //If it doesn't match, rebuid the pythonpath! if (!new HashSet<String>(PythonPathHelper.parsePythonPathFromStr(pythonpath, null)) .equals(new HashSet<String>(pythonPathHelper.getPythonpath()))) { rebuildPath(); } } } catch (CoreException e) { Log.log(e); } return Status.OK_STATUS; } }; /** * Returns the directory that should store completions. * * @param p * @return */ private File getCompletionsCacheDir(IProject p) { IPath path = p.getWorkingLocation(PydevPlugin.getPluginID()); if (path == null) { //this can happen if the project was removed. return null; } File file = new File(path.toOSString()); return file; } public File getCompletionsCacheDir() { return getCompletionsCacheDir(getProject()); } /** * @return the file where the python path helper should be saved. */ private File getAstOutputFile() { File completionsCacheDir = getCompletionsCacheDir(); if (completionsCacheDir == null) { return null; } return new File(completionsCacheDir, "v1_astmanager"); } /** * Can be called to refresh internal info (or after changing the path in the preferences). * @throws CoreException */ public void rebuildPath() { clearCaches(true); //Note: pythonPathNature.getOnlyProjectPythonPathStr(true); cannot be called at this moment //as it may trigger a refresh, which may trigger a build and could ask for PythonNature.getPythonNature (which //could be the method that ended up calling rebuildPath in the first place, so, it'd deadlock). this.rebuildJob.cancel(); this.rebuildJob.schedule(20L); } private RebuildPythonNatureModules rebuildJob = new RebuildPythonNatureModules(); /** * @return Returns the completionsCache. Note that it can be null. */ public ICodeCompletionASTManager getAstManager() { return astManager; //Change: don't wait if it's still not initialized. } public boolean isOkToUse() { return this.astManager != null && this.pythonPathNature != null; } public void setAstManager(ICodeCompletionASTManager astManager) { this.astManager = astManager; } public IPythonPathNature getPythonPathNature() { return pythonPathNature; } public static IPythonPathNature getPythonPathNature(IProject project) { PythonNature pythonNature = getPythonNature(project); if (pythonNature != null) { return pythonNature.pythonPathNature; } return null; } /** * @return all the python natures available in the workspace (for opened and existing projects) */ public static List<IPythonNature> getAllPythonNatures() { List<IPythonNature> natures = new ArrayList<IPythonNature>(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IProject[] projects = root.getProjects(); for (IProject project : projects) { PythonNature nature = getPythonNature(project); if (nature != null) { natures.add(nature); } } return natures; } public static PythonNature getPythonNature(IResource resource) { if (resource == null) { return null; } return getPythonNature(resource.getProject()); } private static final Object lockGetNature = new Object(); /** * @param project the project we want to know about (if it is null, null is returned) * @return the python nature for a project (or null if it does not exist for the project) * * @note: it's synchronized because more than 1 place could call getPythonNature at the same time and more * than one nature ended up being created from project.getNature(). */ public static PythonNature getPythonNature(IProject project) { if (project != null && project.isOpen()) { try { //Speedup: as this method is called a lot, we just check if the nature is available internally without //any locks, and just lock if it's not (which is needed to avoid a racing condition creating more //than 1 nature). try { if (project instanceof Project) { Project p = (Project) project; ProjectInfo info = (ProjectInfo) p.getResourceInfo(false, false); IProjectNature nature = info.getNature(PYTHON_NATURE_ID); if (nature instanceof PythonNature) { return (PythonNature) nature; } } } catch (Throwable e) { //Shouldn't really happen, but as using internal methods of project, who knows if it may change //from one version to another. Log.log(e); } synchronized (lockGetNature) { IProjectNature n = project.getNature(PYTHON_NATURE_ID); if (n instanceof PythonNature) { return (PythonNature) n; } } } catch (CoreException e) { Log.logInfo(e); } } return null; } /** * Stores the version as a cache (the actual version is set in the xml file). * This is so that we don't have a runtime penalty for it. */ private String versionPropertyCache = null; private String interpreterPropertyCache = null; /** * Returns the Python version of the Project. * * It's a String in the format "python 2.4", as defined by the constants PYTHON_VERSION_XX and * JYTHON_VERSION_XX in IPythonNature. * * @note it might have changed on disk (e.g. a repository update). * @return the python version for the project * @throws CoreException */ public String getVersion() throws CoreException { return getVersionAndError().o1; } private Tuple<String, String> getVersionAndError() throws CoreException { if (project != null) { if (versionPropertyCache == null) { String storeVersion = getStore().getPropertyFromXml(getPythonProjectVersionQualifiedName()); if (storeVersion == null) { //there is no such property set (let's set it to the default) setVersion(getDefaultVersion(), null); //will set the versionPropertyCache too } else { //now, before returning and setting in the cache, let's make sure it's a valid version. if (!IPythonNature.Versions.ALL_VERSIONS_ANY_FLAVOR.contains(storeVersion)) { Log.log("The stored version is invalid (" + storeVersion + "). Setting default."); setVersion(getDefaultVersion(), null); //will set the versionPropertyCache too } else { //Ok, it's correct. versionPropertyCache = storeVersion; } } } } else { String msg = "Trying to get version without project set. Returning default."; Log.log(msg); return new Tuple<String, String>(getDefaultVersion(), msg); } if (versionPropertyCache == null) { String msg = "The cached version is null. Returning default."; Log.log(msg); return new Tuple<String, String>(getDefaultVersion(), msg); } else if (!IPythonNature.Versions.ALL_VERSIONS_ANY_FLAVOR.contains(versionPropertyCache)) { String msg = "The cached version (" + versionPropertyCache + ") is invalid. Returning default."; Log.log(msg); return new Tuple<String, String>(getDefaultVersion(), msg); } return new Tuple<String, String>(versionPropertyCache, null); } /** * @param version: the project version given the constants PYTHON_VERSION_XX and * JYTHON_VERSION_XX in IPythonNature. If null, nothing is done for the version. * * @param interpreter the interpreter to be set if null, nothing is done to the interpreter. * * @throws CoreException */ public void setVersion(String version, String interpreter) throws CoreException { clearCaches(false); if (version != null) { this.versionPropertyCache = version; } if (interpreter != null) { this.interpreterPropertyCache = interpreter; } if (project != null) { boolean notify = false; if (version != null) { IPythonNatureStore store = getStore(); QualifiedName pythonProjectVersionQualifiedName = getPythonProjectVersionQualifiedName(); String current = store.getPropertyFromXml(pythonProjectVersionQualifiedName); if (current == null || !current.equals(version)) { store.setPropertyToXml(pythonProjectVersionQualifiedName, version, true); notify = true; } } if (interpreter != null) { IPythonNatureStore store = getStore(); QualifiedName pythonProjectInterpreterQualifiedName = getPythonProjectInterpreterQualifiedName(); String current = store.getPropertyFromXml(pythonProjectInterpreterQualifiedName); if (current == null || !current.equals(interpreter)) { store.setPropertyToXml(pythonProjectInterpreterQualifiedName, interpreter, true); notify = true; } } if (notify) { PythonNatureListenersManager.notifyPythonPathRebuilt(project, this); } } } public String getDefaultVersion() { return PYTHON_VERSION_LATEST; } public void saveAstManager() { File astOutputFile = getAstOutputFile(); if (astOutputFile == null) { //The project was removed. Nothing to save here. Log.log(IStatus.INFO, "Not saving ast manager for: " + this.project + ". No write area available.", null); return; } if (astManager == null) { return; } else { synchronized (astManager.getLock()) { astManager.saveToFile(astOutputFile); } } } public int getInterpreterType() throws CoreException { if (interpreterType == null) { String version = getVersion(); interpreterType = getInterpreterTypeFromVersion(version); } return interpreterType; } public static int getInterpreterTypeFromVersion(String version) throws CoreException { int interpreterType; if (IPythonNature.Versions.ALL_JYTHON_VERSIONS.contains(version)) { interpreterType = INTERPRETER_TYPE_JYTHON; } else if (IPythonNature.Versions.ALL_IRONPYTHON_VERSIONS.contains(version)) { interpreterType = INTERPRETER_TYPE_IRONPYTHON; } else { //if others fail, consider it python interpreterType = INTERPRETER_TYPE_PYTHON; } return interpreterType; } /** * Resolve the module given the absolute path of the file in the filesystem. * * @param fileAbsolutePath the absolute file path * @return the module name */ public String resolveModule(String fileAbsolutePath) { String moduleName = null; if (astManager != null) { moduleName = astManager.getModulesManager().resolveModule(fileAbsolutePath); } return moduleName; } /** * Resolve the module given the absolute path of the file in the filesystem. * * @param fileAbsolutePath the absolute file path * @return the module name * @throws CoreException */ public String resolveModuleOnlyInProjectSources(String fileAbsolutePath, boolean addExternal) throws CoreException { String moduleName = null; if (astManager != null) { IModulesManager modulesManager = astManager.getModulesManager(); if (modulesManager instanceof ProjectModulesManager) { moduleName = ((ProjectModulesManager) modulesManager).resolveModuleOnlyInProjectSources( fileAbsolutePath, addExternal); } } return moduleName; } public static String[] getStrAsStrItems(String str) { return str.split("\\|"); } public IInterpreterManager getRelatedInterpreterManager() { try { int interpreterType = getInterpreterType(); switch (interpreterType) { case IInterpreterManager.INTERPRETER_TYPE_PYTHON: return PydevPlugin.getPythonInterpreterManager(); case IInterpreterManager.INTERPRETER_TYPE_JYTHON: return PydevPlugin.getJythonInterpreterManager(); case IInterpreterManager.INTERPRETER_TYPE_IRONPYTHON: return PydevPlugin.getIronpythonInterpreterManager(); default: throw new RuntimeException("Unable to find the related interpreter manager for type: " + interpreterType); } } catch (Exception e) { throw new RuntimeException(e); } } // ------------------------------------------------------------------------------------------ LOCAL CACHES public void clearCaches(boolean clearGlobalModulesCache) { this.interpreterType = null; this.versionPropertyCache = null; this.interpreterPropertyCache = null; this.pythonPathNature.clearCaches(); if (clearGlobalModulesCache) { ModulesManager.clearCache(); } } Integer interpreterType = null; //cache public void clearBuiltinCompletions() { try { this.getRelatedInterpreterManager().clearBuiltinCompletions(this.getProjectInterpreterName()); } catch (CoreException e) { throw new RuntimeException(e); } } public IToken[] getBuiltinCompletions() { try { return this.getRelatedInterpreterManager().getBuiltinCompletions(this.getProjectInterpreterName()); } catch (Exception e) { throw new RuntimeException(e); } } public IModule getBuiltinMod() { try { return this.getRelatedInterpreterManager().getBuiltinMod(this.getProjectInterpreterName()); } catch (Exception e) { throw new RuntimeException(e); } } public void clearBuiltinMod() { try { this.getRelatedInterpreterManager().clearBuiltinMod(this.getProjectInterpreterName()); } catch (CoreException e) { throw new RuntimeException(e); } } public static List<IPythonNature> getPythonNaturesRelatedTo(int relatedTo) { ArrayList<IPythonNature> ret = new ArrayList<IPythonNature>(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IProject[] projects = root.getProjects(); for (IProject project : projects) { PythonNature nature = getPythonNature(project); try { if (nature != null) { if (nature.getInterpreterType() == relatedTo) { ret.add(nature); } } } catch (CoreException e) { throw new RuntimeException(e); } } return ret; } /** * @return the version of the grammar as defined in IPythonNature.GRAMMAR_PYTHON... */ public int getGrammarVersion() { try { String version = getVersion(); if (version == null) { Log.log("Found null version. Returning default."); return LATEST_GRAMMAR_VERSION; } List<String> splitted = StringUtils.split(version, ' '); if (splitted.size() != 2) { String storeVersion; try { storeVersion = getStore().getPropertyFromXml(getPythonProjectVersionQualifiedName()); } catch (Exception e) { storeVersion = "Unable to get storeVersion. Reason: " + e.getMessage(); } Log.log("Found invalid version: " + version + "\n" + "Returning default\n" + "Project: " + this.project + "\n" + "versionPropertyCache: " + versionPropertyCache + "\n" + "storeVersion:" + storeVersion); return LATEST_GRAMMAR_VERSION; } String grammarVersion = splitted.get(1); return getGrammarVersionFromStr(grammarVersion); } catch (CoreException e) { throw new RuntimeException(e); } } /** * @param grammarVersion a string in the format 2.x or 3.x * @return the grammar version as given in IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION */ public static int getGrammarVersionFromStr(String grammarVersion) { //Note that we don't have the grammar for all versions, so, we use the one closer to it (which is //fine as they're backward compatible). if ("2.1".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_4; } else if ("2.2".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_4; } else if ("2.3".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_4; } else if ("2.4".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_4; } else if ("2.5".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_5; } else if ("2.6".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_6; } else if ("2.7".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_2_7; } else if ("3.0".equals(grammarVersion)) { return GRAMMAR_PYTHON_VERSION_3_0; } if (grammarVersion != null) { if (grammarVersion.startsWith("3")) { return GRAMMAR_PYTHON_VERSION_3_0; } else if (grammarVersion.startsWith("2")) { //latest in the 2.x series return LATEST_GRAMMAR_VERSION; } } Log.log("Unable to recognize version: " + grammarVersion + " returning default."); return LATEST_GRAMMAR_VERSION; } protected IPythonNatureStore getStore() { return pythonNatureStore; } /** * This flag identifies that we're in tests (when that happens, some verifications are more relaxed). */ public static boolean IN_TESTS = false; /** * @return info on the interpreter configured for this nature. * @throws MisconfigurationException * * @note that an exception will be raised if the */ public IInterpreterInfo getProjectInterpreter() throws MisconfigurationException, PythonNatureWithoutProjectException { if (this.project == null) { throw new PythonNatureWithoutProjectException("Project is not set."); } try { String projectInterpreterName = getProjectInterpreterName(); IInterpreterInfo ret; IInterpreterManager relatedInterpreterManager = getRelatedInterpreterManager(); if (relatedInterpreterManager == null) { if (IN_TESTS) { return null; } throw new ProjectMisconfiguredException("Did not expect the interpreter manager to be null."); } if (IPythonNature.DEFAULT_INTERPRETER.equals(projectInterpreterName)) { //if it's the default, let's translate it to the outside world ret = relatedInterpreterManager.getDefaultInterpreterInfo(true); } else { ret = relatedInterpreterManager.getInterpreterInfo(projectInterpreterName, null); } if (ret == null) { final IProject p = this.getProject(); final String projectName; if (p != null) { projectName = p.getName(); } else { projectName = "null"; } String msg = "Invalid interpreter: " + projectInterpreterName + " configured for project: " + projectName + "."; ProjectMisconfiguredException e = new ProjectMisconfiguredException(msg); Log.log(e); throw e; } else { return ret; } } catch (CoreException e) { throw new ProjectMisconfiguredException(e); } } /** * @return The name of the interpreter that should be used for the nature this project is associated to. * * Note that this is the name that's visible to the user (and not the actual path of the executable). * * It can be null if the project is still not set! */ public String getProjectInterpreterName() throws CoreException { if (project != null) { if (interpreterPropertyCache == null) { String storeInterpreter = getStore().getPropertyFromXml(getPythonProjectInterpreterQualifiedName()); if (storeInterpreter == null) { //there is no such property set (let's set it to the default) setVersion(null, IPythonNature.DEFAULT_INTERPRETER); //will set the interpreterPropertyCache too } else { interpreterPropertyCache = storeInterpreter; } } } return interpreterPropertyCache; } /** * @return a list of configuration errors and the interpreter info for the project (the interpreter info can be null) * @throws PythonNatureWithoutProjectException */ public Tuple<List<ProjectConfigError>, IInterpreterInfo> getConfigErrorsAndInfo(final IProject relatedToProject) throws PythonNatureWithoutProjectException { if (IN_TESTS) { return new Tuple<List<ProjectConfigError>, IInterpreterInfo>(new ArrayList<ProjectConfigError>(), null); } ArrayList<ProjectConfigError> lst = new ArrayList<ProjectConfigError>(); if (this.project == null) { lst.add(new ProjectConfigError(relatedToProject, "The configured nature has no associated project.")); } IInterpreterInfo info = null; try { info = this.getProjectInterpreter(); String executableOrJar = info.getExecutableOrJar(); if (!new File(executableOrJar).exists()) { lst.add(new ProjectConfigError(relatedToProject, "The interpreter configured does not exist in the filesystem: " + executableOrJar)); } List<String> projectSourcePathSet = new ArrayList<String>(this.getPythonPathNature() .getProjectSourcePathSet(true)); Collections.sort(projectSourcePathSet); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); for (String path : projectSourcePathSet) { if (path.trim().length() > 0) { IPath p = new Path(path); IResource resource = root.findMember(p); if (resource == null) { relatedToProject.refreshLocal(p.segmentCount(), null); resource = root.findMember(p); //2nd attempt (after refresh) } if (resource == null || !resource.exists()) { lst.add(new ProjectConfigError(relatedToProject, "Source folder: " + path + " not found")); } } } List<String> externalPaths = this.getPythonPathNature().getProjectExternalSourcePathAsList(true); Collections.sort(externalPaths); for (String path : externalPaths) { if (!new File(path).exists()) { lst.add(new ProjectConfigError(relatedToProject, "Invalid external source folder specified: " + path)); } } Tuple<String, String> versionAndError = getVersionAndError(); if (versionAndError.o2 != null) { lst.add(new ProjectConfigError(relatedToProject, StringUtils.replaceNewLines(versionAndError.o2, " "))); } } catch (MisconfigurationException e) { lst.add(new ProjectConfigError(relatedToProject, StringUtils.replaceNewLines(e.getMessage(), " "))); } catch (Throwable e) { lst.add(new ProjectConfigError(relatedToProject, StringUtils.replaceNewLines( "Unexpected error:" + e.getMessage(), " "))); } return new Tuple<List<ProjectConfigError>, IInterpreterInfo>(lst, info); } @Override public String toString() { return "PythonNature: " + this.project; } }