/******************************************************************************* * Copyright (c) 2014, 2016 Red Hat Inc., and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mickael Istria (Red Hat Inc.) - initial API and implementation * Ivica Loncar - Projects open from inside parent inherit working sets ******************************************************************************/ package org.eclipse.ui.internal.navigator.resources.nested; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; /** * @since 3.3 * */ public class NestedProjectManager { private static NestedProjectManager INSTANCE = new NestedProjectManager(); /** * This structure sorts project by location, so we can assume that: * <ul> * <li>If a project is nested under another, then the parent project is the * previous item in the map. So getting the parent is just about checking * the previous project for parency.</li> * <li>the children project of a project (with any depth not only direct * ones) are the immediately following items in the map.</li> * </ul> */ private SortedMap<IPath, IProject> locationsToProjects = new TreeMap<>(new PathComparator()); private NestedProjectManager() { refreshProjectsList(); ResourcesPlugin.getWorkspace().addResourceChangeListener(new IResourceChangeListener() { @Override public void resourceChanged(IResourceChangeEvent event) { IResourceDelta delta = event.getDelta(); IResource resource = null; if (delta != null) { resource = delta.getResource(); } if (resource != null && (resource.getType() == IResource.PROJECT || resource.getType() == IResource.ROOT)) { refreshProjectsList(); } } }, IResourceChangeEvent.POST_CHANGE); } private void refreshProjectsList() { IProject[] knownProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); synchronized (locationsToProjects) { locationsToProjects.clear(); for (IProject project : knownProjects) { IPath location = project.getLocation(); if (location != null) { locationsToProjects.put(location, project); } } } } public static NestedProjectManager getInstance() { return INSTANCE; } /** * @param folder a folder to decide about * @return an {@link IProject} that or {@code null} */ public IProject getProject(IFolder folder) { if (folder == null) { return null; } IPath location = folder.getLocation(); if (location == null) { return null; } IProject res; synchronized (locationsToProjects) { res = locationsToProjects.get(location); } if (res != null && (!res.exists() || !location.equals(res.getLocation()))) { // project was deleted and state not refreshed refreshProjectsList(); return getProject(folder); } return res; } /** * A shorthand for {@code getProject(folder) != null}. * * @param folder * @return {@code true} if project having the same location as {@code folder} exists and nested is enabled, {@code false} otherwise */ public boolean isShownAsProject(IFolder folder) { return getProject(folder) != null; } public boolean isShownAsNested(IProject project) { if (!project.exists()) { return false; } IPath location = project.getLocation(); if (location == null) { return false; } IPath queriedLocation = location.removeLastSegments(1); synchronized (locationsToProjects) { while (queriedLocation.segmentCount() > 0) { if (locationsToProjects.containsKey(queriedLocation)) { return true; } queriedLocation = queriedLocation.removeLastSegments(1); } } return false; } public IContainer getMostDirectOpenContainer(IProject project) { IPath location = project.getLocation(); if (location == null) { return null; } IProject mostDirectParentProject = null; IPath queriedLocation = location.removeLastSegments(1); synchronized (locationsToProjects) { while (mostDirectParentProject == null && queriedLocation.segmentCount() > 0) { mostDirectParentProject = locationsToProjects.get(queriedLocation); if (mostDirectParentProject != null && mostDirectParentProject.getLocation() == null) { mostDirectParentProject = null; } queriedLocation = queriedLocation.removeLastSegments(1); } } if (mostDirectParentProject != null) { IPath parentContainerAbsolutePath = location.removeLastSegments(1); IPath location2 = mostDirectParentProject.getLocation(); if (location2 == null) { return null; } if (parentContainerAbsolutePath.equals(location2)) { return mostDirectParentProject; } IPath parentFolderPathRelativeToProject = parentContainerAbsolutePath .removeFirstSegments(location2.segmentCount()); return mostDirectParentProject.getFolder(parentFolderPathRelativeToProject); } return null; } /** * @param container * a container to ask for nested projects * @return the direct children projects for given container */ public IProject[] getDirectChildrenProjects(IContainer container) { if (container instanceof IWorkspaceRoot) { IWorkspaceRoot root = (IWorkspaceRoot) container; return root.getProjects(); } Set<IProject> res = new HashSet<>(); IPath containerLocation = container.getLocation(); IPath projectLocation = container.getProject().getLocation(); if (containerLocation == null || projectLocation == null) { return res.toArray(new IProject[res.size()]); } synchronized (locationsToProjects) { for (Entry<IPath, IProject> entry : locationsToProjects.tailMap(containerLocation).entrySet()) { if (entry.getValue().equals(container.getProject())) { // ignore current project } else if (containerLocation.isPrefixOf(entry.getKey())) { if (entry.getKey().segmentCount() == containerLocation.segmentCount() + 1) { res.add(entry.getValue()); } } else { // moved to another branch, not worth continuing break; } } } return res.toArray(new IProject[res.size()]); } /** * @param container * @return whether the container has some projects as direct children */ public boolean hasDirectChildrenProjects(IContainer container) { if (container instanceof IWorkspaceRoot) { IWorkspaceRoot root = (IWorkspaceRoot) container; return root.getProjects().length > 0; } IPath containerLocation = container.getLocation(); IPath projectLocation = container.getProject().getLocation(); if (containerLocation == null || projectLocation == null) { return false; } synchronized (locationsToProjects) { for (Entry<IPath, IProject> entry : locationsToProjects.tailMap(containerLocation).entrySet()) { if (entry.getValue().equals(container.getProject())) { // ignore current project } else if (containerLocation.isPrefixOf(entry.getKey())) { if (entry.getKey().segmentCount() == containerLocation.segmentCount() + 1) { return true; } } else { // moved to another branch, not worth continuing break; } } } return false; } }