/******************************************************************************* * Copyright (c) 2004, 2015 IBM Corporation 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: * IBM - Initial API and implementation * James Blackburn (Broadcom Corp.) - ongoing development * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 *******************************************************************************/ package org.eclipse.core.internal.refresh; import java.util.*; import org.eclipse.core.internal.events.ILifecycleListener; import org.eclipse.core.internal.events.LifecycleEvent; import org.eclipse.core.internal.resources.Workspace; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.internal.utils.Policy; import org.eclipse.core.resources.*; import org.eclipse.core.resources.refresh.IRefreshMonitor; import org.eclipse.core.resources.refresh.RefreshProvider; import org.eclipse.core.runtime.*; /** * Manages monitors by creating new monitors when projects are added and * removing monitors when projects are removed. Also handles the polling * mechanism when contributed native monitors cannot handle a project. * * @since 3.0 */ class MonitorManager implements ILifecycleListener, IPathVariableChangeListener, IResourceChangeListener, IResourceDeltaVisitor { /** * The PollingMonitor in charge of doing file-system polls. */ protected final PollingMonitor pollMonitor; /** * The list of registered monitor factories. This field is guarded by <code>this</code> as * it may be read and written by several threads. */ private RefreshProvider[] providers; /** * Reference to the refresh manager. */ protected final RefreshManager refreshManager; /** * A mapping of monitors to a list of resources each monitor is responsible for. */ protected final Map<IRefreshMonitor, List<IResource>> registeredMonitors; /** * Reference to the workspace. */ protected IWorkspace workspace; public MonitorManager(IWorkspace workspace, RefreshManager refreshManager) { this.workspace = workspace; this.refreshManager = refreshManager; registeredMonitors = Collections.synchronizedMap(new HashMap<IRefreshMonitor, List<IResource>>(10)); pollMonitor = new PollingMonitor(refreshManager); } /** * Queries extensions of the refreshProviders extension point, and * creates the provider classes. Will never return <code>null</code>. * * @return RefreshProvider[] The array of registered <code>RefreshProvider</code> * objects or an empty array. */ private RefreshProvider[] getRefreshProviders() { synchronized (this) { if (providers != null) return providers; } IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_REFRESH_PROVIDERS); IConfigurationElement[] infos = extensionPoint.getConfigurationElements(); List<RefreshProvider> providerList = new ArrayList<>(infos.length); for (int i = 0; i < infos.length; i++) { IConfigurationElement configurationElement = infos[i]; RefreshProvider provider = null; try { provider = (RefreshProvider) configurationElement.createExecutableExtension("class"); //$NON-NLS-1$ } catch (CoreException e) { Policy.log(IStatus.WARNING, Messages.refresh_installError, e); } if (provider != null) providerList.add(provider); } synchronized (this) { providers = providerList.toArray(new RefreshProvider[providerList.size()]); return providers; } } /** * Collects the set of root resources that required monitoring. This * includes projects and all linked resources. */ private List<IResource> getResourcesToMonitor() { final List<IResource> resourcesToMonitor = new ArrayList<>(10); IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); for (int i = 0; i < projects.length; i++) { if (!projects[i].isAccessible()) continue; resourcesToMonitor.add(projects[i]); try { IResource[] members = projects[i].members(); for (int j = 0; j < members.length; j++) { if (members[j].isLinked()) resourcesToMonitor.add(members[j]); } } catch (CoreException e) { Policy.log(IStatus.WARNING, Messages.refresh_refreshErr, e); } } return resourcesToMonitor; } @Override public void handleEvent(LifecycleEvent event) { switch (event.kind) { case LifecycleEvent.PRE_LINK_DELETE : case LifecycleEvent.PRE_PROJECT_CLOSE : case LifecycleEvent.PRE_PROJECT_DELETE : unmonitor(event.resource, new NullProgressMonitor()); break; } } private boolean isMonitoring(IResource resource) { synchronized (registeredMonitors) { for (List<IResource> resources : registeredMonitors.values()) { if ((resources != null) && (resources.contains(resource))) return true; } } return false; } /** * Installs a monitor on the given resource. Returns true if the polling * monitor was installed, and false if a refresh provider was installed. */ boolean monitor(IResource resource, IProgressMonitor progressMonitor) { if (isMonitoring(resource)) return false; boolean pollingMonitorNeeded = true; RefreshProvider[] refreshProviders = getRefreshProviders(); SubMonitor subMonitor = SubMonitor.convert(progressMonitor, refreshProviders.length); for (int i = 0; i < refreshProviders.length; i++) { IRefreshMonitor monitor = safeInstallMonitor(refreshProviders[i], resource, subMonitor.split(1)); if (monitor != null) { registerMonitor(monitor, resource); pollingMonitorNeeded = false; } } if (pollingMonitorNeeded) { pollMonitor.monitor(resource); registerMonitor(pollMonitor, resource); } return pollingMonitorNeeded; } /* (non-Javadoc) * @see IRefreshResult#monitorFailed */ public void monitorFailed(IRefreshMonitor monitor, IResource resource) { if (Policy.DEBUG_AUTO_REFRESH) Policy.debug(RefreshManager.DEBUG_PREFIX + " monitor (" + monitor + ") failed to monitor resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$ if (registeredMonitors == null || monitor == null) return; if (resource == null) { List<IResource> resources = registeredMonitors.get(monitor); if (resources == null || resources.isEmpty()) { registeredMonitors.remove(monitor); return; } // synchronized: protect the collection during iteration synchronized (registeredMonitors) { for (IResource resource1 : resources) { pollMonitor.monitor(resource1); registerMonitor(pollMonitor, resource1); } registeredMonitors.remove(monitor); } } else { removeMonitor(monitor, resource); pollMonitor.monitor(resource); registerMonitor(pollMonitor, resource); } } /** * @see IPathVariableChangeListener#pathVariableChanged(IPathVariableChangeEvent) */ @Override public void pathVariableChanged(IPathVariableChangeEvent event) { if (registeredMonitors.isEmpty()) return; String variableName = event.getVariableName(); final Set<IResource> invalidResources = new HashSet<>(); for (List<IResource> resources : registeredMonitors.values()) { for (IResource resource : resources) { IPath rawLocation = resource.getRawLocation(); if (rawLocation != null) { if (rawLocation.segmentCount() > 0 && variableName.equals(rawLocation.segment(0)) && !invalidResources.contains(resource)) { invalidResources.add(resource); } } } } if (!invalidResources.isEmpty()) { MonitorJob.createSystem(Messages.refresh_restoreOnInvalid, invalidResources, new ICoreRunnable() { @Override public void run(IProgressMonitor monitor) { SubMonitor subMonitor = SubMonitor.convert(monitor, invalidResources.size() * 2); for (IResource resource : invalidResources) { unmonitor(resource, subMonitor.split(1)); monitor(resource, subMonitor.split(1)); // Because the monitor is installed asynchronously we // may have missed some changes, we need to refresh it. refreshManager.refresh(resource); } } }).schedule(); } } private void registerMonitor(IRefreshMonitor monitor, IResource resource) { // synchronized: protect the collection during add synchronized (registeredMonitors) { List<IResource> resources = registeredMonitors.get(monitor); if (resources == null) { resources = new ArrayList<>(1); registeredMonitors.put(monitor, resources); } if (!resources.contains(resource)) resources.add(resource); } if (Policy.DEBUG_AUTO_REFRESH) Policy.debug(RefreshManager.DEBUG_PREFIX + " added monitor (" + monitor + ") on resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$ } private void removeMonitor(IRefreshMonitor monitor, IResource resource) { // synchronized: protect the collection during remove synchronized (registeredMonitors) { List<IResource> resources = registeredMonitors.get(monitor); if (resources != null && !resources.isEmpty()) { resources.remove(resource); } else { registeredMonitors.remove(monitor); } } if (Policy.DEBUG_AUTO_REFRESH) Policy.debug(RefreshManager.DEBUG_PREFIX + " removing monitor (" + monitor + ") on resource: " + resource); //$NON-NLS-1$ //$NON-NLS-2$ } private IRefreshMonitor safeInstallMonitor(RefreshProvider provider, IResource resource, IProgressMonitor progressMonitor) { Throwable t = null; try { return provider.installMonitor(resource, refreshManager, progressMonitor); } catch (Exception | LinkageError e) { t = e; } IStatus error = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, Messages.refresh_installError, t); Policy.log(error); return null; } /** * Start the monitoring of resources by all monitors. * @param progressMonitor */ public void start(IProgressMonitor progressMonitor) { List<IResource> resourcesToMonitor = getResourcesToMonitor(); SubMonitor subMonitor = SubMonitor.convert(progressMonitor, resourcesToMonitor.size() + 1); boolean refreshNeeded = false; for (IResource resource : resourcesToMonitor) { refreshNeeded |= !monitor(resource, subMonitor.split(1)); } workspace.getPathVariableManager().addChangeListener(this); workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); //adding the lifecycle listener twice does no harm ((Workspace) workspace).addLifecycleListener(this); if (Policy.DEBUG_AUTO_REFRESH) Policy.debug(RefreshManager.DEBUG_PREFIX + " starting monitor manager."); //$NON-NLS-1$ //If not exclusively using polling, create a polling monitor and run it once, to catch //changes that occurred while the native monitor was turned off. subMonitor.split(1); if (refreshNeeded) { new PollingMonitor(refreshManager).runOnce(); } } /** * Stop the monitoring of resources by all monitors. */ public void stop() { workspace.removeResourceChangeListener(this); workspace.getPathVariableManager().removeChangeListener(this); // synchronized: protect the collection during iteration synchronized (registeredMonitors) { for (IRefreshMonitor monitor : registeredMonitors.keySet()) { monitor.unmonitor(null); } } registeredMonitors.clear(); if (Policy.DEBUG_AUTO_REFRESH) Policy.debug(RefreshManager.DEBUG_PREFIX + " stopping monitor manager."); //$NON-NLS-1$ pollMonitor.cancel(); } void unmonitor(IResource resource, IProgressMonitor progressMonitor) { if (resource == null || !isMonitoring(resource)) return; SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 100); synchronized (registeredMonitors) { SubMonitor loopMonitor = subMonitor.split(90).setWorkRemaining(registeredMonitors.entrySet().size()); for (Map.Entry<IRefreshMonitor, List<IResource>> entry : registeredMonitors.entrySet()) { loopMonitor.worked(1); List<IResource> resources = entry.getValue(); if (resources != null && resources.contains(resource)) { entry.getKey().unmonitor(resource); resources.remove(resource); } } } if (resource.getType() == IResource.PROJECT) unmonitorLinkedContents((IProject) resource, subMonitor.split(10)); } private void unmonitorLinkedContents(IProject project, IProgressMonitor progressMonitor) { if (!project.isAccessible()) return; IResource[] children = null; try { children = project.members(); } catch (CoreException e) { Policy.log(IStatus.WARNING, Messages.refresh_refreshErr, e); } if (children != null && children.length > 0) { SubMonitor subMonitor = SubMonitor.convert(progressMonitor, children.length); for (int i = 0; i < children.length; i++) { if (children[i].isLinked()) { unmonitor(children[i], subMonitor.split(1)); } } } } @Override public void resourceChanged(IResourceChangeEvent event) { IResourceDelta delta = event.getDelta(); if (delta == null) return; try { delta.accept(this); } catch (CoreException e) { //cannot happen as our visitor doesn't throw exceptions } } @Override public boolean visit(IResourceDelta delta) { if (delta.getKind() == IResourceDelta.ADDED) { IResource resource = delta.getResource(); if (resource.isLinked()) monitor(resource, new NullProgressMonitor()); } if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { IProject project = (IProject) delta.getResource(); if (project.isAccessible()) monitor(project, new NullProgressMonitor()); } return true; } }