|
1 /******************************************************************************* |
|
2 * Copyright (c) 2002, 2007 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 - Initial API and implementation |
|
10 *******************************************************************************/ |
|
11 package org.eclipse.core.internal.resources.refresh.win32; |
|
12 |
|
13 import java.io.File; |
|
14 import java.util.*; |
|
15 import org.eclipse.core.internal.refresh.RefreshManager; |
|
16 import org.eclipse.core.internal.utils.Messages; |
|
17 import org.eclipse.core.resources.IResource; |
|
18 import org.eclipse.core.resources.ResourcesPlugin; |
|
19 import org.eclipse.core.resources.refresh.IRefreshMonitor; |
|
20 import org.eclipse.core.resources.refresh.IRefreshResult; |
|
21 import org.eclipse.core.runtime.*; |
|
22 import org.eclipse.core.runtime.jobs.Job; |
|
23 import org.eclipse.osgi.util.NLS; |
|
24 import org.osgi.framework.Bundle; |
|
25 |
|
26 /** |
|
27 * A monitor that works on Win32 platforms. Provides simple notification of |
|
28 * entire trees by reporting that the root of the tree has changed to depth |
|
29 * DEPTH_INFINITE. |
|
30 */ |
|
31 class Win32Monitor extends Job implements IRefreshMonitor { |
|
32 private static final long RESCHEDULE_DELAY = 1000; |
|
33 |
|
34 /** |
|
35 * A ChainedHandle is a linked list of handles. |
|
36 */ |
|
37 protected abstract class ChainedHandle extends Handle { |
|
38 private ChainedHandle next; |
|
39 private ChainedHandle previous; |
|
40 |
|
41 public abstract boolean exists(); |
|
42 |
|
43 public ChainedHandle getNext() { |
|
44 return next; |
|
45 } |
|
46 |
|
47 public ChainedHandle getPrevious() { |
|
48 return previous; |
|
49 } |
|
50 |
|
51 public void setNext(ChainedHandle next) { |
|
52 this.next = next; |
|
53 } |
|
54 |
|
55 public void setPrevious(ChainedHandle previous) { |
|
56 this.previous = previous; |
|
57 } |
|
58 } |
|
59 |
|
60 protected class FileHandle extends ChainedHandle { |
|
61 private File file; |
|
62 |
|
63 public FileHandle(File file) { |
|
64 this.file = file; |
|
65 } |
|
66 |
|
67 public boolean exists() { |
|
68 return file.exists(); |
|
69 } |
|
70 |
|
71 public void handleNotification() { |
|
72 if (!isOpen()) |
|
73 return; |
|
74 ChainedHandle next = getNext(); |
|
75 if (next != null) { |
|
76 if (next.isOpen()) { |
|
77 if (!next.exists()) { |
|
78 if (next instanceof LinkedResourceHandle) { |
|
79 next.close(); |
|
80 LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next; |
|
81 linkedResourceHandle.postRefreshRequest(); |
|
82 } else { |
|
83 next.close(); |
|
84 } |
|
85 ChainedHandle previous = getPrevious(); |
|
86 if (previous != null) |
|
87 previous.open(); |
|
88 } |
|
89 } else { |
|
90 next.open(); |
|
91 if (next.isOpen()) { |
|
92 Handle previous = getPrevious(); |
|
93 previous.close(); |
|
94 if (next instanceof LinkedResourceHandle) |
|
95 ((LinkedResourceHandle) next).postRefreshRequest(); |
|
96 } |
|
97 } |
|
98 } |
|
99 findNextChange(); |
|
100 } |
|
101 |
|
102 public void open() { |
|
103 if (!isOpen()) { |
|
104 Handle next = getNext(); |
|
105 if (next != null && next.isOpen()) { |
|
106 openHandleOn(file); |
|
107 } else { |
|
108 if (exists()) { |
|
109 openHandleOn(file); |
|
110 } |
|
111 Handle previous = getPrevious(); |
|
112 if (previous != null) { |
|
113 previous.open(); |
|
114 } |
|
115 } |
|
116 } |
|
117 } |
|
118 } |
|
119 |
|
120 protected abstract class Handle { |
|
121 protected long handleValue; |
|
122 |
|
123 public Handle() { |
|
124 handleValue = Win32Natives.INVALID_HANDLE_VALUE; |
|
125 } |
|
126 |
|
127 public void close() { |
|
128 if (isOpen()) { |
|
129 if (!Win32Natives.FindCloseChangeNotification(handleValue)) { |
|
130 int error = Win32Natives.GetLastError(); |
|
131 if (error != Win32Natives.ERROR_INVALID_HANDLE) |
|
132 addException(NLS.bind(Messages.WM_errCloseHandle, Integer.toString(error))); |
|
133 } |
|
134 if (RefreshManager.DEBUG) |
|
135 System.out.println(DEBUG_PREFIX + "removed handle: " + handleValue); //$NON-NLS-1$ |
|
136 handleValue = Win32Natives.INVALID_HANDLE_VALUE; |
|
137 } |
|
138 } |
|
139 |
|
140 private long createHandleValue(String path, boolean monitorSubtree, int flags) { |
|
141 long handle = Win32Natives.FindFirstChangeNotification(path, monitorSubtree, flags); |
|
142 if (handle == Win32Natives.INVALID_HANDLE_VALUE) { |
|
143 int error = Win32Natives.GetLastError(); |
|
144 addException(NLS.bind(Messages.WM_errCreateHandle, path, Integer.toString(error))); |
|
145 } |
|
146 return handle; |
|
147 } |
|
148 |
|
149 public void destroy() { |
|
150 close(); |
|
151 } |
|
152 |
|
153 protected void findNextChange() { |
|
154 if (!Win32Natives.FindNextChangeNotification(handleValue)) { |
|
155 int error = Win32Natives.GetLastError(); |
|
156 if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { |
|
157 addException(NLS.bind(Messages.WM_errFindChange, Integer.toString(error))); |
|
158 } |
|
159 removeHandle(this); |
|
160 } |
|
161 } |
|
162 |
|
163 public long getHandleValue() { |
|
164 return handleValue; |
|
165 } |
|
166 |
|
167 public abstract void handleNotification(); |
|
168 |
|
169 public boolean isOpen() { |
|
170 return handleValue != Win32Natives.INVALID_HANDLE_VALUE; |
|
171 } |
|
172 |
|
173 public abstract void open(); |
|
174 |
|
175 protected void openHandleOn(File file) { |
|
176 openHandleOn(file.getAbsolutePath(), false); |
|
177 } |
|
178 |
|
179 protected void openHandleOn(IResource resource) { |
|
180 openHandleOn(resource.getLocation().toOSString(), true); |
|
181 } |
|
182 |
|
183 private void openHandleOn(String path, boolean subtree) { |
|
184 setHandleValue(createHandleValue(path, subtree, Win32Natives.FILE_NOTIFY_CHANGE_FILE_NAME | Win32Natives.FILE_NOTIFY_CHANGE_DIR_NAME | Win32Natives.FILE_NOTIFY_CHANGE_LAST_WRITE | Win32Natives.FILE_NOTIFY_CHANGE_SIZE)); |
|
185 if (isOpen()) { |
|
186 fHandleValueToHandle.put(new Long(getHandleValue()), this); |
|
187 setHandleValueArrays(createHandleArrays()); |
|
188 } else { |
|
189 close(); |
|
190 } |
|
191 } |
|
192 |
|
193 protected void postRefreshRequest(IResource resource) { |
|
194 //native callback occurs even if resource was changed within workspace |
|
195 if (!resource.isSynchronized(IResource.DEPTH_INFINITE)) |
|
196 refreshResult.refresh(resource); |
|
197 } |
|
198 |
|
199 public void setHandleValue(long handleValue) { |
|
200 this.handleValue = handleValue; |
|
201 } |
|
202 } |
|
203 |
|
204 protected class LinkedResourceHandle extends ChainedHandle { |
|
205 private List fileHandleChain; |
|
206 private IResource resource; |
|
207 |
|
208 /** |
|
209 * @param resource |
|
210 */ |
|
211 public LinkedResourceHandle(IResource resource) { |
|
212 this.resource = resource; |
|
213 createFileHandleChain(); |
|
214 } |
|
215 |
|
216 protected void createFileHandleChain() { |
|
217 fileHandleChain = new ArrayList(1); |
|
218 File file = new File(resource.getLocation().toOSString()); |
|
219 file = file.getParentFile(); |
|
220 while (file != null) { |
|
221 fileHandleChain.add(0, new FileHandle(file)); |
|
222 file = file.getParentFile(); |
|
223 } |
|
224 int size = fileHandleChain.size(); |
|
225 for (int i = 0; i < size; i++) { |
|
226 ChainedHandle handle = (ChainedHandle) fileHandleChain.get(i); |
|
227 handle.setPrevious((i > 0) ? (ChainedHandle) fileHandleChain.get(i - 1) : null); |
|
228 handle.setNext((i + 1 < size) ? (ChainedHandle) fileHandleChain.get(i + 1) : this); |
|
229 } |
|
230 setPrevious((size > 0) ? (ChainedHandle) fileHandleChain.get(size - 1) : null); |
|
231 } |
|
232 |
|
233 public void destroy() { |
|
234 super.destroy(); |
|
235 for (Iterator i = fileHandleChain.iterator(); i.hasNext();) { |
|
236 Handle handle = (Handle) i.next(); |
|
237 handle.destroy(); |
|
238 } |
|
239 } |
|
240 |
|
241 public boolean exists() { |
|
242 IPath location = resource.getLocation(); |
|
243 return location == null ? false : location.toFile().exists(); |
|
244 } |
|
245 |
|
246 public void handleNotification() { |
|
247 if (isOpen()) { |
|
248 postRefreshRequest(resource); |
|
249 findNextChange(); |
|
250 } |
|
251 } |
|
252 |
|
253 public void open() { |
|
254 if (!isOpen()) { |
|
255 if (exists()) { |
|
256 openHandleOn(resource); |
|
257 } |
|
258 FileHandle handle = (FileHandle) getPrevious(); |
|
259 if (handle != null && !handle.isOpen()) { |
|
260 handle.open(); |
|
261 } |
|
262 } |
|
263 } |
|
264 |
|
265 public void postRefreshRequest() { |
|
266 postRefreshRequest(resource); |
|
267 } |
|
268 } |
|
269 |
|
270 protected class ResourceHandle extends Handle { |
|
271 private IResource resource; |
|
272 |
|
273 public ResourceHandle(IResource resource) { |
|
274 super(); |
|
275 this.resource = resource; |
|
276 } |
|
277 |
|
278 public IResource getResource() { |
|
279 return resource; |
|
280 } |
|
281 |
|
282 public void handleNotification() { |
|
283 if (isOpen()) { |
|
284 postRefreshRequest(resource); |
|
285 findNextChange(); |
|
286 } |
|
287 } |
|
288 |
|
289 public void open() { |
|
290 if (!isOpen()) { |
|
291 openHandleOn(resource); |
|
292 } |
|
293 } |
|
294 } |
|
295 |
|
296 private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$ |
|
297 private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 300; |
|
298 /** |
|
299 * Any errors that have occurred |
|
300 */ |
|
301 protected MultiStatus errors; |
|
302 /** |
|
303 * Arrays of handles, split evenly when the number of handles is larger |
|
304 * than Win32Natives.MAXIMUM_WAIT_OBJECTS |
|
305 */ |
|
306 protected long[][] fHandleValueArrays; |
|
307 /** |
|
308 * Mapping of handles (java.lang.Long) to absolute paths |
|
309 * (java.lang.String). |
|
310 */ |
|
311 protected Map fHandleValueToHandle; |
|
312 protected IRefreshResult refreshResult; |
|
313 |
|
314 /* |
|
315 * Creates a new monitor. @param result A result that will recieve refresh |
|
316 * callbacks and error notifications |
|
317 */ |
|
318 public Win32Monitor(IRefreshResult result) { |
|
319 super(Messages.WM_jobName); |
|
320 this.refreshResult = result; |
|
321 setPriority(Job.DECORATE); |
|
322 setSystem(true); |
|
323 fHandleValueToHandle = new HashMap(1); |
|
324 setHandleValueArrays(createHandleArrays()); |
|
325 } |
|
326 |
|
327 /** |
|
328 * Logs an exception |
|
329 */ |
|
330 protected synchronized void addException(String message) { |
|
331 if (errors == null) { |
|
332 String msg = Messages.WM_errors; |
|
333 errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null); |
|
334 } |
|
335 errors.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, message, null)); |
|
336 } |
|
337 |
|
338 /* |
|
339 * Splits the given array into arrays of length no greater than <code> max |
|
340 * </code> . The lengths of the sub arrays differ in size by no more than |
|
341 * one element. <p> Examples: <ul><li> If an array of size 11 is split |
|
342 * with a max of 4, the resulting arrays are of size 4, 4, and 3. </li> |
|
343 * <li> If an array of size 18 is split with a max of 5, the resulting |
|
344 * arrays are of size 5, 5, 4, and 4. </li></ul> |
|
345 */ |
|
346 private long[][] balancedSplit(final long[] array, final int max) { |
|
347 int elementCount = array.length; |
|
348 // want to handle [1, max] rather than [0, max) |
|
349 int subArrayCount = ((elementCount - 1) / max) + 1; |
|
350 int subArrayBaseLength = elementCount / subArrayCount; |
|
351 int overflow = elementCount % subArrayCount; |
|
352 long[][] result = new long[subArrayCount][]; |
|
353 int count = 0; |
|
354 for (int i = 0; i < subArrayCount; i++) { |
|
355 int subArrayLength = subArrayBaseLength + (overflow-- > 0 ? 1 : 0); |
|
356 long[] subArray = new long[subArrayLength]; |
|
357 for (int j = 0; j < subArrayLength; j++) { |
|
358 subArray[j] = array[count++]; |
|
359 } |
|
360 result[i] = subArray; |
|
361 } |
|
362 return result; |
|
363 } |
|
364 |
|
365 private Handle createHandle(IResource resource) { |
|
366 if (resource.isLinked()) |
|
367 return new LinkedResourceHandle(resource); |
|
368 return new ResourceHandle(resource); |
|
369 } |
|
370 |
|
371 /* |
|
372 * Since the Win32Natives.WaitForMultipleObjects(...) method cannot accept |
|
373 * more than a certain number of objects, we are forced to split the array |
|
374 * of objects to monitor and monitor each one individually. <p> This method |
|
375 * splits the list of handles into arrays no larger than |
|
376 * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balenced so that they |
|
377 * differ in size by no more than one element. |
|
378 */ |
|
379 protected long[][] createHandleArrays() { |
|
380 long[] handles; |
|
381 // synchronized: in order to protect the map during iteration |
|
382 synchronized (fHandleValueToHandle) { |
|
383 Set keys = fHandleValueToHandle.keySet(); |
|
384 int size = keys.size(); |
|
385 if (size == 0) { |
|
386 return new long[0][0]; |
|
387 } |
|
388 handles = new long[size]; |
|
389 int count = 0; |
|
390 for (Iterator i = keys.iterator(); i.hasNext();) { |
|
391 handles[count++] = ((Long) i.next()).longValue(); |
|
392 } |
|
393 } |
|
394 return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS); |
|
395 } |
|
396 |
|
397 private Handle getHandle(IResource resource) { |
|
398 if (resource == null) { |
|
399 return null; |
|
400 } |
|
401 // synchronized: in order to protect the map during iteration |
|
402 synchronized (fHandleValueToHandle) { |
|
403 for (Iterator i = fHandleValueToHandle.values().iterator(); i.hasNext();) { |
|
404 Handle handle = (Handle) i.next(); |
|
405 if (handle instanceof ResourceHandle) { |
|
406 ResourceHandle resourceHandle = (ResourceHandle) handle; |
|
407 if (resourceHandle.getResource().equals(resource)) { |
|
408 return handle; |
|
409 } |
|
410 } |
|
411 } |
|
412 } |
|
413 return null; |
|
414 } |
|
415 |
|
416 /* |
|
417 * Answers arrays of handles. The handles are split evenly when the number |
|
418 * of handles becomes larger than Win32Natives.MAXIMUM_WAIT_OBJECTS. |
|
419 * @return long[][] |
|
420 */ |
|
421 private long[][] getHandleValueArrays() { |
|
422 return fHandleValueArrays; |
|
423 } |
|
424 |
|
425 /** |
|
426 * Adds a resource to be monitored by this native monitor |
|
427 */ |
|
428 public boolean monitor(IResource resource) { |
|
429 IPath location = resource.getLocation(); |
|
430 if (location == null) { |
|
431 // cannot monitor remotely managed containers |
|
432 return false; |
|
433 } |
|
434 Handle handle = createHandle(resource); |
|
435 // synchronized: handle creation must be atomic |
|
436 synchronized (this) { |
|
437 handle.open(); |
|
438 } |
|
439 if (!handle.isOpen()) { |
|
440 //ignore errors if we can't even create a handle on the resource |
|
441 //it will fall back to polling anyway |
|
442 errors = null; |
|
443 return false; |
|
444 } |
|
445 //make sure the job is running |
|
446 schedule(RESCHEDULE_DELAY); |
|
447 if (RefreshManager.DEBUG) |
|
448 System.out.println(DEBUG_PREFIX + " added monitor for: " + resource); //$NON-NLS-1$ |
|
449 return true; |
|
450 } |
|
451 |
|
452 /** |
|
453 * Removes the handle from the <code>fHandleValueToHandle</code> map. |
|
454 * |
|
455 * @param handle |
|
456 * a handle, not <code>null</code> |
|
457 */ |
|
458 protected void removeHandle(Handle handle) { |
|
459 List handles = new ArrayList(1); |
|
460 handles.add(handle); |
|
461 removeHandles(handles); |
|
462 } |
|
463 |
|
464 /** |
|
465 * Removes all of the handles in the given collection from the <code>fHandleValueToHandle</code> |
|
466 * map. If collections from the <code>fHandleValueToHandle</code> map are |
|
467 * used, copy them before passing them in as this method modifies the |
|
468 * <code>fHandleValueToHandle</code> map. |
|
469 * |
|
470 * @param handles |
|
471 * a collection of handles, not <code>null</code> |
|
472 */ |
|
473 private void removeHandles(Collection handles) { |
|
474 // synchronized: protect the array, removal must be atomic |
|
475 synchronized (this) { |
|
476 for (Iterator i = handles.iterator(); i.hasNext();) { |
|
477 Handle handle = (Handle) i.next(); |
|
478 fHandleValueToHandle.remove(new Long(handle.getHandleValue())); |
|
479 handle.destroy(); |
|
480 } |
|
481 setHandleValueArrays(createHandleArrays()); |
|
482 } |
|
483 } |
|
484 |
|
485 /* |
|
486 * @see java.lang.Runnable#run() |
|
487 */ |
|
488 protected IStatus run(IProgressMonitor monitor) { |
|
489 long start = -System.currentTimeMillis(); |
|
490 if (RefreshManager.DEBUG) |
|
491 System.out.println(DEBUG_PREFIX + "job started."); //$NON-NLS-1$ |
|
492 try { |
|
493 long[][] handleArrays = getHandleValueArrays(); |
|
494 monitor.beginTask(Messages.WM_beginTask, handleArrays.length); |
|
495 // If changes occur to the list of handles, |
|
496 // ignore them until the next time through the loop. |
|
497 for (int i = 0, length = handleArrays.length; i < length; i++) { |
|
498 if (monitor.isCanceled()) |
|
499 return Status.CANCEL_STATUS; |
|
500 waitForNotification(handleArrays[i]); |
|
501 monitor.worked(1); |
|
502 } |
|
503 } finally { |
|
504 monitor.done(); |
|
505 start += System.currentTimeMillis(); |
|
506 if (RefreshManager.DEBUG) |
|
507 System.out.println(DEBUG_PREFIX + "job finished in: " + start + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
|
508 } |
|
509 //always reschedule the job - so it will come back after errors or cancelation |
|
510 //make sure it doesn't hog more that 5% of CPU |
|
511 long delay = Math.max(RESCHEDULE_DELAY, start * 30); |
|
512 if (RefreshManager.DEBUG) |
|
513 System.out.println(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$ |
|
514 final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); |
|
515 //if the bundle is null then the framework has shutdown - just bail out completely (bug 98219) |
|
516 if (bundle == null) |
|
517 return Status.OK_STATUS; |
|
518 //don't reschedule the job if the resources plugin has been shut down |
|
519 if (bundle.getState() == Bundle.ACTIVE) |
|
520 schedule(delay); |
|
521 MultiStatus result = errors; |
|
522 errors = null; |
|
523 //just log native refresh failures |
|
524 if (result != null && !result.isOK()) |
|
525 ResourcesPlugin.getPlugin().getLog().log(result); |
|
526 return Status.OK_STATUS; |
|
527 } |
|
528 |
|
529 protected void setHandleValueArrays(long[][] arrays) { |
|
530 fHandleValueArrays = arrays; |
|
531 } |
|
532 |
|
533 /* (non-Javadoc) |
|
534 * @see org.eclipse.core.runtime.jobs.Job#shouldRun() |
|
535 */ |
|
536 public boolean shouldRun() { |
|
537 return !fHandleValueToHandle.isEmpty(); |
|
538 } |
|
539 |
|
540 /* |
|
541 * @see org.eclipse.core.resources.refresh.IRefreshMonitor#unmonitor(IContainer) |
|
542 */ |
|
543 public void unmonitor(IResource resource) { |
|
544 if (resource == null) { |
|
545 // resource == null means stop monitoring all resources |
|
546 synchronized (fHandleValueToHandle) { |
|
547 removeHandles(new ArrayList(fHandleValueToHandle.values())); |
|
548 } |
|
549 } else { |
|
550 Handle handle = getHandle(resource); |
|
551 if (handle != null) |
|
552 removeHandle(handle); |
|
553 } |
|
554 //stop the job if there are no more handles |
|
555 if (fHandleValueToHandle.isEmpty()) |
|
556 cancel(); |
|
557 } |
|
558 |
|
559 /** |
|
560 * Performs the native call to wait for notification on one of the given |
|
561 * handles. |
|
562 * |
|
563 * @param handleValues |
|
564 * an array of handles, it must contain no duplicates. |
|
565 */ |
|
566 private void waitForNotification(long[] handleValues) { |
|
567 int handleCount = handleValues.length; |
|
568 int index = Win32Natives.WaitForMultipleObjects(handleCount, handleValues, false, WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT); |
|
569 if (index == Win32Natives.WAIT_TIMEOUT) { |
|
570 // nothing happened. |
|
571 return; |
|
572 } |
|
573 if (index == Win32Natives.WAIT_FAILED) { |
|
574 // we ran into a problem |
|
575 int error = Win32Natives.GetLastError(); |
|
576 if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { |
|
577 addException(NLS.bind(Messages.WM_nativeErr, Integer.toString(error))); |
|
578 refreshResult.monitorFailed(this, null); |
|
579 } |
|
580 return; |
|
581 } |
|
582 // a change occurred |
|
583 // WaitForMultipleObjects returns WAIT_OBJECT_0 + index |
|
584 index -= Win32Natives.WAIT_OBJECT_0; |
|
585 Handle handle = (Handle) fHandleValueToHandle.get(new Long(handleValues[index])); |
|
586 if (handle != null) |
|
587 handle.handleNotification(); |
|
588 } |
|
589 } |