Added
Link Here
|
1 |
/******************************************************************************* |
2 |
* Copyright (c) 2010 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 |
*******************************************************************************/ |
12 |
package org.eclipse.wst.sse.core.indexing; |
13 |
|
14 |
import java.io.BufferedInputStream; |
15 |
import java.io.BufferedOutputStream; |
16 |
import java.io.DataInputStream; |
17 |
import java.io.DataOutputStream; |
18 |
import java.io.File; |
19 |
import java.io.FileInputStream; |
20 |
import java.io.FileNotFoundException; |
21 |
import java.io.FileOutputStream; |
22 |
import java.io.IOException; |
23 |
import java.util.Iterator; |
24 |
import java.util.LinkedHashMap; |
25 |
import java.util.Map; |
26 |
|
27 |
import org.eclipse.core.resources.IFile; |
28 |
import org.eclipse.core.resources.IResource; |
29 |
import org.eclipse.core.resources.IResourceChangeEvent; |
30 |
import org.eclipse.core.resources.IResourceChangeListener; |
31 |
import org.eclipse.core.resources.IResourceDelta; |
32 |
import org.eclipse.core.resources.IResourceDeltaVisitor; |
33 |
import org.eclipse.core.resources.IResourceProxy; |
34 |
import org.eclipse.core.resources.IResourceProxyVisitor; |
35 |
import org.eclipse.core.resources.ISaveParticipant; |
36 |
import org.eclipse.core.resources.ResourcesPlugin; |
37 |
import org.eclipse.core.runtime.CoreException; |
38 |
import org.eclipse.core.runtime.IPath; |
39 |
import org.eclipse.core.runtime.IProgressMonitor; |
40 |
import org.eclipse.core.runtime.ISafeRunnable; |
41 |
import org.eclipse.core.runtime.IStatus; |
42 |
import org.eclipse.core.runtime.Path; |
43 |
import org.eclipse.core.runtime.SafeRunner; |
44 |
import org.eclipse.core.runtime.Status; |
45 |
import org.eclipse.core.runtime.SubMonitor; |
46 |
import org.eclipse.core.runtime.jobs.Job; |
47 |
import org.eclipse.osgi.util.NLS; |
48 |
import org.eclipse.wst.sse.core.internal.Logger; |
49 |
import org.eclipse.wst.sse.core.internal.SSECoreMessages; |
50 |
|
51 |
/** |
52 |
* <p>Provides a generic for writing a resource index manager. It is important |
53 |
* to note that this only provides the framework for managing an index, not actually |
54 |
* indexing. The subtle difference is that the manager is in charge of paying attention |
55 |
* to all of the resource actions that take place in the workspace and filtering those |
56 |
* actions down to simple actions that need to be performed on whatever index this manger |
57 |
* is managing.</p> |
58 |
* |
59 |
* <p>The manger does its very best to make sure the index is always consistent, even if |
60 |
* resource events take place when the manager is not running. In the event that the |
61 |
* manager determines it has missed, loosed, or corrupted any resource change events |
62 |
* that have occurred before, during, or after its activation or de-activation then the |
63 |
* manager will inspect the entire workspace to insure the index it is managing is |
64 |
* Consistent.</p> |
65 |
* |
66 |
*/ |
67 |
public abstract class AbstractIndexManager { |
68 |
|
69 |
/** Default time to wait for other tasks to finish */ |
70 |
private static final int WAIT_TIME = 300; |
71 |
|
72 |
/** |
73 |
* <p>Used to report progress on jobs where the total work to complete is unknown. |
74 |
* The created effect is a progress bar that moves but that will never complete.</p> |
75 |
*/ |
76 |
private static final int UNKNOWN_WORK = 100; |
77 |
|
78 |
/** The amount of events to batch up before sending them off to the processing job*/ |
79 |
private static final int BATCH_UP_AMONT = 100; |
80 |
|
81 |
/** If this file exists then a full workspace re-processing is needed */ |
82 |
private static final String RE_PROCESS_FILE_NAME = ".re-process"; //$NON-NLS-1$ |
83 |
|
84 |
/** Common error message to log */ |
85 |
private static final String LOG_ERROR_INDEX_INVALID = |
86 |
"Index may become invalid, incomplete, or enter some other inconsistent state."; //$NON-NLS-1$ |
87 |
|
88 |
/** State: manager is stopped */ |
89 |
private static final byte STATE_DISABLED = 0; |
90 |
|
91 |
/** State: manager is running */ |
92 |
private static final byte STATE_ENABLED = 1; |
93 |
|
94 |
/** Action: add to index */ |
95 |
protected static final byte ACTION_ADD = 0; |
96 |
|
97 |
/** Action: remove from index */ |
98 |
protected static final byte ACTION_REMOVE = 1; |
99 |
|
100 |
/** Action: add to index caused by move operation */ |
101 |
protected static final byte ACTION_ADD_MOVE_FROM = 2; |
102 |
|
103 |
/** Action: remove from index caused by move operation */ |
104 |
protected static final byte ACTION_REMOVE_MOVE_TO = 3; |
105 |
|
106 |
/** Source: action originated from resource change event */ |
107 |
protected static final byte SOURCE_RESROUCE_CHANGE = 0; |
108 |
|
109 |
/** Source: action originated from workspace scan */ |
110 |
protected static final byte SOURCE_WORKSPACE_SCAN = 1; |
111 |
|
112 |
/** Source: action originated from saved state */ |
113 |
protected static final byte SOURCE_SAVED_STATE = 2; |
114 |
|
115 |
/** Source: preserved resources to index */ |
116 |
protected static final byte SOURCE_PRESERVED_RESOURCES_TO_INDEX = 3; |
117 |
|
118 |
/** the name of this index manager */ |
119 |
protected String fName; |
120 |
|
121 |
/** {@link IResourceChangeListener} to listen for file changes */ |
122 |
private ResourceChangeListener fResourceChangeListener; |
123 |
|
124 |
/** The {@link Job} that does all of the indexing */ |
125 |
private ResourceEventProcessingJob fResourceEventProcessingJob; |
126 |
|
127 |
/** A {@link Job} to search the workspace for all files */ |
128 |
private Job fWorkspaceVisitorJob; |
129 |
|
130 |
/** |
131 |
* <p>Current state of the manager</p> |
132 |
* |
133 |
* @see #STATE_DISABLED |
134 |
* @see #STATE_ENABLED |
135 |
*/ |
136 |
private volatile byte fState; |
137 |
|
138 |
/** used to prevent manager from starting and stopping at the same time */ |
139 |
private Object fStartStopLock = new Object(); |
140 |
|
141 |
/** <code>true</code> if the manger is currently starting, <code>false</code> otherwise */ |
142 |
private boolean fStarting; |
143 |
|
144 |
/** |
145 |
* <p>Creates the manager with a given name.</p> |
146 |
* |
147 |
* @param name This will be pre-pended to progress reporting messages and thus should |
148 |
* be translated |
149 |
*/ |
150 |
protected AbstractIndexManager(String name) { |
151 |
this.fName = name; |
152 |
this.fState = STATE_DISABLED; |
153 |
this.fResourceChangeListener = new ResourceChangeListener(); |
154 |
this.fResourceEventProcessingJob = new ResourceEventProcessingJob(); |
155 |
this.fStarting = false; |
156 |
} |
157 |
|
158 |
/** |
159 |
* <p>Starts up the {@link AbstractIndexManager}. If a {@link IResourceDelta} |
160 |
* is provided then it is assumed that all other files in the workspace |
161 |
* have already been index and thus only those in the provided |
162 |
* {@link IResourceDelta} will be processed. Else if the provided |
163 |
* {@link IResourceDelta} is <code>null</code> it is assumed no files |
164 |
* have been indexed yet so the entire workspace will be searched for |
165 |
* files to be indexed.</p> |
166 |
* |
167 |
* <p>If {@link IResourceDelta} is provided this will block until that delta |
168 |
* has finished processing. If no {@link IResourceDelta} provided then a |
169 |
* separate job will be created to process the entire workspace and this method |
170 |
* will return without waiting for that job to complete</p> |
171 |
* |
172 |
* <p>Will block until {@link #stop()} has finished running if it is |
173 |
* currently running</p> |
174 |
* |
175 |
* @param savedStateDelta the delta from a saved state, if <code>null</code> |
176 |
* then the entire workspace will be searched for files to index, else |
177 |
* only files in this {@link IResourceDelta} will be indexed |
178 |
* @param monitor This action can not be canceled but this monitor will be used |
179 |
* to report progress |
180 |
*/ |
181 |
public final void start(IResourceDelta savedStateDelta, IProgressMonitor monitor) { |
182 |
SubMonitor progress = SubMonitor.convert(monitor); |
183 |
synchronized (this.fStartStopLock) { |
184 |
this.fStarting = true; |
185 |
|
186 |
if(this.fState == STATE_DISABLED) { |
187 |
//report status |
188 |
progress.beginTask(this.fName + ": " + SSECoreMessages.IndexManager_starting, //$NON-NLS-1$ |
189 |
2); |
190 |
|
191 |
//start listening for resource change events |
192 |
this.fResourceChangeListener.start(); |
193 |
|
194 |
//check to see if a full re-index is required |
195 |
boolean forcedFullReIndexNeeded = this.isForcedFullReIndexNeeded(); |
196 |
|
197 |
/* start the indexing job only loading preserved state if not doing full index |
198 |
* if failed loading preserved state then force full re-index |
199 |
*/ |
200 |
forcedFullReIndexNeeded = !this.fResourceEventProcessingJob.start(!forcedFullReIndexNeeded, |
201 |
progress.newChild(1)); |
202 |
progress.setWorkRemaining(1); |
203 |
|
204 |
//don't bother processing saved delta if forced full re-index is needed |
205 |
boolean stillNeedFullReIndex = forcedFullReIndexNeeded; |
206 |
if(!forcedFullReIndexNeeded) { |
207 |
//if there is a delta attempt to process it |
208 |
if(savedStateDelta != null) { |
209 |
stillNeedFullReIndex = false; |
210 |
try { |
211 |
//deal with reporting progress |
212 |
SubMonitor savedStateProgress = progress.newChild(1, SubMonitor.SUPPRESS_NONE); |
213 |
savedStateProgress.setTaskName( |
214 |
this.fName + ": " + SSECoreMessages.IndexManager_starting + ": " + //$NON-NLS-1$ //$NON-NLS-2$ |
215 |
SSECoreMessages.IndexManager_processing_deferred_resource_changes); |
216 |
|
217 |
//process delta |
218 |
ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(savedStateProgress, |
219 |
AbstractIndexManager.SOURCE_SAVED_STATE); |
220 |
savedStateDelta.accept(visitor); |
221 |
|
222 |
//process any remaining batched up resources to index |
223 |
visitor.processBatchedResourceEvents(); |
224 |
} catch (CoreException e) { |
225 |
stillNeedFullReIndex = true; |
226 |
Logger.logException(this.fName + ": Could not process saved state. " + //$NON-NLS-1$ |
227 |
"Forced to do a full workspace re-index.", e); //$NON-NLS-1$ |
228 |
} |
229 |
} |
230 |
} |
231 |
progress.worked(1); |
232 |
|
233 |
//if need to process the entire workspace do so in another job |
234 |
if(stillNeedFullReIndex){ |
235 |
this.fWorkspaceVisitorJob = new WorkspaceVisitorJob(); |
236 |
this.fWorkspaceVisitorJob.schedule(); |
237 |
} |
238 |
|
239 |
//update state |
240 |
this.fState = STATE_ENABLED; |
241 |
} |
242 |
this.fStarting = false; |
243 |
} |
244 |
} |
245 |
|
246 |
/** |
247 |
* <p>Safely shuts down the manager.</p> |
248 |
* |
249 |
* <p>This will block until the {@link #start(IResourceDelta, IProgressMonitor)} has |
250 |
* finished (if running). Also until the current resource event has finished being |
251 |
* processed. Finally it will block until the events still to be processed by |
252 |
* the processing job have been preserved to be processed on the next call to |
253 |
* {@link #start(IResourceDelta, IProgressMonitor)}.</p> |
254 |
* |
255 |
* <p>If at any point during this shut down processes something goes wrong the |
256 |
* manager will be sure that on the next call to {@link #start(IResourceDelta, IProgressMonitor)} |
257 |
* the entire workspace will be re-processed.</p> |
258 |
* |
259 |
* @throws InterruptedException |
260 |
*/ |
261 |
public final void stop() throws InterruptedException { |
262 |
synchronized (this.fStartStopLock) { |
263 |
if(this.fState != STATE_DISABLED) { |
264 |
|
265 |
//stop listening for events, and wait for the current event to finish |
266 |
this.fResourceChangeListener.stop(); |
267 |
|
268 |
// if currently visiting entire workspace, give up and try again next load |
269 |
boolean forceFullReIndexNextStart = false; |
270 |
if(this.fWorkspaceVisitorJob != null) { |
271 |
if (this.fWorkspaceVisitorJob.getState() != Job.NONE) { |
272 |
this.fWorkspaceVisitorJob.cancel(); |
273 |
|
274 |
this.forceFullReIndexNextStart(); |
275 |
forceFullReIndexNextStart = true; |
276 |
} |
277 |
} |
278 |
|
279 |
//stop the indexing job, only preserve if not already forcing a re-index |
280 |
forceFullReIndexNextStart = !this.fResourceEventProcessingJob.stop(!forceFullReIndexNextStart); |
281 |
|
282 |
//if preserving failed, then force re-index |
283 |
if(forceFullReIndexNextStart) { |
284 |
this.forceFullReIndexNextStart(); |
285 |
} |
286 |
|
287 |
//update status |
288 |
this.fState = STATE_DISABLED; |
289 |
} |
290 |
} |
291 |
} |
292 |
|
293 |
/** |
294 |
* <p>Should be called by an client of the index this manger manages before the index |
295 |
* is accessed, assuming the client wants an index consistent with the latest |
296 |
* resource changes.</p> |
297 |
* |
298 |
* <p>The supplied monitor will be used to supply user readable progress as the manager |
299 |
* insures the index has been given all the latest resource events. This monitor |
300 |
* maybe canceled, but if it is the state of the index is not guaranteed to be |
301 |
* consistent with the latest resource change events.</p> |
302 |
* |
303 |
* @param monitor Used to report user readable progress as the manager insures the |
304 |
* index is consistent with the latest resource events. This monitor can be canceled |
305 |
* to stop waiting for consistency but then no guaranty is made about the consistency |
306 |
* of the index in relation to un-processed resource changes |
307 |
* |
308 |
* @return <code>true</code> if the wait finished successfully and the manger is consistent, |
309 |
* <code>false</code> otherwise, either an error occurred while waiting for the manager |
310 |
* or the monitor was canceled |
311 |
* |
312 |
* @throws InterruptedException This can happen when waiting for other jobs |
313 |
*/ |
314 |
public final boolean waitForConsistant(IProgressMonitor monitor) { |
315 |
boolean success = true; |
316 |
boolean interupted = false; |
317 |
SubMonitor progress = SubMonitor.convert(monitor); |
318 |
|
319 |
//set up the progress of waiting |
320 |
int remainingWork = 4; |
321 |
progress.beginTask(NLS.bind(SSECoreMessages.IndexManager_Waiting_for_0, this.fName), |
322 |
remainingWork); |
323 |
|
324 |
//wait for start up |
325 |
if(this.fStarting && !monitor.isCanceled()) { |
326 |
SubMonitor startingProgress = progress.newChild(1); |
327 |
startingProgress.subTask(SSECoreMessages.IndexManager_starting); |
328 |
while(this.fStarting && !monitor.isCanceled()) { |
329 |
//this creates a never ending progress that still moves forward |
330 |
startingProgress.setWorkRemaining(UNKNOWN_WORK); |
331 |
startingProgress.newChild(1).worked(1); |
332 |
try { |
333 |
Thread.sleep(WAIT_TIME); |
334 |
} catch (InterruptedException e) { |
335 |
interupted = true; |
336 |
} |
337 |
} |
338 |
} |
339 |
progress.setWorkRemaining(--remainingWork); |
340 |
|
341 |
//wait for workspace visiting job |
342 |
if(this.fWorkspaceVisitorJob != null && this.fWorkspaceVisitorJob.getState() != Job.NONE && !monitor.isCanceled()) { |
343 |
SubMonitor workspaceVisitorProgress = progress.newChild(1); |
344 |
workspaceVisitorProgress.subTask(SSECoreMessages.IndexManager_Processing_entire_workspace_for_the_first_time); |
345 |
while(this.fWorkspaceVisitorJob.getState() != Job.NONE && !monitor.isCanceled()) { |
346 |
//this creates a never ending progress that still moves forward |
347 |
workspaceVisitorProgress.setWorkRemaining(UNKNOWN_WORK); |
348 |
workspaceVisitorProgress.newChild(1).worked(1); |
349 |
try { |
350 |
Thread.sleep(WAIT_TIME); |
351 |
} catch (InterruptedException e) { |
352 |
interupted = true; |
353 |
} |
354 |
} |
355 |
} |
356 |
progress.setWorkRemaining(--remainingWork); |
357 |
|
358 |
//wait for the current resource event |
359 |
if(this.fResourceChangeListener.isProcessingEvents() && !monitor.isCanceled()) { |
360 |
SubMonitor workspaceVisitorProgress = progress.newChild(1); |
361 |
workspaceVisitorProgress.subTask(SSECoreMessages.IndexManager_processing_recent_resource_changes); |
362 |
while(this.fResourceChangeListener.isProcessingEvents() && !monitor.isCanceled()) { |
363 |
workspaceVisitorProgress.setWorkRemaining(UNKNOWN_WORK); |
364 |
workspaceVisitorProgress.newChild(1).worked(1); |
365 |
try { |
366 |
this.fResourceChangeListener.waitForCurrentEvent(WAIT_TIME); |
367 |
} catch (InterruptedException e) { |
368 |
interupted = true; |
369 |
} |
370 |
} |
371 |
} |
372 |
progress.setWorkRemaining(--remainingWork); |
373 |
|
374 |
//wait for all files to be indexed |
375 |
if(this.fResourceEventProcessingJob.getNumResourceEventsToProcess() != 0 && !monitor.isCanceled()) { |
376 |
SubMonitor indexingProgress = progress.newChild(1); |
377 |
int prevNumResrouces; |
378 |
int numResources = this.fResourceEventProcessingJob.getNumResourceEventsToProcess(); |
379 |
while(numResources != 0 && !monitor.isCanceled()) { |
380 |
//update the progress indicator |
381 |
indexingProgress.subTask(AbstractIndexManager.this.fName + ": " + //$NON-NLS-1$ |
382 |
NLS.bind(SSECoreMessages.IndexManager_Indexing_0_Files, |
383 |
"" + numResources)); //$NON-NLS-1$ |
384 |
indexingProgress.setWorkRemaining(numResources); |
385 |
prevNumResrouces = numResources; |
386 |
numResources = this.fResourceEventProcessingJob.getNumResourceEventsToProcess(); |
387 |
int numProcessed = prevNumResrouces - numResources; |
388 |
indexingProgress.worked(numProcessed > 0 ? numProcessed : 0); |
389 |
|
390 |
//give the index some time to do some indexing |
391 |
try { |
392 |
this.fResourceEventProcessingJob.waitForConsistant(WAIT_TIME); |
393 |
} catch (InterruptedException e) { |
394 |
interupted = true; |
395 |
} |
396 |
} |
397 |
} |
398 |
progress.setWorkRemaining(--remainingWork); |
399 |
|
400 |
if(monitor.isCanceled()) { |
401 |
success = false; |
402 |
} |
403 |
|
404 |
//reset the interrupted flag if we were interrupted |
405 |
if(interupted) { |
406 |
Thread.currentThread().interrupt(); |
407 |
} |
408 |
|
409 |
return success; |
410 |
} |
411 |
|
412 |
/** |
413 |
* <p>Used to determine if an {@link IResource} with the given |
414 |
* type and name should be processed by this index manager</p> |
415 |
* |
416 |
* @param type see {@link IResource#getType()} |
417 |
* @param name the name of the resource |
418 |
* |
419 |
* @return <code>true</code> if this index manager processes resources |
420 |
* of the given <code>type</code> with the given <code>name</code>, |
421 |
* <code>false</code> otherwise |
422 |
*/ |
423 |
protected abstract boolean isResourceToIndex(int type, String name); |
424 |
|
425 |
/** |
426 |
* <p>Called for each {@link ResourceEvent} gathered by the various sources and processed |
427 |
* by the {@link ResourceEventProcessingJob}. The implementation of this method |
428 |
* should use the given information to update the index this manger is managing.</p> |
429 |
* |
430 |
* @param source The source that reported this resource event |
431 |
* @param action The action to be taken on the given <code>resource</code> |
432 |
* @param resource The index should perform the given <code>action</code> on this |
433 |
* resource |
434 |
* @param movePath If the given <code>action</code> is {@link AbstractIndexManager#ACTION_ADD_MOVE_FROM} |
435 |
* or {@link AbstractIndexManager#ACTION_REMOVE_MOVE_TO} then this field will not be |
436 |
* null and reports the path the given <code>resource</code> was either moved from or |
437 |
* moved to respectively. |
438 |
* |
439 |
* @see AbstractIndexManager#SOURCE_RESROUCE_CHANGE |
440 |
* @see AbstractIndexManager#SOURCE_SAVED_STATE |
441 |
* @see AbstractIndexManager#SOURCE_WORKSPACE_SCAN |
442 |
* @see AbstractIndexManager#SOURCE_PRESERVED_RESOURCES_TO_INDEX |
443 |
* |
444 |
* @see AbstractIndexManager#ACTION_ADD |
445 |
* @see AbstractIndexManager#ACTION_REMOVE |
446 |
* @see AbstractIndexManager#ACTION_ADD_MOVE_FROM |
447 |
* @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO |
448 |
*/ |
449 |
protected abstract void performAction(byte source, byte action, IResource resource, |
450 |
IPath movePath); |
451 |
|
452 |
/** |
453 |
* <p>Gets the working location of the manager. This is where any relevant |
454 |
* state can be persisted.</p> |
455 |
* |
456 |
* @return the working location of the manager |
457 |
*/ |
458 |
protected abstract IPath getWorkingLocation(); |
459 |
|
460 |
/** |
461 |
* <p>Determines if a resource should be visited or not while |
462 |
* looking for files to index. If a resource should not be |
463 |
* visited it is assumed its children should not be visited either.</p> |
464 |
* |
465 |
* <p>Implementers may override. Default is to ignore resources |
466 |
* starting with a period.</p> |
467 |
* |
468 |
* @param resourceName name of the resource to determine if it |
469 |
* should be visited or not |
470 |
* |
471 |
* @return <code>true</code> if the resource should be visited, |
472 |
* <code>false</code> otherwise |
473 |
*/ |
474 |
protected boolean shouldVisit(String resourceName) { |
475 |
return !resourceName.startsWith(".");//$NON-NLS-1$ |
476 |
} |
477 |
|
478 |
/** |
479 |
* <p>Next time the manager starts up force a full workspace index</p> |
480 |
*/ |
481 |
private void forceFullReIndexNextStart() { |
482 |
IPath reIndexPath = |
483 |
AbstractIndexManager.this.getWorkingLocation().append(RE_PROCESS_FILE_NAME); |
484 |
File file = new File(reIndexPath.toOSString()); |
485 |
try { |
486 |
file.createNewFile(); |
487 |
} catch (IOException e) { |
488 |
Logger.logException(this.fName + ": Could not create file to tell manager to" + //$NON-NLS-1$ |
489 |
" do a full re-index on next load. " + //$NON-NLS-1$ |
490 |
AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); |
491 |
} |
492 |
} |
493 |
|
494 |
/** |
495 |
* @return <code>true</code> if a full workspace index is needed as dictated by |
496 |
* a previous call to {@link #forceFullReIndexNextStart()}, <code>false</code> |
497 |
* otherwise |
498 |
*/ |
499 |
private boolean isForcedFullReIndexNeeded() { |
500 |
boolean forcedFullReIndexNeeded = false; |
501 |
IPath reIndexPath = |
502 |
AbstractIndexManager.this.getWorkingLocation().append(RE_PROCESS_FILE_NAME); |
503 |
File file = new File(reIndexPath.toOSString()); |
504 |
if(file.exists()) { |
505 |
file.delete(); |
506 |
forcedFullReIndexNeeded = true; |
507 |
} |
508 |
|
509 |
return forcedFullReIndexNeeded; |
510 |
} |
511 |
|
512 |
/** |
513 |
* <p>A system {@link Job} used to visit all of the files in the workspace |
514 |
* looking for files to index.</p> |
515 |
* |
516 |
* <p>This should only have to be done once per workspace on the first load, |
517 |
* but if it fails or a SavedState can not be retrieved on a subsequent |
518 |
* workspace load then this will have to be done again.</p> |
519 |
*/ |
520 |
private class WorkspaceVisitorJob extends Job { |
521 |
/** |
522 |
* <p>Default constructor that sets up this job as a system job</p> |
523 |
*/ |
524 |
protected WorkspaceVisitorJob() { |
525 |
super(AbstractIndexManager.this.fName + ": " + //$NON-NLS-1$ |
526 |
SSECoreMessages.IndexManager_Processing_entire_workspace_for_the_first_time); |
527 |
|
528 |
this.setUser(false); |
529 |
this.setSystem(true); |
530 |
this.setPriority(Job.LONG); |
531 |
} |
532 |
|
533 |
/** |
534 |
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) |
535 |
*/ |
536 |
protected IStatus run(IProgressMonitor monitor) { |
537 |
try { |
538 |
//update status |
539 |
monitor.beginTask(AbstractIndexManager.this.fName + ": " + //$NON-NLS-1$ |
540 |
SSECoreMessages.IndexManager_Processing_entire_workspace_for_the_first_time, |
541 |
IProgressMonitor.UNKNOWN); |
542 |
|
543 |
//visit the workspace |
544 |
WorkspaceVisitor visitor = new WorkspaceVisitor(monitor); |
545 |
ResourcesPlugin.getWorkspace().getRoot().accept(visitor, IResource.NONE); |
546 |
|
547 |
//process any remaining batched up resources to index |
548 |
visitor.processBatchedResourceEvents(); |
549 |
} catch(CoreException e) { |
550 |
Logger.logException(AbstractIndexManager.this.fName + |
551 |
": Failed visiting entire workspace for initial index. " + //$NON-NLS-1$ |
552 |
AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); |
553 |
} |
554 |
|
555 |
IStatus status; |
556 |
if(monitor.isCanceled()) { |
557 |
status = Status.CANCEL_STATUS; |
558 |
} else { |
559 |
status = Status.OK_STATUS; |
560 |
} |
561 |
|
562 |
return status; |
563 |
} |
564 |
|
565 |
/** |
566 |
* <p>An {@link IResourceProxyVisitor} used to visit all of the files in the |
567 |
* workspace looking for files to add to the index.</p> |
568 |
* |
569 |
* <p><b>NOTE: </b>After this visitor is used {@link WorkspaceVisitor#processBatchedResourceEvents() |
570 |
* must be called to flush out the last of the {@link ResourceEvent}s produced |
571 |
* by this visitor.</p> |
572 |
*/ |
573 |
private class WorkspaceVisitor implements IResourceProxyVisitor { |
574 |
/** {@link IProgressMonitor} used to report status and check for cancellation */ |
575 |
private SubMonitor fProgress; |
576 |
|
577 |
/** |
578 |
* {@link Map}<{@link IResource}, {@link ResourceEvent}> |
579 |
* <p>Map of resources events created and batched up by this visitor. |
580 |
* These events are periodical be sent off to the |
581 |
* {@link ResourceEventProcessingJob} but need to be sent off |
582 |
* one final time after this visitor finishes it work.</p> |
583 |
* |
584 |
* @see #processBatchedResourceEvents() |
585 |
*/ |
586 |
private Map fBatchedResourceEvents; |
587 |
|
588 |
/** |
589 |
* <p>Default constructor</p> |
590 |
* @param monitor used to report status and allow this visitor to be canceled |
591 |
*/ |
592 |
protected WorkspaceVisitor(IProgressMonitor monitor) { |
593 |
this.fProgress = SubMonitor.convert(monitor); |
594 |
this.fBatchedResourceEvents = new LinkedHashMap(BATCH_UP_AMONT); |
595 |
} |
596 |
|
597 |
/** |
598 |
* <p>As long as the monitor is not canceled visit each file in the workspace |
599 |
* that should be visited.</p> |
600 |
* |
601 |
* @see org.eclipse.core.resources.IResourceProxyVisitor#visit(org.eclipse.core.resources.IResourceProxy) |
602 |
* @see AbstractIndexManager#shouldVisit(String) |
603 |
*/ |
604 |
public boolean visit(IResourceProxy proxy) throws CoreException { |
605 |
this.fProgress.subTask(proxy.getName()); |
606 |
|
607 |
boolean visitChildren = false; |
608 |
|
609 |
/* if not canceled or a hidden resource then process file |
610 |
* else don't visit children |
611 |
*/ |
612 |
if(!this.fProgress.isCanceled() && shouldVisit(proxy.getName())) { |
613 |
if(isResourceToIndex(proxy.getType(), proxy.getName())) { |
614 |
|
615 |
//add the file to be indexed |
616 |
IFile file = (IFile) proxy.requestResource(); |
617 |
if(file.exists()) { |
618 |
this.fBatchedResourceEvents.put(file, new ResourceEvent( |
619 |
AbstractIndexManager.SOURCE_WORKSPACE_SCAN, |
620 |
AbstractIndexManager.ACTION_ADD, |
621 |
null)); |
622 |
} |
623 |
} |
624 |
|
625 |
visitChildren = true; |
626 |
} else { |
627 |
visitChildren = false; |
628 |
} |
629 |
|
630 |
//batch up resource changes before sending them out |
631 |
if(this.fBatchedResourceEvents.size() >= BATCH_UP_AMONT) { |
632 |
this.processBatchedResourceEvents(); |
633 |
} |
634 |
|
635 |
return visitChildren; |
636 |
} |
637 |
|
638 |
/** |
639 |
* <p>Sends any batched up resource events created by this visitor to the |
640 |
* {@link ResourceEventProcessingJob}.<p> |
641 |
* |
642 |
* <p><b>NOTE:</b> This will be called every so often as the visitor is |
643 |
* visiting resources but needs to be called a final time by the user of |
644 |
* this visitor to be sure the final events are sent off</p> |
645 |
*/ |
646 |
protected void processBatchedResourceEvents() { |
647 |
AbstractIndexManager.this.fResourceEventProcessingJob.addResourceEvents( |
648 |
this.fBatchedResourceEvents); |
649 |
this.fBatchedResourceEvents.clear(); |
650 |
} |
651 |
} |
652 |
} |
653 |
|
654 |
/** |
655 |
* <p>Used to listen to resource change events in the workspace. These events |
656 |
* are batched up and then passed onto the {@link ResourceEventProcessingJob}.</p> |
657 |
*/ |
658 |
private class ResourceChangeListener implements IResourceChangeListener { |
659 |
/** |
660 |
* <p>The number of events currently being processed by this listener.</p> |
661 |
* <p>Use the {@link #fEventsBeingProcessedLock} when reading or writing this field</p> |
662 |
* |
663 |
* @see #fEventsBeingProcessedLock |
664 |
*/ |
665 |
private volatile int fEventsBeingProcessed; |
666 |
|
667 |
/** |
668 |
* Lock to use when reading or writing {@link #fEventsBeingProcessed} |
669 |
* |
670 |
* @see #fEventsBeingProcessed |
671 |
*/ |
672 |
private final Object fEventsBeingProcessedLock = new Object(); |
673 |
|
674 |
/** |
675 |
* <p>Current state of this listener</p> |
676 |
* |
677 |
* @see AbstractIndexManager#STATE_DISABLED |
678 |
* @see AbstractIndexManager#STATE_ENABLED |
679 |
*/ |
680 |
private volatile byte fState; |
681 |
|
682 |
/** |
683 |
* <p>Default constructor</p> |
684 |
*/ |
685 |
protected ResourceChangeListener() { |
686 |
this.fState = STATE_DISABLED; |
687 |
this.fEventsBeingProcessed = 0; |
688 |
} |
689 |
|
690 |
/** |
691 |
* <p>Start listening for resource change events</p> |
692 |
*/ |
693 |
protected void start() { |
694 |
this.fState = STATE_ENABLED; |
695 |
ResourcesPlugin.getWorkspace().addResourceChangeListener(this); |
696 |
} |
697 |
|
698 |
/** |
699 |
* <p>Stop listening for resource change events and if already processing |
700 |
* an event then wait for that processing to finish</p> |
701 |
* |
702 |
* @throws InterruptedException waiting for a current event to finish processing |
703 |
* could be interrupted |
704 |
*/ |
705 |
protected void stop() throws InterruptedException { |
706 |
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
707 |
|
708 |
//wait indefinitely for current event to finish processing |
709 |
this.waitForCurrentEvent(0); |
710 |
|
711 |
this.fState = STATE_DISABLED; |
712 |
} |
713 |
|
714 |
/** |
715 |
* <p>Blocks until either the current resource event has been processed or |
716 |
* until the given timeout has passed.</p> |
717 |
* |
718 |
* @param timeout block until either this timeout elapses (0 means never to timeout) |
719 |
* or the current resource change event finishes being processed |
720 |
* |
721 |
* @throws InterruptedException This can happen when waiting for a lock |
722 |
*/ |
723 |
protected void waitForCurrentEvent(int timeout) throws InterruptedException { |
724 |
synchronized (this.fEventsBeingProcessedLock) { |
725 |
if(this.fEventsBeingProcessed != 0) { |
726 |
this.fEventsBeingProcessedLock.wait(timeout); |
727 |
} |
728 |
} |
729 |
} |
730 |
|
731 |
/** |
732 |
* @return <code>true</code> if this listener is currently processing any |
733 |
* events, <code>false</code> otherwise. |
734 |
*/ |
735 |
protected boolean isProcessingEvents() { |
736 |
return this.fEventsBeingProcessed != 0; |
737 |
} |
738 |
|
739 |
/** |
740 |
* <p>Process a resource change event. If it is a pre-close or pre-delete then |
741 |
* the {@link ResourceEventProcessingJob} is paused so it does not try to |
742 |
* process resources that are about to be deleted. The {@link ResourceDeltaVisitor} |
743 |
* is used to actually process the event.</p> |
744 |
* |
745 |
* @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) |
746 |
* @see ResourceDeltaVisitor |
747 |
*/ |
748 |
public void resourceChanged(IResourceChangeEvent event) { |
749 |
try { |
750 |
//update the number of events being processed |
751 |
synchronized (this.fEventsBeingProcessedLock) { |
752 |
++this.fEventsBeingProcessed; |
753 |
} |
754 |
|
755 |
if(this.fState == STATE_ENABLED) { |
756 |
switch(event.getType()) { |
757 |
case IResourceChangeEvent.PRE_CLOSE: |
758 |
case IResourceChangeEvent.PRE_DELETE:{ |
759 |
//pre-close or pre-delete pause the persister job so it does not interfere |
760 |
AbstractIndexManager.this.fResourceEventProcessingJob.pause(); |
761 |
break; |
762 |
} |
763 |
case IResourceChangeEvent.POST_BUILD: |
764 |
case IResourceChangeEvent.POST_CHANGE: { |
765 |
//post change start up the indexer job and process the delta |
766 |
AbstractIndexManager.this.fResourceEventProcessingJob.unPause(); |
767 |
|
768 |
// only analyze the full (starting at root) delta hierarchy |
769 |
IResourceDelta delta = event.getDelta(); |
770 |
if (delta != null && delta.getFullPath().toString().equals("/")) { //$NON-NLS-1$ |
771 |
try { |
772 |
//use visitor to visit all children |
773 |
ResourceDeltaVisitor visitor = new ResourceDeltaVisitor( |
774 |
AbstractIndexManager.SOURCE_RESROUCE_CHANGE); |
775 |
delta.accept(visitor, false); |
776 |
|
777 |
//process any remaining batched up resources to index |
778 |
visitor.processBatchedResourceEvents(); |
779 |
} catch (CoreException e) { |
780 |
Logger.logException(AbstractIndexManager.this.fName + |
781 |
": Failed visiting resrouce change delta. " + //$NON-NLS-1$ |
782 |
AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); |
783 |
} |
784 |
} |
785 |
break; |
786 |
} |
787 |
} |
788 |
} else { |
789 |
Logger.log(Logger.ERROR, "A resource change event came in after " + |
790 |
AbstractIndexManager.this.fName + " shut down. This should never " + |
791 |
"ever happen, but if it does the index may now be inconsistant."); |
792 |
} |
793 |
} finally { |
794 |
//no matter how we exit be sure to update the number of events being processed |
795 |
synchronized (this.fEventsBeingProcessedLock) { |
796 |
--this.fEventsBeingProcessed; |
797 |
|
798 |
//if currently not events being processed, then notify |
799 |
if(this.fEventsBeingProcessed == 0) { |
800 |
this.fEventsBeingProcessedLock.notifyAll(); |
801 |
} |
802 |
} |
803 |
} |
804 |
} |
805 |
} |
806 |
|
807 |
/** |
808 |
* <p>Used to visit {@link IResourceDelta}s from both the {@link IResourceChangeListener} |
809 |
* and from a {@link ISaveParticipant} given to {@link AbstractIndexManager#start(IResourceDelta, IProgressMonitor)}. |
810 |
* The resource events are batched into groups of {@link AbstractIndexManager#BATCH_UP_AMONT} |
811 |
* before being passed onto the {@link ResourceEventProcessingJob}.</p> |
812 |
* |
813 |
* <p><b>NOTE 1: </b> This class is intended for one time use, thus a new instance should |
814 |
* be instantiated each time this visitor is needed to process a new {@link IResourceDelta}.</p> |
815 |
* |
816 |
* <p><b>NOTE 2: </b> Be sure to call {@link ResourceDeltaVisitor#processBatchedResourceEvents()} |
817 |
* after using this visitor to be sure any remaining events get passed onto the |
818 |
* {@link ResourceEventProcessingJob}.</p> |
819 |
* |
820 |
* @see ResourceDeltaVisitor#processBatchedResourceEvents() |
821 |
*/ |
822 |
private class ResourceDeltaVisitor implements IResourceDeltaVisitor { |
823 |
/** {@link IProgressMonitor} used to report status */ |
824 |
private SubMonitor fProgress; |
825 |
|
826 |
/** |
827 |
* <p>The source that should be used when sending resource events to the |
828 |
* {@link ResourceEventProcessingJob}.</p> |
829 |
* |
830 |
* @see AbstractIndexManager#SOURCE_SAVED_STATE |
831 |
* @see AbstractIndexManager#SOURCE_RESROUCE_CHANGE |
832 |
*/ |
833 |
private byte fSource; |
834 |
|
835 |
/** |
836 |
* <p>Due to the nature of a visitor it has no way of knowing the total amount |
837 |
* of work it has to do but it can start to predict it based on the number of |
838 |
* children of each event it processes and whether it plans on visiting those |
839 |
* children</p> |
840 |
*/ |
841 |
private int fPredictedWorkRemaining; |
842 |
|
843 |
/** |
844 |
* {@link Map}<{@link IResource}, {@link ResourceEvent}> |
845 |
* <p>Map of resources events created and batched up by this visitor. |
846 |
* These events are periodical be sent off to the |
847 |
* {@link ResourceEventProcessingJob} but need to be sent off |
848 |
* one final time after this visitor finishes it work.</p> |
849 |
* |
850 |
* @see #processBatchedResourceEvents() |
851 |
*/ |
852 |
private Map fBatchedResourceEvents; |
853 |
|
854 |
/** |
855 |
* <p>Creates a visitor that will create resource events based on the resources |
856 |
* it visits and using the given source as the source of the events.</p> |
857 |
* |
858 |
* @param source The source of the events that should be used when creating |
859 |
* resource events from visited resources |
860 |
* |
861 |
* @see AbstractIndexManager#SOURCE_RESROUCE_CHANGE |
862 |
* @see AbstractIndexManager#SOURCE_SAVED_STATE |
863 |
*/ |
864 |
protected ResourceDeltaVisitor(byte source) { |
865 |
this(SubMonitor.convert(null), source); |
866 |
} |
867 |
|
868 |
/** |
869 |
* <p>Creates a visitor that will create resource events based on the resources |
870 |
* it visits and using the given source as the source of the events and |
871 |
* report its status to the given progress as best it can as it visits |
872 |
* resources.</p> |
873 |
* |
874 |
* <p><b>NOTE:</b> While the {@link SubMonitor} is provided to report status the |
875 |
* visitor will not honor any cancellation requests.</p> |
876 |
* |
877 |
* @param progress Used to report status. This visitor can <b>not</b> be |
878 |
* canceled |
879 |
* @param source The source of the events that should be used when creating |
880 |
* resource events from visited resources |
881 |
* |
882 |
* @see AbstractIndexManager#SOURCE_RESROUCE_CHANGE |
883 |
* @see AbstractIndexManager#SOURCE_SAVED_STATE |
884 |
*/ |
885 |
protected ResourceDeltaVisitor(SubMonitor progress, byte source) { |
886 |
this.fProgress = progress; |
887 |
this.fSource = source; |
888 |
this.fBatchedResourceEvents = new LinkedHashMap(BATCH_UP_AMONT); |
889 |
this.fPredictedWorkRemaining = 1; |
890 |
} |
891 |
|
892 |
/** |
893 |
* <p>Transforms each {@link IResourceDelta} into a {@link ResourceEvent}. |
894 |
* Batches up these {@link ResourceEvent}s and then passes them onto the |
895 |
* {@link ResourceEventProcessingJob}.</p> |
896 |
* |
897 |
* <p><b>NOTE 2: </b> Be sure to call {@link ResourceDeltaVisitor#processBatchedResourceEvents()} |
898 |
* after using this visitor to be sure any remaining events get passed onto the |
899 |
* {@link ResourceEventProcessingJob}.</p> |
900 |
* |
901 |
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta) |
902 |
* @see #processBatchedResourceEvents() |
903 |
*/ |
904 |
public boolean visit(IResourceDelta delta) throws CoreException { |
905 |
//report status |
906 |
this.fProgress.subTask( |
907 |
NLS.bind(SSECoreMessages.IndexManager_0_resources_to_go, "" + fPredictedWorkRemaining) + //$NON-NLS-1$ |
908 |
": " + delta.getFullPath().toString()); //$NON-NLS-1$ |
909 |
|
910 |
//process delta if resource not hidden |
911 |
boolean visitChildren = false; |
912 |
IResource resource = delta.getResource(); |
913 |
if(shouldVisit(resource.getName())) { |
914 |
//check if should index resource |
915 |
if(isResourceToIndex(resource.getType(), resource.getName())) { |
916 |
|
917 |
switch (delta.getKind()) { |
918 |
case IResourceDelta.CHANGED : { |
919 |
/* ignore any change that is not a CONTENT, REPLACED, TYPE, |
920 |
* or MOVE_FROM change |
921 |
*/ |
922 |
if( !((delta.getFlags() & IResourceDelta.CONTENT) != 0) && |
923 |
!((delta.getFlags() & IResourceDelta.REPLACED) != 0) && |
924 |
!((delta.getFlags() & IResourceDelta.TYPE) != 0) && |
925 |
!(((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0))) { |
926 |
|
927 |
break; |
928 |
} |
929 |
} |
930 |
//$FALL-THROUGH$ it is intended that sometimes a change will fall through to add |
931 |
case IResourceDelta.ADDED : { |
932 |
if((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { |
933 |
//create add move from action |
934 |
this.fBatchedResourceEvents.put(resource, new ResourceEvent( |
935 |
this.fSource, |
936 |
AbstractIndexManager.ACTION_ADD_MOVE_FROM, |
937 |
delta.getMovedFromPath())); |
938 |
|
939 |
} else { |
940 |
//create add action |
941 |
this.fBatchedResourceEvents.put(resource, new ResourceEvent( |
942 |
this.fSource, |
943 |
AbstractIndexManager.ACTION_ADD, |
944 |
null)); |
945 |
} |
946 |
|
947 |
break; |
948 |
} |
949 |
case IResourceDelta.REMOVED : { |
950 |
if((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { |
951 |
//create remove move to action |
952 |
this.fBatchedResourceEvents.put(resource, new ResourceEvent( |
953 |
this.fSource, |
954 |
AbstractIndexManager.ACTION_REMOVE_MOVE_TO, |
955 |
delta.getMovedToPath())); |
956 |
} else { |
957 |
//create remove action |
958 |
this.fBatchedResourceEvents.put(resource, new ResourceEvent( |
959 |
this.fSource, |
960 |
AbstractIndexManager.ACTION_REMOVE, |
961 |
null)); |
962 |
} |
963 |
break; |
964 |
} |
965 |
} |
966 |
} |
967 |
|
968 |
visitChildren = true; |
969 |
} else { |
970 |
visitChildren = false; |
971 |
} |
972 |
|
973 |
//deal with trying to report progress |
974 |
if(visitChildren) { |
975 |
this.fPredictedWorkRemaining += delta.getAffectedChildren().length; |
976 |
} |
977 |
this.fProgress.setWorkRemaining(this.fPredictedWorkRemaining); |
978 |
this.fProgress.worked(1); |
979 |
--this.fPredictedWorkRemaining; |
980 |
|
981 |
//batch up resource changes before sending them out |
982 |
if(this.fBatchedResourceEvents.size() >= BATCH_UP_AMONT) { |
983 |
this.processBatchedResourceEvents(); |
984 |
} |
985 |
|
986 |
return visitChildren; |
987 |
} |
988 |
|
989 |
/** |
990 |
* <p>Sends any batched up resource events created by this visitor to the |
991 |
* {@link ResourceEventProcessingJob}.<p> |
992 |
* |
993 |
* <p><b>NOTE:</b> This will be called every so often as the visitor is |
994 |
* visiting resources but needs to be called a final time by the user of |
995 |
* this visitor to be sure the final events are sent off</p> |
996 |
*/ |
997 |
protected void processBatchedResourceEvents() { |
998 |
AbstractIndexManager.this.fResourceEventProcessingJob.addResourceEvents( |
999 |
this.fBatchedResourceEvents); |
1000 |
this.fBatchedResourceEvents.clear(); |
1001 |
} |
1002 |
} |
1003 |
|
1004 |
/** |
1005 |
* <p>Collects {@link ResourceEvent}s from the different sources and then processes |
1006 |
* each one by calling {@link AbstractIndexManager#performAction(byte, byte, IResource, IPath)} |
1007 |
* for each {@link ResourceEvent}.</p> |
1008 |
* |
1009 |
* @see AbstractIndexManager#performAction(byte, byte, IResource, IPath) |
1010 |
*/ |
1011 |
private class ResourceEventProcessingJob extends Job { |
1012 |
/** Length to delay when scheduling job */ |
1013 |
private static final int DELAY = 500; |
1014 |
|
1015 |
/** |
1016 |
* <p>Name of the file where resource events still to index |
1017 |
* will be preserved for the next start up.</p> |
1018 |
*/ |
1019 |
private static final String PRESERVED_RESOURCE_EVENTS_TO_PROCESS_FILE_NAME = ".preservedResourceEvents"; //$NON-NLS-1$ |
1020 |
|
1021 |
/** |
1022 |
* <p>This needs to be updated if {@link #preserveRecievedResourceEvents()} ever |
1023 |
* changes how it persists resource events so that {@link #loadPreservedRecievedResourceEvents(SubMonitor)} |
1024 |
* knows when opening a file that the format is of the current version and if not |
1025 |
* knows it does not know how to read the older version.</p> |
1026 |
* |
1027 |
* @see #preserveRecievedResourceEvents() |
1028 |
* @see #loadPreservedRecievedResourceEvents(SubMonitor) |
1029 |
*/ |
1030 |
private static final long serialVersionUID = 1L; |
1031 |
|
1032 |
/** Whether this job has been paused or not */ |
1033 |
private volatile boolean fIsPaused; |
1034 |
|
1035 |
/** |
1036 |
* {@link Map}<{@link IResource}, {@link ResourceEvent}> |
1037 |
* <p>The list of resources events to be processed</p> |
1038 |
*/ |
1039 |
private Map fResourceEvents; |
1040 |
|
1041 |
/** Lock used when accessing {@link #fBatchedResourceEvents} */ |
1042 |
private final Object fResourceEventsLock = new Object(); |
1043 |
|
1044 |
/** |
1045 |
* Locked used for allowing other jobs to wait on this job. This job |
1046 |
* will notify those waiting on this lock whenever it is done processing |
1047 |
* all resource events it currently knows about. |
1048 |
* |
1049 |
* @see #waitForConsistant(int) |
1050 |
*/ |
1051 |
private final Object fToNotifyLock = new Object(); |
1052 |
|
1053 |
/** |
1054 |
* <p>Sets up this job as a long running system job</p> |
1055 |
*/ |
1056 |
protected ResourceEventProcessingJob() { |
1057 |
super(AbstractIndexManager.this.fName + ": " + //$NON-NLS-1$ |
1058 |
SSECoreMessages.IndexManager_Processing_resource_events); |
1059 |
|
1060 |
//set this up as a long running system job |
1061 |
this.setUser(false); |
1062 |
this.setSystem(true); |
1063 |
this.setPriority(Job.LONG); |
1064 |
|
1065 |
this.fIsPaused = false; |
1066 |
this.fResourceEvents = new LinkedHashMap(); |
1067 |
} |
1068 |
|
1069 |
/** |
1070 |
* <p>Loads any preserved {@link ResourceEvent}s from the last time {@link #stop(boolean)} |
1071 |
* was invoked and schedules the job to be run</p> |
1072 |
* |
1073 |
* <p><b>NOTE: </b>Should be used instead of of calling {@link Job#schedule()} |
1074 |
* because this method also takes care of loading preserved state.</p> |
1075 |
* |
1076 |
* @param loadPreservedResourceEvents <code>true</code> if should load any |
1077 |
* preserved {@link ResourceEvent}s from the last time {@link #stop(boolean)} |
1078 |
* was invoked |
1079 |
* |
1080 |
* @return <code>true</code> if either <code>loadPreservedResourceEvents</code> |
1081 |
* was false or there was success in loading the preserved {@link ResourceEvent}s. |
1082 |
* If <code>false</code> then some {@link ResourceEvent}s may have been loosed |
1083 |
* and to insure index consistency with the workspace a full workspace re-index |
1084 |
* is needed. |
1085 |
* |
1086 |
* @see #stop(boolean) |
1087 |
*/ |
1088 |
protected synchronized boolean start(boolean loadPreservedResourceEvents, |
1089 |
SubMonitor progress) { |
1090 |
|
1091 |
boolean successLoadingPreserved = true; |
1092 |
|
1093 |
//attempt to load preserved resource events if requested |
1094 |
if(!loadPreservedResourceEvents) { |
1095 |
File preservedResourceEventsFile = this.getPreservedResourceEventsFile(); |
1096 |
preservedResourceEventsFile.delete(); |
1097 |
} else { |
1098 |
successLoadingPreserved = this.loadPreservedRecievedResourceEvents(progress); |
1099 |
} |
1100 |
|
1101 |
//start up the job |
1102 |
this.schedule(); |
1103 |
|
1104 |
return successLoadingPreserved; |
1105 |
} |
1106 |
|
1107 |
/** |
1108 |
* <p>Immediately stops the job and preserves any {@link ResourceEvent}s in the queue |
1109 |
* to be processed by not yet processed if requested</p> |
1110 |
* |
1111 |
* @param preserveResourceEvents <code>true</code> to preserve any {@link ResourceEvent}s |
1112 |
* in the queue yet to be processed, <code>false</code> otherwise |
1113 |
* |
1114 |
* @return <code>true</code> if either <code>preserveResourceEvents</code> is |
1115 |
* <code>false</code> or if there was success in preserving the {@link ResourceEvent}s |
1116 |
* yet to be processed. If <code>false</code> then the preserving failed and a |
1117 |
* full workspace re-processing is needed the next time the manger is started |
1118 |
* |
1119 |
* @throws InterruptedException This could happen when trying to cancel or join |
1120 |
* the job in progress, but it really shouldn't |
1121 |
* |
1122 |
* @see #start(boolean, SubMonitor) |
1123 |
*/ |
1124 |
protected synchronized boolean stop(boolean preserveResourceEvents) throws InterruptedException { |
1125 |
//this will not block indefinitely because it is known this job can be canceled |
1126 |
this.cancel(); |
1127 |
this.join(); |
1128 |
|
1129 |
//preserve if requested, else be sure no preserve file is left over for next start |
1130 |
boolean success = true; |
1131 |
if(preserveResourceEvents && this.hasResourceEventsToProcess()) { |
1132 |
success = this.preserveRecievedResourceEvents(); |
1133 |
} else { |
1134 |
this.getPreservedResourceEventsFile().delete(); |
1135 |
} |
1136 |
|
1137 |
return success; |
1138 |
} |
1139 |
|
1140 |
/** |
1141 |
* @return <code>true</code> if job is currently running or paused |
1142 |
* |
1143 |
* @see #pause() |
1144 |
* @see #unPause() |
1145 |
*/ |
1146 |
protected synchronized boolean isProcessing() { |
1147 |
return this.getState() != Job.NONE || this.fIsPaused; |
1148 |
} |
1149 |
|
1150 |
/** |
1151 |
* <p>Un-pauses this job. This has no effect if the job is already running.</p> |
1152 |
* <p>This should be used in place of {@link Job#schedule()} to reset state |
1153 |
* caused by calling {@link #pause()}</p> |
1154 |
* |
1155 |
* @see #pause() |
1156 |
*/ |
1157 |
protected synchronized void unPause() { |
1158 |
this.fIsPaused = false; |
1159 |
|
1160 |
//get the job running again depending on its current state |
1161 |
if(this.getState() == Job.SLEEPING) { |
1162 |
this.wakeUp(DELAY); |
1163 |
} else { |
1164 |
this.schedule(DELAY); |
1165 |
} |
1166 |
} |
1167 |
|
1168 |
/** |
1169 |
* <p>Pauses this job, even if it is running</p> |
1170 |
* <p>This should be used in place of {@link Job#sleep()} because {@link Job#sleep()} |
1171 |
* will not pause a job that is already running but calling this will pause this job |
1172 |
* even if it is running. {@link #unPause()} must be used to start this job again</p> |
1173 |
* |
1174 |
* @see #unPause() |
1175 |
*/ |
1176 |
protected synchronized void pause() { |
1177 |
//if job is already running this will force it to pause |
1178 |
this.fIsPaused = true; |
1179 |
|
1180 |
//this only works if the job is not running |
1181 |
this.sleep(); |
1182 |
} |
1183 |
|
1184 |
/** |
1185 |
* <p>Adds a batch of {@link ResourceEvent}s to the queue of events to be processed. |
1186 |
* Will also un-pause the job if it is not already running</p> |
1187 |
* |
1188 |
* @param resourceEvents {@link Map}<{@link IResource}, {@link ResourceEvent}> |
1189 |
* A batch of {@link ResourceEvent}s to be processed |
1190 |
* |
1191 |
* @see #addResourceEvent(ResourceEvent) |
1192 |
* @see #unPause() |
1193 |
*/ |
1194 |
protected void addResourceEvents(Map resourceEvents) { |
1195 |
Iterator iter = resourceEvents.keySet().iterator(); |
1196 |
while(iter.hasNext()) { |
1197 |
IResource resource = (IResource)iter.next(); |
1198 |
ResourceEvent resourceEvent = (ResourceEvent)resourceEvents.get(resource); |
1199 |
addResourceEvent(resource, resourceEvent); |
1200 |
} |
1201 |
|
1202 |
//un-pause the processor if it is not already running |
1203 |
if(!isProcessing()) { |
1204 |
this.unPause(); |
1205 |
} |
1206 |
} |
1207 |
|
1208 |
/** |
1209 |
* <p>Gets the number of {@link ResourceEvent}s left to process by this job. This |
1210 |
* count is only valid for the exact moment it is returned because events are |
1211 |
* constantly being added and removed from the queue of events to process</p> |
1212 |
* |
1213 |
* @return the number of {@link ResourceEvent}s left to process |
1214 |
*/ |
1215 |
protected int getNumResourceEventsToProcess() { |
1216 |
return this.fResourceEvents.size(); |
1217 |
} |
1218 |
|
1219 |
/** |
1220 |
* <p>Blocks until either the given timeout elapses (0 means never to timeout), or |
1221 |
* there are currently no {@link ResourceEvent}s to process or being processed |
1222 |
* by this job</p> |
1223 |
* |
1224 |
* @param timeout block until either this timeout elapses (0 means never to timeout) |
1225 |
* or there are currently no {@link ResourceEvent}s to process or being processed |
1226 |
* by this job |
1227 |
* |
1228 |
* @throws InterruptedException This can happen when waiting for a lock |
1229 |
*/ |
1230 |
protected void waitForConsistant(int timeout) throws InterruptedException { |
1231 |
if(hasResourceEventsToProcess() || isProcessing()) { |
1232 |
synchronized (this.fToNotifyLock) { |
1233 |
this.fToNotifyLock.wait(timeout); |
1234 |
} |
1235 |
} |
1236 |
} |
1237 |
|
1238 |
/** |
1239 |
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) |
1240 |
*/ |
1241 |
protected IStatus run(IProgressMonitor monitor) { |
1242 |
try { |
1243 |
//report status |
1244 |
SubMonitor progress = SubMonitor.convert(monitor); |
1245 |
|
1246 |
while(!this.fIsPaused && !monitor.isCanceled() && this.hasResourceEventsToProcess()) { |
1247 |
//report status |
1248 |
progress.setTaskName(AbstractIndexManager.this.fName + ": " + //$NON-NLS-1$ |
1249 |
NLS.bind(SSECoreMessages.IndexManager_Indexing_0_Files, |
1250 |
"" + getNumResourceEventsToProcess())); //$NON-NLS-1$ |
1251 |
progress.setWorkRemaining(getNumResourceEventsToProcess()); |
1252 |
|
1253 |
//get the next event to process |
1254 |
ResourceEvent resourceEvent = null; |
1255 |
IResource resource = null; |
1256 |
synchronized (this.fResourceEventsLock) { |
1257 |
resource = (IResource) this.fResourceEvents.keySet().iterator().next(); |
1258 |
resourceEvent = (ResourceEvent)this.fResourceEvents.remove(resource); |
1259 |
} |
1260 |
|
1261 |
//report status |
1262 |
monitor.subTask(resource.getName()); |
1263 |
|
1264 |
//perform action safely |
1265 |
final byte source = resourceEvent.fSource; |
1266 |
final byte action = resourceEvent.fAction; |
1267 |
final IResource finResource = resource; |
1268 |
final IPath movePath = resourceEvent.fMovePath; |
1269 |
SafeRunner.run(new ISafeRunnable() { |
1270 |
public void run() throws Exception { |
1271 |
AbstractIndexManager.this.performAction(source, action, |
1272 |
finResource, movePath); |
1273 |
} |
1274 |
|
1275 |
public void handleException(Throwable e) { |
1276 |
Logger.logException("Error while performing an update to the index. " + //$NON-NLS-1$ |
1277 |
AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); |
1278 |
} |
1279 |
}); |
1280 |
|
1281 |
//report progress |
1282 |
progress.worked(1); |
1283 |
|
1284 |
//avoid dead locks |
1285 |
Job.getJobManager().currentJob().yieldRule(monitor); |
1286 |
} |
1287 |
|
1288 |
//done work |
1289 |
monitor.done(); |
1290 |
} finally { |
1291 |
//want to be sure we notify no matter how we exit |
1292 |
this.notifyIfConsistant(); |
1293 |
} |
1294 |
|
1295 |
/* if canceled then return CANCEL, |
1296 |
* else if done or paused return OK |
1297 |
*/ |
1298 |
IStatus exitStatus; |
1299 |
if(monitor.isCanceled()) { |
1300 |
exitStatus = Status.CANCEL_STATUS; |
1301 |
} else { |
1302 |
exitStatus = Status.OK_STATUS; |
1303 |
} |
1304 |
|
1305 |
return exitStatus; |
1306 |
} |
1307 |
|
1308 |
/** |
1309 |
* <p>If resource not already scheduled to be processed, schedule it |
1310 |
* else if resource already scheduled to be processed, update the action only if |
1311 |
* the new action comes from a resource change event.</p> |
1312 |
* |
1313 |
* <p>Ignore other sources for updating existing resource events because all other |
1314 |
* sources are "start-up" type sources and thus only {@link ResourceEvent} with a |
1315 |
* source of a resource change event trump existing events.</p> |
1316 |
* |
1317 |
* @param resourceEvent {@link ResourceEvent} to be processed by this job |
1318 |
*/ |
1319 |
private void addResourceEvent(IResource resource, ResourceEvent resourceEvent) { |
1320 |
|
1321 |
synchronized (this.fResourceEventsLock) { |
1322 |
/* if resource not already scheduled to be processed, schedule it |
1323 |
* else if resource already scheduled to be processed, update the action only if |
1324 |
* the new action comes from a resource change event |
1325 |
*/ |
1326 |
if(!this.fResourceEvents.containsKey(resource)) { |
1327 |
this.fResourceEvents.put(resource, resourceEvent); |
1328 |
} else if(resourceEvent.fSource == AbstractIndexManager.SOURCE_RESROUCE_CHANGE) { |
1329 |
((ResourceEvent)this.fResourceEvents.get(resource)).fAction = resourceEvent.fAction; |
1330 |
} else { |
1331 |
//Purposely ignoring all other resource events |
1332 |
} |
1333 |
} |
1334 |
} |
1335 |
|
1336 |
/** |
1337 |
* @return <code>true</code> if there are any resources to process, |
1338 |
* <code>false</code> otherwise |
1339 |
*/ |
1340 |
private boolean hasResourceEventsToProcess() { |
1341 |
return !this.fResourceEvents.isEmpty(); |
1342 |
} |
1343 |
|
1344 |
/** |
1345 |
* <p>Preserves all of the resource events that have been received by this |
1346 |
* manager but not yet processed</p> |
1347 |
* |
1348 |
* <p>If this operation was successful then the next time the manager starts |
1349 |
* it can load these events and process them. If it was not successful then a |
1350 |
* full re-processing of the entire workspace will need to take place to be |
1351 |
* sure the index is consistent.</p> |
1352 |
* |
1353 |
* <p><b>NOTE:</b> If this method changes how it preserves these events then |
1354 |
* {@link #serialVersionUID} will need to be incremented so that the manger |
1355 |
* does not attempt to load an old version of the file that may exist in a users |
1356 |
* workspace. Also {@link #loadPreservedRecievedResourceEvents(SubMonitor)} will |
1357 |
* have to be updated to load the new file structure</p> |
1358 |
* |
1359 |
* @return <code>true</code> if successfully preserved the resources the resource |
1360 |
* events that have been received by not yet processed, <code>false</code> otherwise |
1361 |
* |
1362 |
* @see #serialVersionUID |
1363 |
* @see #loadPreservedRecievedResourceEvents(SubMonitor) |
1364 |
*/ |
1365 |
private boolean preserveRecievedResourceEvents() { |
1366 |
File preservedResourceEventsFile = this.getPreservedResourceEventsFile(); |
1367 |
boolean success = true; |
1368 |
|
1369 |
synchronized (this.fResourceEventsLock) { |
1370 |
DataOutputStream dos = null; |
1371 |
try { |
1372 |
//if file already exists delete it |
1373 |
if(preservedResourceEventsFile.exists()) { |
1374 |
preservedResourceEventsFile.delete(); |
1375 |
preservedResourceEventsFile.createNewFile(); |
1376 |
} |
1377 |
|
1378 |
//create output objects |
1379 |
FileOutputStream fos = new FileOutputStream(preservedResourceEventsFile); |
1380 |
BufferedOutputStream bos = new BufferedOutputStream(fos); |
1381 |
dos = new DataOutputStream(bos); |
1382 |
|
1383 |
//write serial version |
1384 |
dos.writeLong(serialVersionUID); |
1385 |
|
1386 |
//write size |
1387 |
dos.writeInt(this.getNumResourceEventsToProcess()); |
1388 |
|
1389 |
//write out all the information needed to restore the resource events to process |
1390 |
Iterator iter = this.fResourceEvents.keySet().iterator(); |
1391 |
while(iter.hasNext()) { |
1392 |
IResource resource = (IResource)iter.next(); |
1393 |
ResourceEvent resourceEvent = (ResourceEvent)this.fResourceEvents.get(resource); |
1394 |
|
1395 |
if(resourceEvent.fSource != AbstractIndexManager.SOURCE_WORKSPACE_SCAN) { |
1396 |
//write out information |
1397 |
dos.writeByte(resourceEvent.fAction); |
1398 |
dos.writeByte(resource.getType()); |
1399 |
dos.writeBytes(resource.getLocation().toPortableString()); |
1400 |
dos.writeByte('\0'); |
1401 |
if(resourceEvent.fMovePath != null) { |
1402 |
dos.writeBytes(resourceEvent.fMovePath.toPortableString()); |
1403 |
} |
1404 |
dos.writeByte('\0'); |
1405 |
} |
1406 |
} |
1407 |
|
1408 |
this.fResourceEvents.clear(); |
1409 |
|
1410 |
dos.flush(); |
1411 |
} catch (FileNotFoundException e) { |
1412 |
Logger.logException(AbstractIndexManager.this.fName + |
1413 |
": Exception while opening file to preserve resrouces to index.", //$NON-NLS-1$ |
1414 |
e); |
1415 |
success = false; |
1416 |
} catch (IOException e) { |
1417 |
Logger.logException(AbstractIndexManager.this.fName + |
1418 |
": Exception while writing to file to preserve resrouces to index.", //$NON-NLS-1$ |
1419 |
e); |
1420 |
success = false; |
1421 |
} finally { |
1422 |
//be sure to close output |
1423 |
if(dos != null) { |
1424 |
try { |
1425 |
dos.close(); |
1426 |
} catch (IOException e) { |
1427 |
Logger.logException(AbstractIndexManager.this.fName + |
1428 |
": Exception while closing file with preserved resources to index.", //$NON-NLS-1$ |
1429 |
e); |
1430 |
success = false; |
1431 |
} |
1432 |
} |
1433 |
} |
1434 |
|
1435 |
//if failed, for consistency must do a full re-process next workspace load |
1436 |
if(!success) { |
1437 |
preservedResourceEventsFile.delete(); |
1438 |
} |
1439 |
} |
1440 |
|
1441 |
return success; |
1442 |
} |
1443 |
|
1444 |
/** |
1445 |
* <p>Loads the received resource events that were preserved during the manger's |
1446 |
* last shut down so they can be processed now</p> |
1447 |
* |
1448 |
* <p>If this operation is not successful then a full re-processing of the |
1449 |
* entire workspace is needed to be sure the index is consistent.</p> |
1450 |
* |
1451 |
* @param progress used to report status of loading the preserved received resource events |
1452 |
* @return <code>true</code> if the loading of the preserved received resource events |
1453 |
* was successful, <code>false</code> otherwise. |
1454 |
* |
1455 |
* @see #serialVersionUID |
1456 |
* @see #preserveRecievedResourceEvents() |
1457 |
*/ |
1458 |
private boolean loadPreservedRecievedResourceEvents(SubMonitor progress) { |
1459 |
progress.subTask(SSECoreMessages.IndexManager_processing_deferred_resource_changes); |
1460 |
|
1461 |
boolean success = true; |
1462 |
File preservedResourceEventsFile = this.getPreservedResourceEventsFile(); |
1463 |
|
1464 |
if(preservedResourceEventsFile.exists()) { |
1465 |
Map preservedResrouceEvents = null; |
1466 |
|
1467 |
DataInputStream dis = null; |
1468 |
try { |
1469 |
FileInputStream fis = new FileInputStream(preservedResourceEventsFile); |
1470 |
BufferedInputStream bis = new BufferedInputStream(fis); |
1471 |
dis = new DataInputStream(bis); |
1472 |
|
1473 |
//check serial version first |
1474 |
long preservedSerialVersionUID = dis.readLong(); |
1475 |
if(preservedSerialVersionUID == serialVersionUID) { |
1476 |
|
1477 |
//read each record |
1478 |
int numberOfRecords = dis.readInt(); |
1479 |
preservedResrouceEvents = new LinkedHashMap(numberOfRecords); |
1480 |
progress.setWorkRemaining(numberOfRecords); |
1481 |
for(int i = 0; i < numberOfRecords; ++i) { |
1482 |
//action is first byte |
1483 |
byte action = dis.readByte(); |
1484 |
|
1485 |
//file type is the next byte |
1486 |
byte fileType = dis.readByte(); |
1487 |
|
1488 |
//resource location are the next bytes |
1489 |
StringBuffer resourceLocationBuffer = new StringBuffer(); |
1490 |
byte b = dis.readByte(); |
1491 |
while(b != '\0') { |
1492 |
resourceLocationBuffer.append(b); |
1493 |
} |
1494 |
|
1495 |
//move path are the next bytes |
1496 |
StringBuffer movePathBuffer = new StringBuffer(); |
1497 |
b = dis.readByte(); |
1498 |
while(b != '\0') { |
1499 |
movePathBuffer.append(b); |
1500 |
} |
1501 |
|
1502 |
//get the resource |
1503 |
IResource resource = null; |
1504 |
IPath resourcePath = new Path(resourceLocationBuffer.toString()); |
1505 |
if(fileType == IResource.FILE) { |
1506 |
resource = |
1507 |
ResourcesPlugin.getWorkspace().getRoot().getFile(resourcePath); |
1508 |
} else { |
1509 |
resource = |
1510 |
ResourcesPlugin.getWorkspace().getRoot().getFolder(resourcePath); |
1511 |
} |
1512 |
|
1513 |
//get the move path |
1514 |
IPath movePath = null; |
1515 |
if(movePathBuffer.length() != 0) { |
1516 |
movePath = new Path(movePathBuffer.toString()); |
1517 |
} |
1518 |
|
1519 |
//add the object to the list of of preserved resources |
1520 |
preservedResrouceEvents.put(resource, new ResourceEvent( |
1521 |
AbstractIndexManager.SOURCE_PRESERVED_RESOURCES_TO_INDEX, |
1522 |
action, movePath)); |
1523 |
|
1524 |
progress.worked(1); |
1525 |
} |
1526 |
} else { |
1527 |
success = false; |
1528 |
} |
1529 |
} catch (FileNotFoundException e) { |
1530 |
Logger.logException(AbstractIndexManager.this.fName + |
1531 |
": Exception while opening file to read preserved resrouces to index.", //$NON-NLS-1$ |
1532 |
e); |
1533 |
success = false; |
1534 |
} catch (IOException e) { |
1535 |
Logger.logException(AbstractIndexManager.this.fName + |
1536 |
": Exception while reading from file of preserved resrouces to index.", //$NON-NLS-1$ |
1537 |
e); |
1538 |
success = false; |
1539 |
} finally { |
1540 |
if(dis != null) { |
1541 |
try { |
1542 |
dis.close(); |
1543 |
} catch (IOException e) { |
1544 |
Logger.logException(AbstractIndexManager.this.fName + |
1545 |
": Exception while closing file of preserved resources" + //$NON-NLS-1$ |
1546 |
" to index that was just read. This should have no" + //$NON-NLS-1$ |
1547 |
" effect on the consistency of the index.", //$NON-NLS-1$ |
1548 |
e); |
1549 |
} |
1550 |
} |
1551 |
} |
1552 |
|
1553 |
//if success loading preserved then add to master list |
1554 |
if(success) { |
1555 |
synchronized (this.fResourceEventsLock) { |
1556 |
Iterator iter = preservedResrouceEvents.keySet().iterator(); |
1557 |
while(iter.hasNext()) { |
1558 |
IResource resource = (IResource)iter.next(); |
1559 |
ResourceEvent event = (ResourceEvent)preservedResrouceEvents.get(resource); |
1560 |
this.fResourceEvents.put(resource, event); |
1561 |
} |
1562 |
} |
1563 |
} else { |
1564 |
//failed reading file, so delete it |
1565 |
preservedResourceEventsFile.delete(); |
1566 |
} |
1567 |
} |
1568 |
|
1569 |
return success; |
1570 |
} |
1571 |
|
1572 |
/** |
1573 |
* @return {@link File} that contains any resource events received but not processed |
1574 |
* by this manager the last time it shutdown. This file may or may not actually |
1575 |
* exist. |
1576 |
* |
1577 |
* @see #preserveRecievedResourceEvents() |
1578 |
* @see #loadPreservedRecievedResourceEvents(SubMonitor) |
1579 |
*/ |
1580 |
private File getPreservedResourceEventsFile() { |
1581 |
IPath preservedResroucesToIndexPath = |
1582 |
AbstractIndexManager.this.getWorkingLocation().append( |
1583 |
PRESERVED_RESOURCE_EVENTS_TO_PROCESS_FILE_NAME); |
1584 |
return new File(preservedResroucesToIndexPath.toOSString()); |
1585 |
} |
1586 |
|
1587 |
/** |
1588 |
* <p>If all resource events have been processed |
1589 |
*/ |
1590 |
private void notifyIfConsistant() { |
1591 |
if(!this.hasResourceEventsToProcess()) { |
1592 |
synchronized (this.fToNotifyLock) { |
1593 |
this.fToNotifyLock.notifyAll(); |
1594 |
} |
1595 |
} |
1596 |
} |
1597 |
} |
1598 |
|
1599 |
/** |
1600 |
* <p>Represents a resource that was discovered by this manager. Contains |
1601 |
* all the information this manager and the index needs to know about this |
1602 |
* resource. Such has how the manager was notified about this resource and |
1603 |
* the type of action occurring on the resource.</p> |
1604 |
*/ |
1605 |
private static class ResourceEvent { |
1606 |
/** |
1607 |
* <p>The source of this resource event</p> |
1608 |
* |
1609 |
* @see AbstractIndexManager#SOURCE_RESROUCE_CHANGE |
1610 |
* @see AbstractIndexManager#SOURCE_SAVED_STATE |
1611 |
* @see AbstractIndexManager#SOURCE_WORKSPACE_SCAN |
1612 |
* @see AbstractIndexManager#SOURCE_PRESERVED_RESOURCES_TO_INDEX |
1613 |
*/ |
1614 |
protected byte fSource; |
1615 |
|
1616 |
/** |
1617 |
* <p>The action that the index should take with this resource</p> |
1618 |
* |
1619 |
* @see AbstractIndexManager#ACTION_ADD |
1620 |
* @see AbstractIndexManager#ACTION_REMOVE |
1621 |
* @see AbstractIndexManager#ACTION_ADD_MOVE_FROM |
1622 |
* @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO |
1623 |
*/ |
1624 |
protected byte fAction; |
1625 |
|
1626 |
/** |
1627 |
* |
1628 |
* <p>If the {@link #fAction} is {@link AbstractIndexManager#ACTION_ADD_MOVE_FROM} or |
1629 |
* {@link AbstractIndexManager#ACTION_REMOVE_MOVE_TO} then this field will have the |
1630 |
* path the resource was moved from or moved to. Else this field will be <code>null</code></p> |
1631 |
* |
1632 |
* <p><b>NOTE: </b>Maybe <code>null</code>.</p> |
1633 |
* |
1634 |
* @see AbstractIndexManager#ACTION_ADD_MOVE_FROM |
1635 |
* @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO |
1636 |
*/ |
1637 |
protected IPath fMovePath; |
1638 |
|
1639 |
/** |
1640 |
* <p>Creates a resource event that the index needs to react to in some way</p> |
1641 |
* |
1642 |
* @param source source that the manger used to learn of this resource |
1643 |
* @param action action the index should take on this resource |
1644 |
* @param resource resource that the index should know about |
1645 |
* @param movePath if action is {@link AbstractIndexManager#ACTION_ADD_MOVE_FROM} or |
1646 |
* {@link AbstractIndexManager#ACTION_REMOVE_MOVE_TO} then this should be the path the |
1647 |
* resource was moved from or moved to respectively, else should be <code>null</code> |
1648 |
* |
1649 |
* @see AbstractIndexManager#SOURCE_RESROUCE_CHANGE |
1650 |
* @see AbstractIndexManager#SOURCE_SAVED_STATE |
1651 |
* @see AbstractIndexManager#SOURCE_WORKSPACE_SCAN |
1652 |
* @see AbstractIndexManager#SOURCE_PRESERVED_RESOURCES_TO_INDEX |
1653 |
* |
1654 |
* @see AbstractIndexManager#ACTION_ADD |
1655 |
* @see AbstractIndexManager#ACTION_REMOVE |
1656 |
* @see AbstractIndexManager#ACTION_ADD_MOVE_FROM |
1657 |
* @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO |
1658 |
*/ |
1659 |
protected ResourceEvent(byte source, byte action, IPath movePath) { |
1660 |
this.fSource = source; |
1661 |
this.fAction = action; |
1662 |
this.fMovePath = movePath; |
1663 |
} |
1664 |
} |
1665 |
} |