package org.eclipse.core.internal.events;
import java.util.*;
import org.eclipse.core.internal.events.ResourceChangeListenerList.ListenerEntry;
import org.eclipse.core.internal.resources.*;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.internal.watson.ElementTree;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
public class NotificationManager implements IManager, ILifecycleListener {
class NotifyJob extends Job {
private final ICoreRunnable noop = monitor -> {
};
public NotifyJob() {
super(Messages.resources_updating);
setSystem(true);
}
@Override
public IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
notificationRequested = true;
try {
workspace.run(noop, null, IResource.NONE, null);
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return NotificationManager.class == family;
}
}
private static final long NOTIFICATION_DELAY = 1500;
private final Set<Thread> avoidNotify = Collections.synchronizedSet(new HashSet<>());
protected boolean isNotifying;
private ResourceDelta lastDelta;
private long lastDeltaId;
private ElementTree lastDeltaState;
protected long lastNotifyDuration = 0L;
private long lastPostBuildId = 0;
private ElementTree lastPostBuildTree;
private long lastPostChangeId = 0;
private ElementTree lastPostChangeTree;
private ResourceChangeListenerList listeners;
protected volatile boolean notificationRequested = false;
private Job notifyJob;
Workspace workspace;
public NotificationManager(Workspace workspace) {
this.workspace = workspace;
listeners = new ResourceChangeListenerList();
notifyJob = new NotifyJob();
}
public void addListener(IResourceChangeListener listener, int eventMask) {
listeners.add(listener, eventMask);
if (ResourceStats.TRACE_LISTENERS)
ResourceStats.listenerAdded(listener);
}
public boolean beginAvoidNotify() {
return avoidNotify.add(Thread.currentThread());
}
public void beginNotify() {
notifyJob.cancel();
notificationRequested = false;
}
public void broadcastChanges(ElementTree lastState, ResourceChangeEvent event, boolean lockTree) {
final int type = event.getType();
try {
if (!listeners.hasListenerFor(type))
return;
isNotifying = true;
ResourceDelta delta = getDelta(lastState, type);
if (delta == null || delta.getKind() == 0) {
int trigger = event.getBuildKind();
if (trigger == IncrementalProjectBuilder.AUTO_BUILD || trigger == 0)
return;
}
event.setDelta(delta);
long start = System.currentTimeMillis();
notify(getListeners(), event, lockTree);
lastNotifyDuration = System.currentTimeMillis() - start;
} finally {
isNotifying = false;
cleanUp(lastState, type);
}
}
private void cleanUp(ElementTree lastState, int type) {
boolean postChange = type == IResourceChangeEvent.POST_CHANGE;
if (postChange || type == IResourceChangeEvent.POST_BUILD) {
long id = workspace.getMarkerManager().getChangeId();
lastState.immutable();
if (postChange) {
lastPostChangeTree = lastState;
lastPostChangeId = id;
} else {
lastPostBuildTree = lastState;
lastPostBuildId = id;
}
workspace.getMarkerManager().resetMarkerDeltas(Math.min(lastPostBuildId, lastPostChangeId));
lastDelta = null;
lastDeltaState = lastState;
}
}
public void broadcastChanges(IResourceChangeListener listener, int type, IResourceDelta delta) {
ResourceChangeListenerList.ListenerEntry[] entries;
entries = new ResourceChangeListenerList.ListenerEntry[] {new ResourceChangeListenerList.ListenerEntry(listener, type)};
notify(entries, new ResourceChangeEvent(workspace, type, 0, delta), false);
}
public void endAvoidNotify() {
avoidNotify.remove(Thread.currentThread());
}
public void requestNotify() {
if (isNotifying || avoidNotify.contains(Thread.currentThread()))
return;
long delay = Math.max(NOTIFICATION_DELAY, lastNotifyDuration * 10);
if (notifyJob.getState() == Job.NONE)
notifyJob.schedule(delay);
}
protected ResourceDelta getDelta(ElementTree tree, int type) {
long id = workspace.getMarkerManager().getChangeId();
boolean postChange = type == IResourceChangeEvent.POST_CHANGE;
if (!postChange && lastDelta != null && !ElementTree.hasChanges(tree, lastDeltaState, ResourceComparator.getNotificationComparator(), true)) {
if (id != lastDeltaId) {
Map<IPath, MarkerSet> markerDeltas = workspace.getMarkerManager().getMarkerDeltas(lastPostBuildId);
lastDelta.updateMarkers(markerDeltas);
}
} else {
ElementTree oldTree = postChange ? lastPostChangeTree : lastPostBuildTree;
long markerId = postChange ? lastPostChangeId : lastPostBuildId;
lastDelta = ResourceDeltaFactory.computeDelta(workspace, oldTree, tree, Path.ROOT, markerId + 1);
}
lastDeltaState = tree;
lastDeltaId = id;
return lastDelta;
}
protected ResourceChangeListenerList.ListenerEntry[] getListeners() {
return listeners.getListeners();
}
@Override
public void handleEvent(LifecycleEvent event) {
switch (event.kind) {
case LifecycleEvent.PRE_PROJECT_CLOSE :
if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_CLOSE))
return;
IProject project = (IProject) event.resource;
notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_CLOSE, project), true);
break;
case LifecycleEvent.PRE_PROJECT_MOVE :
if (event.resource.equals(event.newResource))
return;
case LifecycleEvent.PRE_PROJECT_DELETE :
if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_DELETE))
return;
project = (IProject) event.resource;
notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_DELETE, project), true);
break;
case LifecycleEvent.PRE_REFRESH :
if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_REFRESH))
return;
if (event.resource.getType() == IResource.PROJECT)
notify(getListeners(), new ResourceChangeEvent(event.resource, IResourceChangeEvent.PRE_REFRESH, event.resource), true);
else if (event.resource.getType() == IResource.ROOT)
notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_REFRESH, null), true);
break;
}
}
private void notify(ResourceChangeListenerList.ListenerEntry[] resourceListeners, final ResourceChangeEvent event, final boolean lockTree) {
int type = event.getType();
boolean oldLock = workspace.isTreeLocked();
if (lockTree)
workspace.setTreeLocked(true);
try {
for (ListenerEntry resourceListener : resourceListeners) {
if ((type & resourceListener.eventMask) != 0) {
final IResourceChangeListener listener = resourceListener.listener;
if (ResourceStats.TRACE_LISTENERS)
ResourceStats.startNotify(listener);
SafeRunner.run(new ISafeRunnable() {
@Override
public void handleException(Throwable e) {
}
@Override
public void run() throws Exception {
if (Policy.DEBUG_NOTIFICATIONS)
Policy.debug("Notifying " + listener.getClass().getName() + " about resource change event" + event.toDebugString());
listener.resourceChanged(event);
}
});
if (ResourceStats.TRACE_LISTENERS)
ResourceStats.endNotify();
}
}
} finally {
if (lockTree)
workspace.setTreeLocked(oldLock);
}
}
public void removeListener(IResourceChangeListener listener) {
listeners.remove(listener);
if (ResourceStats.TRACE_LISTENERS)
ResourceStats.listenerRemoved(listener);
}
public boolean shouldNotify() {
return !isNotifying && notificationRequested;
}
@Override
public void shutdown(IProgressMonitor monitor) {
listeners = new ResourceChangeListenerList();
}
@Override
public void startup(IProgressMonitor monitor) {
lastPostBuildTree = lastPostChangeTree = workspace.getElementTree();
workspace.addLifecycleListener(this);
}
}