|
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 |
} |