KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > events > NotificationManager


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.core.internal.events;
12
13 import java.util.*;
14 import org.eclipse.core.internal.resources.IManager;
15 import org.eclipse.core.internal.resources.Workspace;
16 import org.eclipse.core.internal.utils.Messages;
17 import org.eclipse.core.internal.watson.ElementTree;
18 import org.eclipse.core.resources.*;
19 import org.eclipse.core.runtime.*;
20 import org.eclipse.core.runtime.jobs.Job;
21
22 public class NotificationManager implements IManager, ILifecycleListener {
23     class NotifyJob extends Job {
24         private final IWorkspaceRunnable noop = new IWorkspaceRunnable() {
25             public void run(IProgressMonitor monitor) {
26             }
27         };
28
29         public NotifyJob() {
30             super(Messages.resources_updating);
31             setSystem(true);
32         }
33
34         public IStatus run(IProgressMonitor monitor) {
35             if (monitor.isCanceled())
36                 return Status.CANCEL_STATUS;
37             notificationRequested = true;
38             try {
39                 workspace.run(noop, null, IResource.NONE, null);
40             } catch (CoreException e) {
41                 return e.getStatus();
42             }
43             return Status.OK_STATUS;
44         }
45     }
46
47     private static final long NOTIFICATION_DELAY = 1500;
48     /**
49      * The Threads that are currently avoiding notification.
50      */

51     private Set avoidNotify = new HashSet();
52
53     /**
54      * Indicates whether a notification is currently in progress. Used to avoid
55      * causing a notification to be requested as a result of another notification.
56      */

57     protected boolean isNotifying;
58
59     // if there are no changes between the current tree and the last delta state then we
60
// can reuse the lastDelta (if any). If the lastMarkerChangeId is different then the current
61
// one then we have to update that delta with new marker change info
62
/**
63      * last delta we broadcast
64      */

65     private ResourceDelta lastDelta;
66     /**
67      * the marker change Id the last time we computed a delta
68      */

69     private long lastDeltaId;
70     /**
71      * tree the last time we computed a delta
72      */

73     private ElementTree lastDeltaState;
74     protected long lastNotifyDuration = 0L;
75     /**
76      * the marker change id at the end of the last POST_AUTO_BUILD
77      */

78     private long lastPostBuildId = 0;
79     /**
80      * The state of the workspace at the end of the last POST_BUILD
81      * notification
82      */

83     private ElementTree lastPostBuildTree;
84     /**
85      * the marker change id at the end of the last POST_CHANGE
86      */

87     private long lastPostChangeId = 0;
88     /**
89      * The state of the workspace at the end of the last POST_CHANGE
90      * notification
91      */

92     private ElementTree lastPostChangeTree;
93
94     private ResourceChangeListenerList listeners;
95
96     protected boolean notificationRequested = false;
97     private Job notifyJob;
98     Workspace workspace;
99
100     public NotificationManager(Workspace workspace) {
101         this.workspace = workspace;
102         listeners = new ResourceChangeListenerList();
103         notifyJob = new NotifyJob();
104     }
105
106     public void addListener(IResourceChangeListener listener, int eventMask) {
107         listeners.add(listener, eventMask);
108         if (ResourceStats.TRACE_LISTENERS)
109             ResourceStats.listenerAdded(listener);
110     }
111
112     /**
113      * Indicates the beginning of a block where periodic notifications should be avoided.
114      * Returns true if notification avoidance really started, and false for nested
115      * operations.
116      */

117     public boolean beginAvoidNotify() {
118         return avoidNotify.add(Thread.currentThread());
119     }
120
121     /**
122      * Signals the beginning of the notification phase at the end of a top level operation.
123      */

124     public void beginNotify() {
125         notifyJob.cancel();
126         notificationRequested = false;
127     }
128
129     /**
130      * The main broadcast point for notification deltas
131      */

132     public void broadcastChanges(ElementTree lastState, ResourceChangeEvent event, boolean lockTree) {
133         final int type = event.getType();
134         try {
135             // Do the notification if there are listeners for events of the given type.
136
if (!listeners.hasListenerFor(type))
137                 return;
138             isNotifying = true;
139             ResourceDelta delta = getDelta(lastState, type);
140             //don't broadcast POST_CHANGE or autobuild events if the delta is empty
141
if (delta == null || delta.getKind() == 0) {
142                 int trigger = event.getBuildKind();
143                 if (trigger == IncrementalProjectBuilder.AUTO_BUILD || trigger == 0)
144                     return;
145             }
146             event.setDelta(delta);
147             long start = System.currentTimeMillis();
148             notify(getListeners(), event, lockTree);
149             lastNotifyDuration = System.currentTimeMillis() - start;
150         } finally {
151             // Update the state regardless of whether people are listening.
152
isNotifying = false;
153             cleanUp(lastState, type);
154         }
155     }
156
157     /**
158      * Performs cleanup at the end of a resource change notification
159      */

160     private void cleanUp(ElementTree lastState, int type) {
161         // Remember the current state as the last notified state if requested.
162
// Be sure to clear out the old delta
163
boolean postChange = type == IResourceChangeEvent.POST_CHANGE;
164         if (postChange || type == IResourceChangeEvent.POST_BUILD) {
165             long id = workspace.getMarkerManager().getChangeId();
166             lastState.immutable();
167             if (postChange) {
168                 lastPostChangeTree = lastState;
169                 lastPostChangeId = id;
170             } else {
171                 lastPostBuildTree = lastState;
172                 lastPostBuildId = id;
173             }
174             workspace.getMarkerManager().resetMarkerDeltas(Math.min(lastPostBuildId, lastPostChangeId));
175             lastDelta = null;
176             lastDeltaState = lastState;
177         }
178     }
179
180     /**
181      * Helper method for the save participant lifecycle computation. */

182     public void broadcastChanges(IResourceChangeListener listener, int type, IResourceDelta delta) {
183         ResourceChangeListenerList.ListenerEntry[] entries;
184         entries = new ResourceChangeListenerList.ListenerEntry[] {new ResourceChangeListenerList.ListenerEntry(listener, type)};
185         notify(entries, new ResourceChangeEvent(workspace, type, 0, delta), false);
186     }
187
188     /**
189      * Indicates the end of a block where periodic notifications should be avoided.
190      */

191     public void endAvoidNotify() {
192         avoidNotify.remove(Thread.currentThread());
193     }
194
195     /**
196      * Requests that a periodic notification be scheduled
197      */

198     public void requestNotify() {
199         //don't do intermediate notifications if the current thread doesn't want them
200
if (isNotifying || avoidNotify.contains(Thread.currentThread()))
201             return;
202         //notifications must never take more than one tenth of operation time
203
long delay = Math.max(NOTIFICATION_DELAY, lastNotifyDuration * 10);
204         if (notifyJob.getState() == Job.NONE)
205             notifyJob.schedule(delay);
206     }
207
208     /**
209      * Computes and returns the resource delta for the given event type and the
210      * given current tree state.
211      */

212     protected ResourceDelta getDelta(ElementTree tree, int type) {
213         long id = workspace.getMarkerManager().getChangeId();
214         // If we have a delta from last time and no resources have changed
215
// since then, we can reuse the delta structure.
216
// However, be sure not to mix deltas from post_change with build events, because they use
217
// a different reference point for delta computation.
218
boolean postChange = type == IResourceChangeEvent.POST_CHANGE;
219         if (!postChange && lastDelta != null && !ElementTree.hasChanges(tree, lastDeltaState, ResourceComparator.getNotificationComparator(), true)) {
220             // Markers may have changed since the delta was generated. If so, get the new
221
// marker state and insert it in to the delta which is being reused.
222
if (id != lastDeltaId) {
223                 Map markerDeltas = workspace.getMarkerManager().getMarkerDeltas(lastPostBuildId);
224                 lastDelta.updateMarkers(markerDeltas);
225             }
226         } else {
227             // We don't have a delta or something changed so recompute the whole deal.
228
ElementTree oldTree = postChange ? lastPostChangeTree : lastPostBuildTree;
229             long markerId = postChange ? lastPostChangeId : lastPostBuildId;
230             lastDelta = ResourceDeltaFactory.computeDelta(workspace, oldTree, tree, Path.ROOT, markerId + 1);
231         }
232         // remember the state of the world when this delta was consistent
233
lastDeltaState = tree;
234         lastDeltaId = id;
235         return lastDelta;
236     }
237
238     protected ResourceChangeListenerList.ListenerEntry[] getListeners() {
239         return listeners.getListeners();
240     }
241
242     public void handleEvent(LifecycleEvent event) {
243         switch (event.kind) {
244             case LifecycleEvent.PRE_PROJECT_CLOSE :
245                 if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_CLOSE))
246                     return;
247                 IProject project = (IProject) event.resource;
248                 notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_CLOSE, project), true);
249                 break;
250             case LifecycleEvent.PRE_PROJECT_MOVE :
251                 //only notify deletion on move if old project handle is going
252
// away
253
if (event.resource.equals(event.newResource))
254                     return;
255                 //fall through
256
case LifecycleEvent.PRE_PROJECT_DELETE :
257                 if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_DELETE))
258                     return;
259                 project = (IProject) event.resource;
260                 notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_DELETE, project), true);
261                 break;
262         }
263     }
264
265     private void notify(ResourceChangeListenerList.ListenerEntry[] resourceListeners, final IResourceChangeEvent event, final boolean lockTree) {
266         int type = event.getType();
267         boolean oldLock = workspace.isTreeLocked();
268         if (lockTree)
269             workspace.setTreeLocked(true);
270         try {
271             for (int i = 0; i < resourceListeners.length; i++) {
272                 if ((type & resourceListeners[i].eventMask) != 0) {
273                     final IResourceChangeListener listener = resourceListeners[i].listener;
274                     if (ResourceStats.TRACE_LISTENERS)
275                         ResourceStats.startNotify(listener);
276                     SafeRunner.run(new ISafeRunnable() {
277                         public void handleException(Throwable JavaDoc e) {
278                             // exception logged in SafeRunner#run
279
}
280
281                         public void run() throws Exception JavaDoc {
282                             listener.resourceChanged(event);
283                         }
284                     });
285                     if (ResourceStats.TRACE_LISTENERS)
286                         ResourceStats.endNotify();
287                 }
288             }
289         } finally {
290             if (lockTree)
291                 workspace.setTreeLocked(oldLock);
292         }
293     }
294
295     public void removeListener(IResourceChangeListener listener) {
296         listeners.remove(listener);
297         if (ResourceStats.TRACE_LISTENERS)
298             ResourceStats.listenerRemoved(listener);
299     }
300
301     /**
302      * Returns true if a notification is needed. This happens if
303      * sufficient time has elapsed since the last notification
304      * @return true if a notification is needed, and false otherwise
305      */

306     public boolean shouldNotify() {
307         return !isNotifying && notificationRequested;
308     }
309
310     public void shutdown(IProgressMonitor monitor) {
311         //wipe out any existing listeners
312
listeners = new ResourceChangeListenerList();
313     }
314
315     public void startup(IProgressMonitor monitor) {
316         // get the current state of the workspace as the starting point and
317
// tell the workspace to track changes from there. This gives the
318
// notification manager an initial basis for comparison.
319
lastPostBuildTree = lastPostChangeTree = workspace.getElementTree();
320         workspace.addLifecycleListener(this);
321     }
322 }
323
Popular Tags