/** * 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. */ package org.python.pydev.navigator; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.python.pydev.core.IInterpreterInfo; import org.python.pydev.core.PythonNatureWithoutProjectException; import org.python.pydev.core.log.Log; import org.python.pydev.navigator.elements.ProjectConfigError; import org.python.pydev.navigator.elements.PythonSourceFolder; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_ui.ImageCache; import org.python.pydev.shared_ui.UIConstants; import org.python.pydev.shared_ui.utils.PyMarkerUtils; /** * This class contains information about the project (info we need to show in the tree). */ public class ProjectInfoForPackageExplorer { /** * These are the source folders that can be found in this file provider. The way we * see things in this provider, the python model starts only after some source folder * is found. */ private static final Map<IProject, ProjectInfoForPackageExplorer> projectToSourceFolders = new HashMap<IProject, ProjectInfoForPackageExplorer>(); private static final Object lockProjectToSourceFolders = new Object(); /** * @return the information on a project. Can create it if it's not available. */ public static ProjectInfoForPackageExplorer getProjectInfo(final IProject project) { if (project == null) { return null; } synchronized (lockProjectToSourceFolders) { ProjectInfoForPackageExplorer projectInfo = projectToSourceFolders.get(project); if (projectInfo == null) { if (!project.isOpen()) { return null; } //No project info: create it projectInfo = projectToSourceFolders.get(project); if (projectInfo == null) { projectInfo = new ProjectInfoForPackageExplorer(project); projectToSourceFolders.put(project, projectInfo); } } else { if (!project.isOpen()) { projectToSourceFolders.remove(project); projectInfo = null; } } return projectInfo; } } /** * Note that the source folders are added/removed lazily (not when the info is recreated) */ public final Set<PythonSourceFolder> sourceFolders = new HashSet<PythonSourceFolder>(); /** * Whenever the info is recreated this is also recreated. */ public final List<ProjectConfigError> configErrors = new ArrayList<ProjectConfigError>(); /** * The interpreter info available (may be null) */ public IInterpreterInfo interpreterInfo; /** * Cache for the interpreter info tree root (so, if asked more than once this one will be reused). */ private InterpreterInfoTreeNodeRoot<LabelAndImage> interpreterInfoTreeRoot; /** * Creates the info for the passed project. */ private ProjectInfoForPackageExplorer(IProject project) { this.recreateInfo(project); } /** * Recreates the information about the project. */ public void recreateInfo(IProject project) { interpreterInfoTreeRoot = null; configErrors.clear(); Tuple<List<ProjectConfigError>, IInterpreterInfo> configErrorsAndInfo = getConfigErrorsAndInfo(project); configErrors.addAll(configErrorsAndInfo.o1); this.interpreterInfo = configErrorsAndInfo.o2; } public synchronized InterpreterInfoTreeNodeRoot<LabelAndImage> getProjectInfoTreeStructure(IProject project, Object parent) { if (parent == null || this.interpreterInfo == null) { return null; } PythonNature nature = PythonNature.getPythonNature(project); if (interpreterInfoTreeRoot != null) { if (interpreterInfoTreeRoot.getParent().equals(parent) && interpreterInfoTreeRoot.interpreterInfo.equals(interpreterInfo)) { return interpreterInfoTreeRoot; } } interpreterInfoTreeRoot = null; try { ImageCache imageCache = PydevPlugin.getImageCache(); //The root will create its children automatically. String nameForUI = interpreterInfo.getNameForUI(); nameForUI = StringUtils.shorten(nameForUI, 40); interpreterInfoTreeRoot = new InterpreterInfoTreeNodeRoot<LabelAndImage>(interpreterInfo, nature, parent, new LabelAndImage(nameForUI, imageCache.get(UIConstants.PY_INTERPRETER_ICON))); } catch (Throwable e) { Log.log(e); return null; } return interpreterInfoTreeRoot; } /** * Do the update of the markers in a separate job so that we don't do that in the ui-thread. * * See: #PyDev-88: Eclipse freeze on project import and project creation (presumable cause: virtualenvs as custom interpreters) */ private static final class UpdatePydevPackageExplorerProblemMarkers extends Job { private IProject fProject; private ProjectConfigError[] fProjectConfigErrors; private final Object lockJob = new Object(); private UpdatePydevPackageExplorerProblemMarkers(String name) { super(name); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override protected IStatus run(IProgressMonitor monitor) { final IProject project; final ProjectConfigError[] projectConfigErrors; synchronized (lockJob) { project = this.fProject; projectConfigErrors = this.fProjectConfigErrors; this.fProject = null; this.fProjectConfigErrors = null; //In a racing condition it's possible that it was scheduled again when the projectConfigErrors was already //set to null. if (projectConfigErrors == null) { return Status.OK_STATUS; } } ArrayList lst = new ArrayList(projectConfigErrors.length); for (ProjectConfigError error : projectConfigErrors) { try { Map attributes = new HashMap(); attributes.put(IMarker.MESSAGE, error.getLabel()); attributes.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); lst.add(attributes); } catch (Exception e) { Log.log(e); } } PyMarkerUtils.replaceMarkers((Map<String, Object>[]) lst.toArray(new Map[lst.size()]), project, PythonBaseModelProvider.PYDEV_PACKAGE_EXPORER_PROBLEM_MARKER, true, monitor); synchronized (ProjectInfoForPackageExplorer.lock) { //Only need to lock at the outer lock as it's the same one needed for the job creation. if (this.fProject == null) { //if it's not null, it means it was rescheduled! ProjectInfoForPackageExplorer.projectToJob.remove(project); } } return Status.OK_STATUS; } public void setInfo(IProject project, ProjectConfigError[] projectConfigErrors) { synchronized (lockJob) { this.fProject = project; this.fProjectConfigErrors = projectConfigErrors; } } } private static final Map<IProject, UpdatePydevPackageExplorerProblemMarkers> projectToJob = new HashMap<IProject, UpdatePydevPackageExplorerProblemMarkers>(); private static final Object lock = new Object(); /** * Never returns null. * * This method should only be called through recreateInfo. */ private Tuple<List<ProjectConfigError>, IInterpreterInfo> getConfigErrorsAndInfo(IProject project) { if (project == null || !project.isOpen()) { return new Tuple<List<ProjectConfigError>, IInterpreterInfo>(new ArrayList<ProjectConfigError>(), null); } PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { return new Tuple<List<ProjectConfigError>, IInterpreterInfo>(new ArrayList<ProjectConfigError>(), null); } //If the info is not readily available, we try to get some more times... after that, if still not available, //we just return as if it's all OK. Tuple<List<ProjectConfigError>, IInterpreterInfo> configErrorsAndInfo = null; boolean goodToGo = false; for (int i = 0; i < 10 && !goodToGo; i++) { try { configErrorsAndInfo = nature.getConfigErrorsAndInfo(project); goodToGo = true; } catch (PythonNatureWithoutProjectException e1) { goodToGo = false; synchronized (this) { try { wait(100); } catch (InterruptedException e) { } } } } if (configErrorsAndInfo == null) { return new Tuple<List<ProjectConfigError>, IInterpreterInfo>(new ArrayList<ProjectConfigError>(), null); } if (nature != null) { synchronized (lock) { UpdatePydevPackageExplorerProblemMarkers job = projectToJob .get(project); if (job == null) { job = new UpdatePydevPackageExplorerProblemMarkers( "Update pydev package explorer markers for: " + project); projectToJob.put(project, job); } job.setInfo(project, configErrorsAndInfo.o1.toArray(new ProjectConfigError[configErrorsAndInfo.o1.size()])); job.schedule(); } } return configErrorsAndInfo; } }