1 /*******************************************************************************
2  * Copyright (c) 2006, 2017 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Dina Sayed, dsayed@eg.ibm.com, IBM -  bug 269844
14  *     Andrey Loskutov <loskutov@gmx.de> - generified interface, bug 462760
15  *     Mickael Istria (Red Hat Inc.) - Bug 486901
16  *     Lucas Bullen (Red Hat Inc.) - Bug 522096 - "Close Projects" on working set
17  *******************************************************************************/
18 package org.eclipse.ui.actions;
19 
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23 
24 import org.eclipse.core.resources.IProject;
25 import org.eclipse.core.resources.IResource;
26 import org.eclipse.core.resources.IResourceChangeEvent;
27 import org.eclipse.core.resources.IResourceDelta;
28 import org.eclipse.core.resources.ResourcesPlugin;
29 import org.eclipse.core.runtime.CoreException;
30 import org.eclipse.jface.dialogs.IDialogConstants;
31 import org.eclipse.jface.dialogs.MessageDialogWithToggle;
32 import org.eclipse.jface.preference.IPreferenceStore;
33 import org.eclipse.jface.window.IShellProvider;
34 import org.eclipse.osgi.util.NLS;
35 import org.eclipse.swt.widgets.Shell;
36 import org.eclipse.ui.PlatformUI;
37 import org.eclipse.ui.ide.IDEActionFactory;
38 import org.eclipse.ui.internal.ide.IDEInternalPreferences;
39 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
40 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
41 import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
42 import org.eclipse.ui.internal.ide.misc.DisjointSet;
43 
44 /**
45  * This action closes all projects that are unrelated to the selected projects. A
46  * project is unrelated if it is not directly or transitively referenced by one
47  * of the selected projects, and does not directly or transitively reference
48  * one of the selected projects.
49  * <p>
50  * This class may be instantiated; it is not intended to be subclassed.
51  * </p>
52  *
53  * @see IDEActionFactory#CLOSE_UNRELATED_PROJECTS
54  * @since 3.3
55  */
56 public class CloseUnrelatedProjectsAction extends CloseResourceAction {
57 	/**
58 	 * The id of this action.
59 	 */
60 	public static final String ID = PlatformUI.PLUGIN_ID + ".CloseUnrelatedProjectsAction"; //$NON-NLS-1$
61 
62 	private List<IResource> projectsToClose = new ArrayList<>();
63 
64 	private boolean selectionDirty = true;
65 
66 	private List<? extends IResource> oldSelection = Collections.emptyList();
67 
68 
69 	/**
70 	 * Builds the connected component set for the input projects.
71 	 * The result is a DisjointSet where all related projects belong
72 	 * to the same set.
73 	 */
buildConnectedComponents(IProject[] projects)74 	private static DisjointSet<IProject> buildConnectedComponents(IProject[] projects) {
75 		//initially each vertex is in a set by itself
76 		DisjointSet<IProject> set = new DisjointSet<>();
77 		for (IProject project : projects) {
78 			set.makeSet(project);
79 		}
80 		for (IProject project : projects) {
81 			try {
82 				IProject[] references = project.getReferencedProjects();
83 				//each reference represents an edge in the project reference
84 				//digraph from projects[i] -> references[j]
85 				for (IProject reference : references) {
86 					IProject setOne = set.findSet(project);
87 					//note that referenced projects may not exist in the workspace
88 					IProject setTwo = set.findSet(reference);
89 					//these two projects are related, so join their sets
90 					if (setOne != null && setTwo != null && setOne != setTwo) {
91 						set.union(setOne, setTwo);
92 					}
93 				}
94 			} catch (CoreException e) {
95 				//assume inaccessible projects have no references
96 			}
97 		}
98 		return set;
99 	}
100 
101 	/**
102 	 * Creates this action.
103 	 *
104 	 * @param shell
105 	 *            The shell to use for parenting any dialogs created by this
106 	 *            action.
107 	 *
108 	 * @deprecated {@link #CloseUnrelatedProjectsAction(IShellProvider)}
109 	 */
110 	@Deprecated
CloseUnrelatedProjectsAction(Shell shell)111 	public CloseUnrelatedProjectsAction(Shell shell) {
112 		super(shell, IDEWorkbenchMessages.CloseUnrelatedProjectsAction_text);
113 		initAction();
114 	}
115 
116 	/**
117 	 * Creates this action.
118 	 *
119 	 * @param provider
120 	 *            The shell to use for parenting any dialogs created by this
121 	 *            action.
122 	 * @since 3.4
123 	 */
CloseUnrelatedProjectsAction(IShellProvider provider)124 	public CloseUnrelatedProjectsAction(IShellProvider provider){
125 		super(provider, IDEWorkbenchMessages.CloseUnrelatedProjectsAction_text,
126 				IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip,
127 				IDEWorkbenchMessages.CloseUnrelatedProjectsAction_text_plural,
128 				IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip_plural);
129 		initAction();
130 	}
131 
132 	@Override
run()133 	public void run() {
134 		if (promptForConfirmation()) {
135 			super.run();
136 		}
137 	}
138 
139 	/**
140 	 * Returns whether to close unrelated projects.
141 	 * Consults the preference and prompts the user if necessary.
142 	 *
143 	 * @return <code>true</code> if unrelated projects should be closed, and
144 	 *         <code>false</code> otherwise.
145 	 */
promptForConfirmation()146 	private boolean promptForConfirmation() {
147 		IPreferenceStore store = IDEWorkbenchPlugin.getDefault().getPreferenceStore();
148 		if (store.getBoolean(IDEInternalPreferences.CLOSE_UNRELATED_PROJECTS)) {
149 			return true;
150 		}
151 
152 		// get first project name
153 		List<? extends IResource> selection = super.getSelectedResources();
154 		int selectionSize = selection.size();
155 		if (selectionSize == 0) {
156 			return true;
157 		}
158 
159 		String message = null;
160 		if (selectionSize == 1) { // if one project is selected then print its name
161 			IResource firstSelected = selection.get(0);
162 			String projectName = null;
163 			if (firstSelected instanceof IProject) {
164 				projectName = ((IProject) firstSelected).getName();
165 			}
166 			message = NLS.bind(IDEWorkbenchMessages.CloseUnrelatedProjectsAction_confirmMsg1, projectName);
167 		} else // if more then one project is selected then print there number
168 			message = NLS.bind(IDEWorkbenchMessages.CloseUnrelatedProjectsAction_confirmMsgN, selectionSize);
169 
170 			MessageDialogWithToggle dialog = MessageDialogWithToggle.openOkCancelConfirm(
171 						getShell(), IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip,
172 						message, IDEWorkbenchMessages.CloseUnrelatedProjectsAction_AlwaysClose,
173 						false, null, null);
174 		if (dialog.getReturnCode() != IDialogConstants.OK_ID) {
175 			return false;
176 		}
177 		store.setValue(IDEInternalPreferences.CLOSE_UNRELATED_PROJECTS, dialog.getToggleState());
178 		return true;
179 	}
180 
181 	/**
182 	 * Initializes for the constructor.
183 	 */
initAction()184 	private void initAction(){
185 		setId(ID);
186 		setToolTipText(IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip);
187 		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IIDEHelpContextIds.CLOSE_UNRELATED_PROJECTS_ACTION);
188 	}
189 
190 	@Override
clearCache()191 	protected void clearCache() {
192 		super.clearCache();
193 		oldSelection = Collections.emptyList();
194 		selectionDirty = true;
195 	}
196 
197 	/**
198 	 * Computes the related projects of the selection.
199 	 */
computeRelated(List<? extends IResource> selection)200 	private List<IResource> computeRelated(List<? extends IResource> selection) {
201 		if (selection.contains(ResourcesPlugin.getWorkspace().getRoot())) {
202 			return new ArrayList<>();
203 		}
204 		//build the connected component set for all projects in the workspace
205 		DisjointSet<IProject> set = buildConnectedComponents(ResourcesPlugin.getWorkspace().getRoot().getProjects());
206 		//remove the connected components that the selected projects are in
207 		for (IResource resource : selection) {
208 			IProject project = resource.getProject();
209 			if (project != null) {
210 				set.removeSet(project);
211 			}
212 		}
213 		//the remainder of the projects in the disjoint set are unrelated to the selection
214 		List<IResource> projects = new ArrayList<>();
215 		set.toList(projects);
216 		return projects;
217 	}
218 
219 	@Override
getSelectedResources()220 	protected List<? extends IResource> getSelectedResources() {
221 		if (selectionDirty) {
222 			List<? extends IResource> newSelection = super.getSelectedResources();
223 			if (!oldSelection.equals(newSelection)) {
224 				oldSelection = newSelection;
225 				projectsToClose = computeRelated(newSelection);
226 			}
227 			selectionDirty = false;
228 		}
229 		return projectsToClose;
230 	}
231 
232 	/**
233 	 * Handles a resource changed event by updating the enablement
234 	 * when projects change.
235 	 * <p>
236 	 * This method overrides the super-type implementation to update
237 	 * the selection when the open state or description of any project changes.
238 	 */
239 	@Override
resourceChanged(IResourceChangeEvent event)240 	public void resourceChanged(IResourceChangeEvent event) {
241 		// don't bother looking at delta if selection not applicable
242 		if (selectionIsOfType(IResource.PROJECT)) {
243 			IResourceDelta delta = event.getDelta();
244 			if (delta != null) {
245 				IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED);
246 				for (IResourceDelta projDelta : projDeltas) {
247 					//changing either the description or the open state can affect enablement
248 					if ((projDelta.getFlags() & (IResourceDelta.OPEN | IResourceDelta.DESCRIPTION)) != 0) {
249 						selectionChanged(getStructuredSelection());
250 						return;
251 					}
252 				}
253 			}
254 		}
255 	}
256 }
257