/** * 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 Oct 7, 2006 * @author Fabio */ package org.python.pydev.navigator; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; 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.resources.IWorkspaceRoot; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.ui.IMemento; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.navigator.IPipelinedTreeContentProvider; import org.eclipse.ui.navigator.PipelinedShapeModification; import org.eclipse.ui.navigator.PipelinedViewerUpdate; import org.python.pydev.core.log.Log; import org.python.pydev.navigator.elements.IWrappedResource; import org.python.pydev.navigator.elements.ProjectConfigError; import org.python.pydev.navigator.elements.PythonFile; import org.python.pydev.navigator.elements.PythonFolder; import org.python.pydev.navigator.elements.PythonProjectSourceFolder; import org.python.pydev.navigator.elements.PythonResource; import org.python.pydev.navigator.elements.PythonSourceFolder; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.shared_core.structure.FastStack; import org.python.pydev.shared_core.structure.TreeNode; /** * This is the Model provider for python elements. * * It intercepts the adds/removes and changes the original elements for elements * that actually reflect the python model (with source folder, etc). * * * Tests for package explorer: * 1. Start eclipse with a file deep in the structure and without having anything expanded in the tree, make a 'show in' * * @author Fabio */ public final class PythonModelProvider extends PythonBaseModelProvider implements IPipelinedTreeContentProvider { /* (non-Javadoc) * @see org.python.pydev.navigator.PythonBaseModelProvider#getChildren(java.lang.Object) */ @Override public Object[] getChildren(Object parentElement) { Object[] ret = super.getChildren(parentElement); if (parentElement instanceof PythonProjectSourceFolder) { PythonProjectSourceFolder projectSourceFolder = (PythonProjectSourceFolder) parentElement; Set<Object> set = new HashSet<Object>(); fillChildrenForProject(set, (IProject) projectSourceFolder.getActualObject(), projectSourceFolder); if (set.size() > 0) { Object[] newRet = new Object[ret.length + set.size()]; System.arraycopy(ret, 0, newRet, 0, ret.length); int i = ret.length; for (Object o : set) { newRet[i] = o; i++; } ret = newRet; } } return ret; } /** * This method basically replaces all the elements for other resource elements * or for wrapped elements. * * @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#getPipelinedChildren(java.lang.Object, java.util.Set) */ @Override @SuppressWarnings("unchecked") public void getPipelinedChildren(Object parent, Set currentElements) { if (DEBUG) { System.out.println("getPipelinedChildren for: " + parent); } if (parent instanceof IWrappedResource) { //Note: It seems that this NEVER happens (IWrappedResources only have getChildren called, not getPipelinedChildren) Object[] children = getChildren(parent); currentElements.clear(); currentElements.addAll(Arrays.asList(children)); if (DEBUG) { System.out.println("getPipelinedChildren RETURN: " + currentElements); } if (parent instanceof PythonProjectSourceFolder) { PythonProjectSourceFolder projectSourceFolder = (PythonProjectSourceFolder) parent; IProject project = (IProject) projectSourceFolder.getActualObject(); fillChildrenForProject(currentElements, project, parent); } return; } else if (parent instanceof IWorkspaceRoot) { switch (topLevelChoice.getRootMode()) { case TopLevelProjectsOrWorkingSetChoice.WORKING_SETS: currentElements.clear(); currentElements.addAll(getWorkingSetsCallback.call((IWorkspaceRoot) parent)); if (currentElements.size() == 0) { currentElements.add(createErrorNoWorkingSetsDefined(parent)); } case TopLevelProjectsOrWorkingSetChoice.PROJECTS: //Just go on... } } else if (parent instanceof IWorkingSet) { IWorkingSet workingSet = (IWorkingSet) parent; currentElements.clear(); currentElements.addAll(Arrays.asList(workingSet.getElements())); if (currentElements.size() == 0) { currentElements.add(createErrorNoWorkingSetsDefined(workingSet)); } } else if (parent instanceof TreeNode) { TreeNode treeNode = (TreeNode) parent; currentElements.addAll(treeNode.getChildren()); } else if (parent instanceof IProject) { IProject project = (IProject) parent; fillChildrenForProject(currentElements, project, getResourceInPythonModel(project)); } PipelinedShapeModification modification = new PipelinedShapeModification(parent, currentElements); convertToPythonElementsAddOrRemove(modification, true); if (DEBUG) { System.out.println("getPipelinedChildren RETURN: " + modification.getChildren()); } } @SuppressWarnings("unchecked") private void fillChildrenForProject(Set currentElements, IProject project, Object parent) { ProjectInfoForPackageExplorer projectInfo = ProjectInfoForPackageExplorer.getProjectInfo(project); if (projectInfo != null) { currentElements.addAll(projectInfo.configErrors); InterpreterInfoTreeNode<LabelAndImage> projectInfoTreeStructure = projectInfo.getProjectInfoTreeStructure( project, parent); if (projectInfoTreeStructure != null) { currentElements.add(projectInfoTreeStructure); } } } /** * This method basically replaces all the elements for other resource elements * or for wrapped elements. * * @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#getPipelinedElements(java.lang.Object, java.util.Set) */ @Override public void getPipelinedElements(Object input, Set currentElements) { if (DEBUG) { System.out.println("getPipelinedElements for: " + input); } getPipelinedChildren(input, currentElements); } /** * This method basically get the actual parent for the resource or the parent * for a wrapped element (which may be a resource or a wrapped resource). * * @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#getPipelinedParent(java.lang.Object, java.lang.Object) */ @Override public Object getPipelinedParent(Object object, Object aSuggestedParent) { if (DEBUG) { System.out.println("getPipelinedParent for: " + object); } //Now, we got the parent for the resources correctly at this point, but there's one last thing we may need to //do: the actual parent may be a working set! Object p = this.topLevelChoice.getWorkingSetParentIfAvailable(object, getWorkingSetsCallback); if (p != null) { aSuggestedParent = p; } else if (object instanceof IWrappedResource) { IWrappedResource resource = (IWrappedResource) object; Object parentElement = resource.getParentElement(); if (parentElement != null) { aSuggestedParent = parentElement; } } else if (object instanceof TreeNode<?>) { TreeNode<?> treeNode = (TreeNode<?>) object; return treeNode.getParent(); } else if (object instanceof ProjectConfigError) { ProjectConfigError configError = (ProjectConfigError) object; return configError.getParent(); } if (DEBUG) { System.out.println("getPipelinedParent RETURN: " + aSuggestedParent); } return aSuggestedParent; } /** * This method intercepts some addition to the tree and converts its elements to python * elements. * * @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#interceptAdd(org.eclipse.ui.navigator.PipelinedShapeModification) */ @Override public PipelinedShapeModification interceptAdd(PipelinedShapeModification addModification) { if (DEBUG) { System.out.println("interceptAdd"); } convertToPythonElementsAddOrRemove(addModification, true); return addModification; } @Override public boolean interceptRefresh(PipelinedViewerUpdate refreshSynchronization) { if (DEBUG) { System.out.println("interceptRefresh"); } return convertToPythonElementsUpdateOrRefresh(refreshSynchronization.getRefreshTargets()); } @Override public PipelinedShapeModification interceptRemove(PipelinedShapeModification removeModification) { if (DEBUG) { System.out.println("interceptRemove"); } convertToPythonElementsAddOrRemove(removeModification, false); return removeModification; } @Override public boolean interceptUpdate(PipelinedViewerUpdate updateSynchronization) { if (DEBUG) { debug("Before interceptUpdate", updateSynchronization); } boolean ret = convertToPythonElementsUpdateOrRefresh(updateSynchronization.getRefreshTargets()); if (DEBUG) { debug("After interceptUpdate", updateSynchronization); } return ret; } /** * Helper for debugging the things we have in an update */ private void debug(String desc, PipelinedViewerUpdate updateSynchronization) { System.out.println("\nDesc:" + desc); System.out.println("Refresh targets:"); for (Object o : updateSynchronization.getRefreshTargets()) { System.out.println(o); } } /** * Helper for debugging the things we have in a modification */ private void debug(String desc, PipelinedShapeModification modification) { System.out.println("\nDesc:" + desc); Object parent = modification.getParent(); System.out.println("Parent:" + parent); System.out.println("Children:"); for (Object o : modification.getChildren()) { System.out.println(o); } } /** * This is the function that is responsible for restoring the paths in the tree. */ @Override public void restoreState(IMemento memento) { new PyPackageStateSaver(this, viewer, memento).restoreState(); } /** * This is the function that is responsible for saving the paths in the tree. */ @Override public void saveState(IMemento memento) { new PyPackageStateSaver(this, viewer, memento).saveState(); } /** * Converts the shape modification to use Python elements. * * @param modification: the shape modification to convert * @param isAdd: boolean indicating whether this convertion is happening in an add operation */ private void convertToPythonElementsAddOrRemove(PipelinedShapeModification modification, boolean isAdd) { if (DEBUG) { debug("Before", modification); } Object parent = modification.getParent(); if (parent instanceof IContainer) { IContainer parentContainer = (IContainer) parent; Object pythonParent = getResourceInPythonModel(parentContainer, true); if (pythonParent instanceof IWrappedResource) { IWrappedResource parentResource = (IWrappedResource) pythonParent; modification.setParent(parentResource); wrapChildren(parentResource, parentResource.getSourceFolder(), modification.getChildren(), isAdd); } else if (pythonParent == null) { Object parentInWrap = parentContainer; PythonSourceFolder sourceFolderInWrap = null; //this may happen when a source folder is added or some element that still doesn't have it's parent in the model... //so, we have to get the parent's parent until we actually 'know' that it is not in the model (or until we run //out of parents to try) //the case in which we reproduce this is Test 1 (described in the class) FastStack<Object> found = new FastStack<Object>(20); while (true) { //add the current to the found if (parentContainer == null) { break; } found.push(parentContainer); if (parentContainer instanceof IProject) { //we got to the project without finding any part of a python model already there, so, let's see //if any of the parts was actually a source folder (that was still not added) tryCreateModelFromProject((IProject) parentContainer, found); //and now, if it was created, try to convert it to the python model (without any further add) convertToPythonElementsUpdateOrRefresh(modification.getChildren()); return; } Object p = getResourceInPythonModel(parentContainer, true); if (p instanceof IWrappedResource) { IWrappedResource wrappedResource = (IWrappedResource) p; sourceFolderInWrap = wrappedResource.getSourceFolder(); while (found.size() > 0) { Object f = found.pop(); if (f instanceof IResource) { //no need to create it if it's already in the model! Object child = sourceFolderInWrap.getChild((IResource) f); if (child != null && child instanceof IWrappedResource) { wrappedResource = (IWrappedResource) child; continue; } } //creating is enough to add it to the model if (f instanceof IFile) { wrappedResource = new PythonFile(wrappedResource, (IFile) f, sourceFolderInWrap); } else if (f instanceof IFolder) { wrappedResource = new PythonFolder(wrappedResource, (IFolder) f, sourceFolderInWrap); } } parentInWrap = wrappedResource; break; } parentContainer = parentContainer.getParent(); } wrapChildren(parentInWrap, sourceFolderInWrap, modification.getChildren(), isAdd); } } else if (parent == null) { wrapChildren(null, null, modification.getChildren(), isAdd); } if (DEBUG) { debug("After", modification); } } /** * Given a Path from the 1st child of the project, will try to create that path in the python model. * @param project the project * @param found a stack so that the last element added is the leaf of the path we want to discover */ private void tryCreateModelFromProject(IProject project, FastStack<Object> found) { PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { return;//if the python nature is not available, we won't have any python elements here } Set<String> sourcePathSet = new HashSet<String>(); try { sourcePathSet = nature.getPythonPathNature().getProjectSourcePathSet(true); } catch (CoreException e) { Log.log(e); } Object currentParent = project; PythonSourceFolder pythonSourceFolder = null; for (Iterator<Object> it = found.topDownIterator(); it.hasNext();) { Object child = it.next(); if (child instanceof IFolder || child instanceof IProject) { if (pythonSourceFolder == null) { pythonSourceFolder = tryWrapSourceFolder(currentParent, (IContainer) child, sourcePathSet); if (pythonSourceFolder != null) { currentParent = pythonSourceFolder; } else if (child instanceof IContainer) { currentParent = child; } //just go on (if we found the source folder or not, because if we found, that's ok, and if //we didn't, then the children will not be in the python model anyway) continue; } } if (pythonSourceFolder != null) { IWrappedResource r = doWrap(currentParent, pythonSourceFolder, child); if (r != null) { child = r; } } currentParent = child; } } /** * Actually wraps some resource into a wrapped resource. * * @param parent this is the parent * it may be null -- in the case of a remove * it may be a wrapped resource (if it is in the python model) * it may be a resource (if it is a source folder) * * * @param pythonSourceFolder this is the python source folder for the resource (it may be null if the resource itself is a source folder * or if it is actually a resource that has already been removed) * @param currentChildren those are the children that should be wrapped * @param isAdd whether this is an add operation or not * @return */ @SuppressWarnings("unchecked") protected boolean wrapChildren(Object parent, PythonSourceFolder pythonSourceFolder, Set currentChildren, boolean isAdd) { LinkedHashSet convertedChildren = new LinkedHashSet(); for (Iterator childrenItr = currentChildren.iterator(); childrenItr.hasNext();) { Object child = childrenItr.next(); if (child == null) { //only case when a child is removed and another one is not added (null) childrenItr.remove(); continue; } //yeap, it may be an object that's not an actual resource (created by some other plugin... just continue) if (!(child instanceof IResource)) { continue; } Object existing = getResourceInPythonModel((IResource) child, true); if (existing == null) { if (isAdd) { //add IWrappedResource w = doWrap(parent, pythonSourceFolder, child); if (w != null) { //if it is null, it is not below a python source folder childrenItr.remove(); convertedChildren.add(w); } } else { continue; //it has already been removed } } else { //existing != null childrenItr.remove(); convertedChildren.add(existing); if (!isAdd) { //also remove it from the model IWrappedResource wrapped = (IWrappedResource) existing; wrapped.getSourceFolder().removeChild((IResource) child); } } } //if we did have some wrapping... go on and add them to the out list (and return true) if (!convertedChildren.isEmpty()) { currentChildren.addAll(convertedChildren); return true; } //nothing happened, so, just say it return false; } /** * This method tries to wrap a given resource as a wrapped resource (if possible) * * @param parent the parent of the wrapped resource * @param pythonSourceFolder the source folder that contains this resource * @param child the object that should be wrapped * @return the object as an object from the python model */ protected IWrappedResource doWrap(Object parent, PythonSourceFolder pythonSourceFolder, Object child) { if (child instanceof IProject) { //ok, let's see if the child is a source folder (as the default project can be the actual source folder) if (pythonSourceFolder == null && parent != null) { PythonSourceFolder f = doWrapPossibleSourceFolder(parent, (IProject) child); if (f != null) { return f; } } } else if (child instanceof IFolder) { IFolder folder = (IFolder) child; //it may be a PythonSourceFolder if (pythonSourceFolder == null && parent != null) { PythonSourceFolder f = doWrapPossibleSourceFolder(parent, folder); if (f != null) { return f; } } if (pythonSourceFolder != null) { return new PythonFolder((IWrappedResource) parent, folder, pythonSourceFolder); } } else if (child instanceof IFile) { if (pythonSourceFolder != null) { //if the python source folder is null, that means that this is a file that is not actually below a source folder -- so, don't wrap it return new PythonFile((IWrappedResource) parent, (IFile) child, pythonSourceFolder); } } else if (child instanceof IResource) { if (pythonSourceFolder != null) { return new PythonResource((IWrappedResource) parent, (IResource) child, pythonSourceFolder); } } else { throw new RuntimeException("Unexpected class:" + child.getClass()); } return null; } /** * Try to wrap a folder or project as a source folder... */ private PythonSourceFolder doWrapPossibleSourceFolder(Object parent, IContainer container) { try { IProject project; if (!(container instanceof IProject)) { project = ((IContainer) parent).getProject(); } else { project = (IProject) container; } PythonNature nature = PythonNature.getPythonNature(project); if (nature != null) { //check for source folder Set<String> sourcePathSet = nature.getPythonPathNature().getProjectSourcePathSet(true); PythonSourceFolder newSourceFolder = tryWrapSourceFolder(parent, container, sourcePathSet); if (newSourceFolder != null) { return newSourceFolder; } } } catch (CoreException e) { throw new RuntimeException(e); } return null; } /** * This method checks if the given folder can be wrapped as a source-folder, and if that's possible, creates and returns * it * @return a created source folder or null if it couldn't be created. */ private PythonSourceFolder tryWrapSourceFolder(Object parent, IContainer container, Set<String> sourcePathSet) { IPath fullPath = container.getFullPath(); if (sourcePathSet.contains(fullPath.toString())) { PythonSourceFolder sourceFolder; if (container instanceof IFolder) { sourceFolder = new PythonSourceFolder(parent, (IFolder) container); } else if (container instanceof IProject) { sourceFolder = new PythonProjectSourceFolder(parent, (IProject) container); } else { return null; //some other container we don't know how to treat! } //System.out.println("Created source folder: "+ret[i]+" - "+folder.getProject()+" - "+folder.getProjectRelativePath()); Set<PythonSourceFolder> sourceFolders = getProjectSourceFolders(container.getProject()); sourceFolders.add(sourceFolder); return sourceFolder; } return null; } /** * Converts elements to the python model -- but only creates it if it's parent is found in the python model */ @SuppressWarnings("unchecked") protected boolean convertToPythonElementsUpdateOrRefresh(Set currentChildren) { LinkedHashSet convertedChildren = new LinkedHashSet(); for (Iterator childrenItr = currentChildren.iterator(); childrenItr.hasNext();) { Object child = childrenItr.next(); if (child == null) { //only case when a child is removed and another one is not added (null) childrenItr.remove(); continue; } if (child instanceof IResource && !(child instanceof IWrappedResource)) { IResource res = (IResource) child; Object resourceInPythonModel = getResourceInPythonModel(res, true); if (resourceInPythonModel != null) { //if it is in the python model, just go on childrenItr.remove(); convertedChildren.add(resourceInPythonModel); } else { //now, if it's not but its parent is, go on and create it IContainer p = res.getParent(); if (p == null) { continue; } Object pythonParent = getResourceInPythonModel(p, true); if (pythonParent instanceof IWrappedResource) { IWrappedResource parent = (IWrappedResource) pythonParent; if (res instanceof IProject) { throw new RuntimeException("A project's parent should never be an IWrappedResource!"); } else if (res instanceof IFolder) { childrenItr.remove(); convertedChildren.add(new PythonFolder(parent, (IFolder) res, parent.getSourceFolder())); } else if (res instanceof IFile) { childrenItr.remove(); convertedChildren.add(new PythonFile(parent, (IFile) res, parent.getSourceFolder())); } else if (child instanceof IResource) { childrenItr.remove(); convertedChildren.add(new PythonResource(parent, (IResource) child, parent .getSourceFolder())); } } else if (res instanceof IFolder) { //ok, still not in the model... could it be a PythonSourceFolder IFolder folder = (IFolder) res; IProject project = folder.getProject(); if (project == null) { continue; } PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { continue; } Set<String> sourcePathSet = new HashSet<String>(); try { sourcePathSet = nature.getPythonPathNature().getProjectSourcePathSet(true); } catch (CoreException e) { Log.log(e); } PythonSourceFolder wrapped = tryWrapSourceFolder(p, folder, sourcePathSet); if (wrapped != null) { childrenItr.remove(); convertedChildren.add(wrapped); } } } } } if (!convertedChildren.isEmpty()) { currentChildren.addAll(convertedChildren); return true; } return false; } }