1 /*******************************************************************************
2  * Copyright (c) 2000, 2015 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  *******************************************************************************/
14 package org.eclipse.ui.model;
15 
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Iterator;
19 
20 import org.eclipse.core.resources.IContainer;
21 import org.eclipse.core.resources.IResource;
22 import org.eclipse.core.resources.IResourceChangeEvent;
23 import org.eclipse.core.resources.IResourceChangeListener;
24 import org.eclipse.core.resources.IResourceDelta;
25 import org.eclipse.core.resources.IWorkspace;
26 import org.eclipse.jface.viewers.AbstractTreeViewer;
27 import org.eclipse.jface.viewers.StructuredViewer;
28 import org.eclipse.jface.viewers.Viewer;
29 import org.eclipse.swt.widgets.Control;
30 
31 /**
32  * Tree content provider for resource objects that can be adapted to the
33  * interface {@link org.eclipse.ui.model.IWorkbenchAdapter IWorkbenchAdapter}.
34  * This provider will listen for resource changes within the workspace and
35  * update the viewer as necessary.
36  * <p>
37  * This class may be instantiated, or subclassed by clients.
38  * </p>
39  */
40 public class WorkbenchContentProvider extends BaseWorkbenchContentProvider
41 		implements IResourceChangeListener {
42 	private Viewer viewer;
43 
44 	/**
45 	 * Creates the resource content provider.
46 	 */
WorkbenchContentProvider()47 	public WorkbenchContentProvider() {
48 		super();
49 	}
50 
51 	@Override
dispose()52 	public void dispose() {
53 		if (viewer != null) {
54 			IWorkspace workspace = null;
55 			Object obj = viewer.getInput();
56 			if (obj instanceof IWorkspace) {
57 				workspace = (IWorkspace) obj;
58 			} else if (obj instanceof IContainer) {
59 				workspace = ((IContainer) obj).getWorkspace();
60 			}
61 			if (workspace != null) {
62 				workspace.removeResourceChangeListener(this);
63 			}
64 		}
65 
66 		super.dispose();
67 	}
68 
69 	@Override
inputChanged(Viewer viewer, Object oldInput, Object newInput)70 	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
71 		super.inputChanged(viewer, oldInput, newInput);
72 
73 		this.viewer = viewer;
74 		IWorkspace oldWorkspace = null;
75 		IWorkspace newWorkspace = null;
76 
77 		if (oldInput instanceof IWorkspace) {
78 			oldWorkspace = (IWorkspace) oldInput;
79 		} else if (oldInput instanceof IContainer) {
80 			oldWorkspace = ((IContainer) oldInput).getWorkspace();
81 		}
82 
83 		if (newInput instanceof IWorkspace) {
84 			newWorkspace = (IWorkspace) newInput;
85 		} else if (newInput instanceof IContainer) {
86 			newWorkspace = ((IContainer) newInput).getWorkspace();
87 		}
88 
89 		if (oldWorkspace != newWorkspace) {
90 			if (oldWorkspace != null) {
91 				oldWorkspace.removeResourceChangeListener(this);
92 			}
93 			if (newWorkspace != null) {
94 				newWorkspace.addResourceChangeListener(this,
95 						IResourceChangeEvent.POST_CHANGE);
96 			}
97 		}
98 	}
99 
100 	@Override
resourceChanged(final IResourceChangeEvent event)101 	public final void resourceChanged(final IResourceChangeEvent event) {
102 
103 		processDelta(event.getDelta());
104 
105 	}
106 
107 	/**
108 	 * Process the resource delta.
109 	 *
110 	 * @param delta delta to process; not <code>null</code>
111 	 */
processDelta(IResourceDelta delta)112 	protected void processDelta(IResourceDelta delta) {
113 
114 		Control ctrl = viewer.getControl();
115 		if (ctrl == null || ctrl.isDisposed()) {
116 			return;
117 		}
118 
119 
120 		final Collection runnables = new ArrayList();
121 		processDelta(delta, runnables);
122 
123 		if (runnables.isEmpty()) {
124 			return;
125 		}
126 
127 		//Are we in the UIThread? If so spin it until we are done
128 		if (ctrl.getDisplay().getThread() == Thread.currentThread()) {
129 			runUpdates(runnables);
130 		} else {
131 			ctrl.getDisplay().asyncExec(() -> {
132 				//Abort if this happens after disposes
133 				Control ctrl1 = viewer.getControl();
134 				if (ctrl1 == null || ctrl1.isDisposed()) {
135 					return;
136 				}
137 
138 				runUpdates(runnables);
139 			});
140 		}
141 
142 	}
143 
144 	/**
145 	 * Run all of the runnables that are the widget updates
146 	 * @param runnables
147 	 */
runUpdates(Collection runnables)148 	private void runUpdates(Collection runnables) {
149 		Iterator runnableIterator = runnables.iterator();
150 		while(runnableIterator.hasNext()){
151 			((Runnable)runnableIterator.next()).run();
152 		}
153 
154 	}
155 
156 	/**
157 	 * Process a resource delta. Add any runnables
158 	 */
processDelta(IResourceDelta delta, Collection runnables)159 	private void processDelta(IResourceDelta delta, Collection runnables) {
160 		//he widget may have been destroyed
161 		// by the time this is run. Check for this and do nothing if so.
162 		Control ctrl = viewer.getControl();
163 		if (ctrl == null || ctrl.isDisposed()) {
164 			return;
165 		}
166 
167 		// Get the affected resource
168 		final IResource resource = delta.getResource();
169 
170 		// If any children have changed type, just do a full refresh of this
171 		// parent,
172 		// since a simple update on such children won't work,
173 		// and trying to map the change to a remove and add is too dicey.
174 		// The case is: folder A renamed to existing file B, answering yes to
175 		// overwrite B.
176 		IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED);
177 		for (IResourceDelta affectedChild : affectedChildren) {
178 			if ((affectedChild.getFlags() & IResourceDelta.TYPE) != 0) {
179 				runnables.add(getRefreshRunnable(resource));
180 				return;
181 			}
182 		}
183 
184 		// Opening a project just affects icon, but we need to refresh when
185 		// a project is closed because if child items have not yet been created
186 		// in the tree we still need to update the item's children
187 		int changeFlags = delta.getFlags();
188 		if ((changeFlags & IResourceDelta.OPEN) != 0) {
189 			if (resource.isAccessible())  {
190 				runnables.add(getUpdateRunnable(resource));
191 			} else {
192 				runnables.add(getRefreshRunnable(resource));
193 				return;
194 			}
195 		}
196 		// Check the flags for changes the Navigator cares about.
197 		// See ResourceLabelProvider for the aspects it cares about.
198 		// Notice we don't care about F_CONTENT or F_MARKERS currently.
199 		if ((changeFlags & (IResourceDelta.SYNC
200 				| IResourceDelta.TYPE | IResourceDelta.DESCRIPTION)) != 0) {
201 			runnables.add(getUpdateRunnable(resource));
202 		}
203 		// Replacing a resource may affect its label and its children
204 		if ((changeFlags & IResourceDelta.REPLACED) != 0) {
205 			runnables.add(getRefreshRunnable(resource));
206 			return;
207 		}
208 
209 		// Handle changed children .
210 		for (IResourceDelta affectedChild : affectedChildren) {
211 			processDelta(affectedChild, runnables);
212 		}
213 
214 		// @issue several problems here:
215 		//  - should process removals before additions, to avoid multiple equal
216 		// elements in viewer
217 		//   - Kim: processing removals before additions was the indirect cause of
218 		// 44081 and its varients
219 		//   - Nick: no delta should have an add and a remove on the same element,
220 		// so processing adds first is probably OK
221 		//  - using setRedraw will cause extra flashiness
222 		//  - setRedraw is used even for simple changes
223 		//  - to avoid seeing a rename in two stages, should turn redraw on/off
224 		// around combined removal and addition
225 		//   - Kim: done, and only in the case of a rename (both remove and add
226 		// changes in one delta).
227 
228 		IResourceDelta[] addedChildren = delta
229 				.getAffectedChildren(IResourceDelta.ADDED);
230 		IResourceDelta[] removedChildren = delta
231 				.getAffectedChildren(IResourceDelta.REMOVED);
232 
233 		if (addedChildren.length == 0 && removedChildren.length == 0) {
234 			return;
235 		}
236 
237 		final Object[] addedObjects;
238 		final Object[] removedObjects;
239 
240 		// Process additions before removals as to not cause selection
241 		// preservation prior to new objects being added
242 		// Handle added children. Issue one update for all insertions.
243 		int numMovedFrom = 0;
244 		int numMovedTo = 0;
245 		if (addedChildren.length > 0) {
246 			addedObjects = new Object[addedChildren.length];
247 			for (int i = 0; i < addedChildren.length; i++) {
248 				addedObjects[i] = addedChildren[i].getResource();
249 				if ((addedChildren[i].getFlags() & IResourceDelta.MOVED_FROM) != 0) {
250 					++numMovedFrom;
251 				}
252 			}
253 		} else {
254 			addedObjects = new Object[0];
255 		}
256 
257 		// Handle removed children. Issue one update for all removals.
258 		if (removedChildren.length > 0) {
259 			removedObjects = new Object[removedChildren.length];
260 			for (int i = 0; i < removedChildren.length; i++) {
261 				removedObjects[i] = removedChildren[i].getResource();
262 				if ((removedChildren[i].getFlags() & IResourceDelta.MOVED_TO) != 0) {
263 					++numMovedTo;
264 				}
265 			}
266 		} else {
267 			removedObjects = new Object[0];
268 		}
269 		// heuristic test for items moving within same folder (i.e. renames)
270 		final boolean hasRename = numMovedFrom > 0 && numMovedTo > 0;
271 
272 		Runnable addAndRemove = () -> {
273 			if (viewer instanceof AbstractTreeViewer) {
274 				AbstractTreeViewer treeViewer = (AbstractTreeViewer) viewer;
275 				// Disable redraw until the operation is finished so we don't
276 				// get a flash of both the new and old item (in the case of
277 				// rename)
278 				// Only do this if we're both adding and removing files (the
279 				// rename case)
280 				if (hasRename) {
281 					treeViewer.getControl().setRedraw(false);
282 				}
283 				try {
284 					if (addedObjects.length > 0) {
285 						treeViewer.add(resource, addedObjects);
286 					}
287 					if (removedObjects.length > 0) {
288 						treeViewer.remove(removedObjects);
289 					}
290 				}
291 				finally {
292 					if (hasRename) {
293 						treeViewer.getControl().setRedraw(true);
294 					}
295 				}
296 			} else {
297 				((StructuredViewer) viewer).refresh(resource);
298 			}
299 		};
300 		runnables.add(addAndRemove);
301 	}
302 	/**
303 	 * Return a runnable for refreshing a resource.
304 	 * @param resource
305 	 * @return Runnable
306 	 */
getRefreshRunnable(final IResource resource)307 	private Runnable getRefreshRunnable(final IResource resource) {
308 		return () -> ((StructuredViewer) viewer).refresh(resource);
309 	}
310 
311 		/**
312 		 * Return a runnable for refreshing a resource.
313 		 * @param resource
314 		 * @return Runnable
315 		 */
getUpdateRunnable(final IResource resource)316 		private Runnable getUpdateRunnable(final IResource resource) {
317 			return () -> ((StructuredViewer) viewer).update(resource, null);
318 		}
319 }
320