1 /*******************************************************************************
2  * Copyright (c) 2004, 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 - Initial API and implementation
13  *     James Blackburn (Broadcom Corp.) - ongoing development
14  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
15  *******************************************************************************/
16 package org.eclipse.core.internal.refresh;
17 
18 import java.util.*;
19 import org.eclipse.core.internal.events.ILifecycleListener;
20 import org.eclipse.core.internal.events.LifecycleEvent;
21 import org.eclipse.core.internal.resources.Workspace;
22 import org.eclipse.core.internal.utils.Messages;
23 import org.eclipse.core.internal.utils.Policy;
24 import org.eclipse.core.resources.*;
25 import org.eclipse.core.resources.refresh.IRefreshMonitor;
26 import org.eclipse.core.resources.refresh.RefreshProvider;
27 import org.eclipse.core.runtime.*;
28 
29 /**
30  * Manages monitors by creating new monitors when projects are added and
31  * removing monitors when projects are removed. Also handles the polling
32  * mechanism when contributed native monitors cannot handle a project.
33  *
34  * @since 3.0
35  */
36 class MonitorManager implements ILifecycleListener, IPathVariableChangeListener, IResourceChangeListener, IResourceDeltaVisitor {
37 	/**
38 	 * The PollingMonitor in charge of doing file-system polls.
39 	 */
40 	protected final PollingMonitor pollMonitor;
41 	/**
42 	 * The list of registered monitor factories. This field is guarded by <code>this</code> as
43 	 * it may be read and written by several threads.
44 	 */
45 	private RefreshProvider[] providers;
46 	/**
47 	 * Reference to the refresh manager.
48 	 */
49 	protected final RefreshManager refreshManager;
50 	/**
51 	 * A mapping of monitors to a list of resources each monitor is responsible for.
52 	 */
53 	protected final Map<IRefreshMonitor, List<IResource>> registeredMonitors;
54 	/**
55 	 * Reference to the workspace.
56 	 */
57 	protected IWorkspace workspace;
58 
MonitorManager(IWorkspace workspace, RefreshManager refreshManager)59 	public MonitorManager(IWorkspace workspace, RefreshManager refreshManager) {
60 		this.workspace = workspace;
61 		this.refreshManager = refreshManager;
62 		registeredMonitors = Collections.synchronizedMap(new HashMap<>(10));
63 		pollMonitor = new PollingMonitor(refreshManager);
64 	}
65 
66 	/**
67 	 * Queries extensions of the refreshProviders extension point, and
68 	 * creates the provider classes. Will never return <code>null</code>.
69 	 *
70 	 * @return RefreshProvider[] The array of registered <code>RefreshProvider</code>
71 	 *             objects or an empty array.
72 	 */
getRefreshProviders()73 	private RefreshProvider[] getRefreshProviders() {
74 		synchronized (this) {
75 			if (providers != null)
76 				return providers;
77 		}
78 		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_REFRESH_PROVIDERS);
79 		IConfigurationElement[] infos = extensionPoint.getConfigurationElements();
80 		List<RefreshProvider> providerList = new ArrayList<>(infos.length);
81 		for (IConfigurationElement configurationElement : infos) {
82 			RefreshProvider provider = null;
83 			try {
84 				provider = (RefreshProvider) configurationElement.createExecutableExtension("class"); //$NON-NLS-1$
85 			} catch (CoreException e) {
86 				Policy.log(IStatus.WARNING, Messages.refresh_installError, e);
87 			}
88 			if (provider != null)
89 				providerList.add(provider);
90 		}
91 		synchronized (this) {
92 			providers = providerList.toArray(new RefreshProvider[providerList.size()]);
93 			return providers;
94 		}
95 	}
96 
97 	/**
98 	 * Collects the set of root resources that required monitoring. This
99 	 * includes projects and all linked resources.
100 	 */
getResourcesToMonitor()101 	private List<IResource> getResourcesToMonitor() {
102 		final List<IResource> resourcesToMonitor = new ArrayList<>(10);
103 		IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
104 		for (IProject project : projects) {
105 			if (!project.isAccessible()) {
106 				continue;
107 			}
108 			resourcesToMonitor.add(project);
109 			try {
110 				IResource[] members = project.members();
111 				for (IResource member : members) {
112 					if (member.isLinked())
113 						resourcesToMonitor.add(member);
114 				}
115 			}catch (CoreException e) {
116 				Policy.log(IStatus.WARNING, Messages.refresh_refreshErr, e);
117 			}
118 		}
119 		return resourcesToMonitor;
120 	}
121 
122 	@Override
handleEvent(LifecycleEvent event)123 	public void handleEvent(LifecycleEvent event) {
124 		switch (event.kind) {
125 			case LifecycleEvent.PRE_LINK_DELETE :
126 			case LifecycleEvent.PRE_PROJECT_CLOSE :
127 			case LifecycleEvent.PRE_PROJECT_DELETE :
128 				unmonitor(event.resource, new NullProgressMonitor());
129 				break;
130 		}
131 	}
132 
isMonitoring(IResource resource)133 	private boolean isMonitoring(IResource resource) {
134 		synchronized (registeredMonitors) {
135 			for (List<IResource> resources : registeredMonitors.values()) {
136 				if ((resources != null) && (resources.contains(resource)))
137 					return true;
138 			}
139 		}
140 		return false;
141 	}
142 
143 	/**
144 	 * Installs a monitor on the given resource. Returns true if the polling
145 	 * monitor was installed, and false if a refresh provider was installed.
146 	 */
monitor(IResource resource, IProgressMonitor progressMonitor)147 	boolean monitor(IResource resource, IProgressMonitor progressMonitor) {
148 		if (isMonitoring(resource))
149 			return false;
150 		boolean pollingMonitorNeeded = true;
151 		RefreshProvider[] refreshProviders = getRefreshProviders();
152 		SubMonitor subMonitor = SubMonitor.convert(progressMonitor, refreshProviders.length);
153 		for (RefreshProvider refreshProvider : refreshProviders) {
154 			IRefreshMonitor monitor = safeInstallMonitor(refreshProvider, resource, subMonitor.split(1));
155 			if (monitor != null) {
156 				registerMonitor(monitor, resource);
157 				pollingMonitorNeeded = false;
158 			}
159 		}
160 		if (pollingMonitorNeeded) {
161 			pollMonitor.monitor(resource);
162 			registerMonitor(pollMonitor, resource);
163 		}
164 		return pollingMonitorNeeded;
165 	}
166 
167 	/* (non-Javadoc)
168 	 * @see IRefreshResult#monitorFailed
169 	 */
monitorFailed(IRefreshMonitor monitor, IResource resource)170 	public void monitorFailed(IRefreshMonitor monitor, IResource resource) {
171 		if (Policy.DEBUG_AUTO_REFRESH)
172 			Policy.debug(RefreshManager.DEBUG_PREFIX + " monitor (" + monitor + ") failed to monitor resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$
173 		if (registeredMonitors == null || monitor == null)
174 			return;
175 		if (resource == null) {
176 			List<IResource> resources = registeredMonitors.get(monitor);
177 			if (resources == null || resources.isEmpty()) {
178 				registeredMonitors.remove(monitor);
179 				return;
180 			}
181 			// synchronized: protect the collection during iteration
182 			synchronized (registeredMonitors) {
183 				for (IResource resource1 : resources) {
184 					pollMonitor.monitor(resource1);
185 					registerMonitor(pollMonitor, resource1);
186 				}
187 				registeredMonitors.remove(monitor);
188 			}
189 		} else {
190 			removeMonitor(monitor, resource);
191 			pollMonitor.monitor(resource);
192 			registerMonitor(pollMonitor, resource);
193 		}
194 	}
195 
196 	/**
197 	 * @see IPathVariableChangeListener#pathVariableChanged(IPathVariableChangeEvent)
198 	 */
199 	@Override
pathVariableChanged(IPathVariableChangeEvent event)200 	public void pathVariableChanged(IPathVariableChangeEvent event) {
201 		if (registeredMonitors.isEmpty())
202 			return;
203 		String variableName = event.getVariableName();
204 		final Set<IResource> invalidResources = new HashSet<>();
205 		for (List<IResource> resources : registeredMonitors.values()) {
206 			for (IResource resource : resources) {
207 				IPath rawLocation = resource.getRawLocation();
208 				if (rawLocation != null) {
209 					if (rawLocation.segmentCount() > 0 && variableName.equals(rawLocation.segment(0)) && !invalidResources.contains(resource)) {
210 						invalidResources.add(resource);
211 					}
212 				}
213 			}
214 		}
215 		if (!invalidResources.isEmpty()) {
216 			MonitorJob.createSystem(Messages.refresh_restoreOnInvalid, invalidResources, (ICoreRunnable) monitor -> {
217 				SubMonitor subMonitor = SubMonitor.convert(monitor, invalidResources.size() * 2);
218 				for (IResource resource : invalidResources) {
219 					unmonitor(resource, subMonitor.split(1));
220 					monitor(resource, subMonitor.split(1));
221 					// Because the monitor is installed asynchronously we
222 					// may have missed some changes, we need to refresh it.
223 					refreshManager.refresh(resource);
224 				}
225 			}).schedule();
226 		}
227 	}
228 
registerMonitor(IRefreshMonitor monitor, IResource resource)229 	private void registerMonitor(IRefreshMonitor monitor, IResource resource) {
230 		// synchronized: protect the collection during add
231 		synchronized (registeredMonitors) {
232 			List<IResource> resources = registeredMonitors.get(monitor);
233 			if (resources == null) {
234 				resources = new ArrayList<>(1);
235 				registeredMonitors.put(monitor, resources);
236 			}
237 			if (!resources.contains(resource))
238 				resources.add(resource);
239 		}
240 		if (Policy.DEBUG_AUTO_REFRESH)
241 			Policy.debug(RefreshManager.DEBUG_PREFIX + " added monitor (" + monitor + ") on resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$
242 	}
243 
removeMonitor(IRefreshMonitor monitor, IResource resource)244 	private void removeMonitor(IRefreshMonitor monitor, IResource resource) {
245 		// synchronized: protect the collection during remove
246 		synchronized (registeredMonitors) {
247 			List<IResource> resources = registeredMonitors.get(monitor);
248 			if (resources != null && !resources.isEmpty()) {
249 				resources.remove(resource);
250 			} else {
251 				registeredMonitors.remove(monitor);
252 			}
253 		}
254 		if (Policy.DEBUG_AUTO_REFRESH)
255 			Policy.debug(RefreshManager.DEBUG_PREFIX + " removing monitor (" + monitor + ") on resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$
256 	}
257 
safeInstallMonitor(RefreshProvider provider, IResource resource, IProgressMonitor progressMonitor)258 	private IRefreshMonitor safeInstallMonitor(RefreshProvider provider, IResource resource, IProgressMonitor progressMonitor) {
259 		Throwable t = null;
260 		try {
261 			return provider.installMonitor(resource, refreshManager, progressMonitor);
262 		} catch (Exception | LinkageError e) {
263 			t = e;
264 		}
265 		IStatus error = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, Messages.refresh_installError, t);
266 		Policy.log(error);
267 		return null;
268 	}
269 
270 	/**
271 	 * Start the monitoring of resources by all monitors.
272 	 * @param progressMonitor
273 	 */
start(IProgressMonitor progressMonitor)274 	public void start(IProgressMonitor progressMonitor) {
275 		List<IResource> resourcesToMonitor = getResourcesToMonitor();
276 		SubMonitor subMonitor = SubMonitor.convert(progressMonitor, resourcesToMonitor.size() + 1);
277 		boolean refreshNeeded = false;
278 		for (IResource resource : resourcesToMonitor) {
279 			refreshNeeded |= !monitor(resource, subMonitor.split(1));
280 		}
281 		workspace.getPathVariableManager().addChangeListener(this);
282 		workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
283 		//adding the lifecycle listener twice does no harm
284 		((Workspace) workspace).addLifecycleListener(this);
285 		if (Policy.DEBUG_AUTO_REFRESH)
286 			Policy.debug(RefreshManager.DEBUG_PREFIX + " starting monitor manager."); //$NON-NLS-1$
287 		//If not exclusively using polling, create a polling monitor and run it once, to catch
288 		//changes that occurred while the native monitor was turned off.
289 		subMonitor.split(1);
290 		if (refreshNeeded) {
291 			new PollingMonitor(refreshManager).runOnce();
292 		}
293 	}
294 
295 	/**
296 	 * Stop the monitoring of resources by all monitors.
297 	 */
stop()298 	public void stop() {
299 		workspace.removeResourceChangeListener(this);
300 		workspace.getPathVariableManager().removeChangeListener(this);
301 		// synchronized: protect the collection during iteration
302 		synchronized (registeredMonitors) {
303 			for (IRefreshMonitor monitor : registeredMonitors.keySet()) {
304 				monitor.unmonitor(null);
305 			}
306 		}
307 		registeredMonitors.clear();
308 		if (Policy.DEBUG_AUTO_REFRESH)
309 			Policy.debug(RefreshManager.DEBUG_PREFIX + " stopping monitor manager."); //$NON-NLS-1$
310 		pollMonitor.cancel();
311 	}
312 
unmonitor(IResource resource, IProgressMonitor progressMonitor)313 	void unmonitor(IResource resource, IProgressMonitor progressMonitor) {
314 		if (resource == null || !isMonitoring(resource))
315 			return;
316 		SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 100);
317 		synchronized (registeredMonitors) {
318 			SubMonitor loopMonitor = subMonitor.split(90).setWorkRemaining(registeredMonitors.entrySet().size());
319 			for (Map.Entry<IRefreshMonitor, List<IResource>> entry : registeredMonitors.entrySet()) {
320 				loopMonitor.worked(1);
321 				List<IResource> resources = entry.getValue();
322 				if (resources != null && resources.contains(resource)) {
323 					entry.getKey().unmonitor(resource);
324 					resources.remove(resource);
325 				}
326 			}
327 		}
328 		if (resource.getType() == IResource.PROJECT)
329 			unmonitorLinkedContents((IProject) resource, subMonitor.split(10));
330 	}
331 
unmonitorLinkedContents(IProject project, IProgressMonitor progressMonitor)332 	private void unmonitorLinkedContents(IProject project, IProgressMonitor progressMonitor) {
333 		if (!project.isAccessible())
334 			return;
335 		IResource[] children = null;
336 		try {
337 			children = project.members();
338 		} catch (CoreException e) {
339 			Policy.log(IStatus.WARNING, Messages.refresh_refreshErr, e);
340 		}
341 		if (children != null && children.length > 0) {
342 			SubMonitor subMonitor = SubMonitor.convert(progressMonitor, children.length);
343 			for (IResource child : children) {
344 				if (child.isLinked()) {
345 					unmonitor(child, subMonitor.split(1));
346 				}
347 			}
348 		}
349 	}
350 
351 	@Override
resourceChanged(IResourceChangeEvent event)352 	public void resourceChanged(IResourceChangeEvent event) {
353 		IResourceDelta delta = event.getDelta();
354 		if (delta == null)
355 			return;
356 		try {
357 			delta.accept(this);
358 		} catch (CoreException e) {
359 			//cannot happen as our visitor doesn't throw exceptions
360 		}
361 	}
362 
363 	@Override
visit(IResourceDelta delta)364 	public boolean visit(IResourceDelta delta) {
365 		if (delta.getKind() == IResourceDelta.ADDED) {
366 			IResource resource = delta.getResource();
367 			if (resource.isLinked())
368 				monitor(resource, new NullProgressMonitor());
369 		}
370 		if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
371 			IProject project = (IProject) delta.getResource();
372 			if (project.isAccessible())
373 				monitor(project, new NullProgressMonitor());
374 		}
375 		return true;
376 	}
377 
378 }
379