1 /******************************************************************************* 2 * Copyright (c) 2000, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.core; 15 16 import java.io.BufferedInputStream; 17 import java.io.BufferedOutputStream; 18 import java.io.DataInputStream; 19 import java.io.DataOutputStream; 20 import java.io.File; 21 import java.io.FileInputStream; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.util.*; 25 import java.util.Map.Entry; 26 27 import org.eclipse.core.resources.*; 28 import org.eclipse.core.runtime.*; 29 import org.eclipse.jdt.core.*; 30 import org.eclipse.jdt.internal.core.DeltaProcessor.RootInfo; 31 import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo; 32 import org.eclipse.jdt.internal.core.nd.indexer.Indexer; 33 import org.eclipse.jdt.internal.core.nd.indexer.IndexerEvent; 34 import org.eclipse.jdt.internal.core.nd.java.JavaIndex; 35 import org.eclipse.jdt.internal.core.util.Util; 36 37 /** 38 * Keep the global states used during Java element delta processing. 39 */ 40 public class DeltaProcessingState implements IResourceChangeListener, Indexer.Listener { 41 42 /* 43 * Collection of listeners for Java element deltas 44 */ 45 public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5]; 46 public int[] elementChangedListenerMasks = new int[5]; 47 public int elementChangedListenerCount = 0; 48 49 /* 50 * Collection of pre Java resource change listeners 51 */ 52 public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1]; 53 public int[] preResourceChangeEventMasks = new int[1]; 54 public int preResourceChangeListenerCount = 0; 55 56 /* 57 * The delta processor for the current thread. 58 */ 59 private ThreadLocal<DeltaProcessor> deltaProcessors = new ThreadLocal<>(); 60 doNotUse()61 public void doNotUse() { 62 // reset the delta processor of the current thread to avoid to keep it in memory 63 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=269476 64 this.deltaProcessors.set(null); 65 } 66 67 /* A table from IPath (from a classpath entry) to DeltaProcessor.RootInfo */ 68 public Map<IPath, RootInfo> roots = new LinkedHashMap<>(); 69 70 /* A table from IPath (from a classpath entry) to ArrayList of DeltaProcessor.RootInfo 71 * Used when an IPath corresponds to more than one root */ 72 public Map<IPath, List<RootInfo>> otherRoots = new HashMap<>(); 73 74 /* A table from IPath (from a classpath entry) to DeltaProcessor.RootInfo 75 * from the last time the delta processor was invoked. */ 76 public Map<IPath, RootInfo> oldRoots = new LinkedHashMap<>(); 77 78 /* A table from IPath (from a classpath entry) to ArrayList of DeltaProcessor.RootInfo 79 * from the last time the delta processor was invoked. 80 * Used when an IPath corresponds to more than one root */ 81 public Map<IPath, List<RootInfo>> oldOtherRoots = new HashMap<>(); 82 83 /* A table from IPath (a source attachment path from a classpath entry) to IPath (a root path) */ 84 public Map<IPath, IPath> sourceAttachments = new HashMap<>(); 85 86 /* A table from IJavaProject to IJavaProject[] (the list of direct dependent of the key) */ 87 public Map<IJavaProject, IJavaProject[]> projectDependencies = new HashMap<>(); 88 89 /* Whether the roots tables should be recomputed */ 90 public boolean rootsAreStale = true; 91 92 /* Threads that are currently running initializeRoots() */ 93 private Set<Thread> initializingThreads = Collections.synchronizedSet(new HashSet<>()); 94 95 /* A table from file system absoulte path (String) to timestamp (Long) */ 96 public Hashtable<IPath, Long> externalTimeStamps; 97 98 /* 99 * Map from IProject to ClasspathChange 100 * Note these changes need to be kept on the delta processing state to ensure we don't loose them 101 * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=271102 Java model corrupt after switching target platform) 102 */ 103 private Map<IProject, ClasspathChange> classpathChanges = new LinkedHashMap<>(); 104 105 /* A table from JavaProject to ClasspathValidation */ 106 private Map<JavaProject, ClasspathValidation> classpathValidations = new LinkedHashMap<>(); 107 108 /* A table from JavaProject to ProjectReferenceChange */ 109 private Set<IJavaProject> projectReferenceChanges = new LinkedHashSet<>(); 110 111 /* A table from JavaProject to ExternalFolderChange */ 112 private Map<JavaProject, ExternalFolderChange> externalFolderChanges = new LinkedHashMap<>(); 113 114 /** 115 * Workaround for bug 15168 circular errors not reported 116 * This is a cache of the projects before any project addition/deletion has started. 117 */ 118 private Set<String> javaProjectNamesCache; 119 120 /* 121 * A list of IJavaElement used as a scope for external archives refresh during POST_CHANGE. 122 * This is null if no refresh is needed. 123 */ 124 private Set<IJavaElement> externalElementsToRefresh; 125 126 /* 127 * Need to clone defensively the listener information, in case some listener is reacting to some notification iteration by adding/changing/removing 128 * any of the other (for example, if it deregisters itself). 129 */ addElementChangedListener(IElementChangedListener listener, int eventMask)130 public synchronized void addElementChangedListener(IElementChangedListener listener, int eventMask) { 131 for (int i = 0; i < this.elementChangedListenerCount; i++){ 132 if (this.elementChangedListeners[i] == listener){ 133 134 // only clone the masks, since we could be in the middle of notifications and one listener decide to change 135 // any event mask of another listeners (yet not notified). 136 int cloneLength = this.elementChangedListenerMasks.length; 137 System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[cloneLength], 0, cloneLength); 138 this.elementChangedListenerMasks[i] |= eventMask; // could be different 139 return; 140 } 141 } 142 // may need to grow, no need to clone, since iterators will have cached original arrays and max boundary and we only add to the end. 143 int length; 144 if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount){ 145 System.arraycopy(this.elementChangedListeners, 0, this.elementChangedListeners = new IElementChangedListener[length*2], 0, length); 146 System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[length*2], 0, length); 147 } 148 this.elementChangedListeners[this.elementChangedListenerCount] = listener; 149 this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask; 150 this.elementChangedListenerCount++; 151 } 152 153 /* 154 * Adds the given element to the list of elements used as a scope for external jars refresh. 155 */ addForRefresh(IJavaElement externalElement)156 public synchronized void addForRefresh(IJavaElement externalElement) { 157 if (this.externalElementsToRefresh == null) { 158 this.externalElementsToRefresh = new LinkedHashSet<>(); 159 } 160 this.externalElementsToRefresh.add(externalElement); 161 } 162 addPreResourceChangedListener(IResourceChangeListener listener, int eventMask)163 public synchronized void addPreResourceChangedListener(IResourceChangeListener listener, int eventMask) { 164 for (int i = 0; i < this.preResourceChangeListenerCount; i++){ 165 if (this.preResourceChangeListeners[i] == listener) { 166 this.preResourceChangeEventMasks[i] |= eventMask; 167 return; 168 } 169 } 170 // may need to grow, no need to clone, since iterators will have cached original arrays and max boundary and we only add to the end. 171 int length; 172 if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount) { 173 System.arraycopy(this.preResourceChangeListeners, 0, this.preResourceChangeListeners = new IResourceChangeListener[length*2], 0, length); 174 System.arraycopy(this.preResourceChangeEventMasks, 0, this.preResourceChangeEventMasks = new int[length*2], 0, length); 175 } 176 this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener; 177 this.preResourceChangeEventMasks[this.preResourceChangeListenerCount] = eventMask; 178 this.preResourceChangeListenerCount++; 179 } 180 getDeltaProcessor()181 public DeltaProcessor getDeltaProcessor() { 182 DeltaProcessor deltaProcessor = this.deltaProcessors.get(); 183 if (deltaProcessor != null) return deltaProcessor; 184 deltaProcessor = new DeltaProcessor(this, JavaModelManager.getJavaModelManager()); 185 this.deltaProcessors.set(deltaProcessor); 186 return deltaProcessor; 187 } 188 addClasspathChange(IProject project, IClasspathEntry[] oldRawClasspath, IPath oldOutputLocation, IClasspathEntry[] oldResolvedClasspath)189 public ClasspathChange addClasspathChange(IProject project, IClasspathEntry[] oldRawClasspath, IPath oldOutputLocation, IClasspathEntry[] oldResolvedClasspath) { 190 synchronized (this.classpathChanges) { 191 ClasspathChange change = this.classpathChanges.get(project); 192 if (change == null) { 193 change = new ClasspathChange((JavaProject) JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(project), oldRawClasspath, oldOutputLocation, oldResolvedClasspath); 194 this.classpathChanges.put(project, change); 195 } else { 196 if (change.oldRawClasspath == null) 197 change.oldRawClasspath = oldRawClasspath; 198 if (change.oldOutputLocation == null) 199 change.oldOutputLocation = oldOutputLocation; 200 if (change.oldResolvedClasspath == null) 201 change.oldResolvedClasspath = oldResolvedClasspath; 202 } 203 return change; 204 } 205 } 206 getClasspathChange(IProject project)207 public ClasspathChange getClasspathChange(IProject project) { 208 synchronized (this.classpathChanges) { 209 return this.classpathChanges.get(project); 210 } 211 } 212 removeAllClasspathChanges()213 public Map<IProject, ClasspathChange> removeAllClasspathChanges() { 214 synchronized (this.classpathChanges) { 215 Map<IProject, ClasspathChange> result = this.classpathChanges; 216 this.classpathChanges = new LinkedHashMap<>(result.size()); 217 return result; 218 } 219 } 220 addClasspathValidation(JavaProject project)221 public synchronized ClasspathValidation addClasspathValidation(JavaProject project) { 222 ClasspathValidation validation = this.classpathValidations.get(project); 223 if (validation == null) { 224 validation = new ClasspathValidation(project); 225 this.classpathValidations.put(project, validation); 226 } 227 return validation; 228 } 229 addExternalFolderChange(JavaProject project, IClasspathEntry[] oldResolvedClasspath)230 public synchronized void addExternalFolderChange(JavaProject project, IClasspathEntry[] oldResolvedClasspath) { 231 ExternalFolderChange change = this.externalFolderChanges.get(project); 232 if (change == null) { 233 change = new ExternalFolderChange(project, oldResolvedClasspath); 234 this.externalFolderChanges.put(project, change); 235 } 236 } 237 addProjectReferenceChange(IJavaProject project)238 public synchronized void addProjectReferenceChange(IJavaProject project) { 239 this.projectReferenceChanges.add(project); 240 } 241 initializeRoots(boolean initAfterLoad)242 public void initializeRoots(boolean initAfterLoad) { 243 244 // recompute root infos only if necessary 245 RootInfos rootInfos = null; 246 if (this.rootsAreStale) { 247 Thread currentThread = Thread.currentThread(); 248 boolean addedCurrentThread = false; 249 try { 250 // if reentering initialization (through a container initializer for example) no need to compute roots again 251 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=47213 252 if (!this.initializingThreads.add(currentThread)) return; 253 addedCurrentThread = true; 254 255 // all classpaths in the workspace are going to be resolved 256 // ensure that containers are initialized in one batch 257 JavaModelManager.getJavaModelManager().forceBatchInitializations(initAfterLoad); 258 259 rootInfos = getRootInfos(false/*don't use previous session values*/); 260 261 } finally { 262 if (addedCurrentThread) { 263 this.initializingThreads.remove(currentThread); 264 } 265 } 266 } 267 synchronized(this) { 268 this.oldRoots = this.roots; 269 this.oldOtherRoots = this.otherRoots; 270 if (this.rootsAreStale && rootInfos != null) { // double check again 271 this.roots = rootInfos.roots; 272 this.otherRoots = rootInfos.otherRoots; 273 this.sourceAttachments = rootInfos.sourceAttachments; 274 this.projectDependencies = rootInfos.projectDependencies; 275 this.rootsAreStale = false; 276 } 277 } 278 } 279 initializeRootsWithPreviousSession()280 synchronized void initializeRootsWithPreviousSession() { 281 RootInfos rootInfos = getRootInfos(true/*use previous session values*/); 282 if (rootInfos != null) { 283 this.roots = rootInfos.roots; 284 this.otherRoots = rootInfos.otherRoots; 285 this.sourceAttachments = rootInfos.sourceAttachments; 286 this.projectDependencies = rootInfos.projectDependencies; 287 this.rootsAreStale = false; 288 } 289 } 290 getRootInfos(boolean usePreviousSession)291 private RootInfos getRootInfos(boolean usePreviousSession) { 292 RootInfos ri = new RootInfos(); 293 294 IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel(); 295 IJavaProject[] projects; 296 try { 297 projects = model.getJavaProjects(); 298 } catch (JavaModelException e) { 299 // nothing can be done 300 return null; 301 } 302 for (int i = 0, length = projects.length; i < length; i++) { 303 JavaProject project = (JavaProject) projects[i]; 304 IClasspathEntry[] classpath; 305 try { 306 if (usePreviousSession) { 307 PerProjectInfo perProjectInfo = project.getPerProjectInfo(); 308 project.resolveClasspath(perProjectInfo, true/*use previous session values*/, false/*don't add classpath change*/); 309 classpath = perProjectInfo.resolvedClasspath; 310 } else { 311 classpath = project.getResolvedClasspath(); 312 } 313 } catch (JavaModelException e) { 314 // continue with next project 315 continue; 316 } 317 for (int j= 0, classpathLength = classpath.length; j < classpathLength; j++) { 318 IClasspathEntry entry = classpath[j]; 319 if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { 320 IJavaProject key = model.getJavaProject(entry.getPath().segment(0)); // TODO (jerome) reuse handle 321 IJavaProject[] dependents = ri.projectDependencies.get(key); 322 if (dependents == null) { 323 dependents = new IJavaProject[] {project}; 324 } else { 325 int dependentsLength = dependents.length; 326 System.arraycopy(dependents, 0, dependents = new IJavaProject[dependentsLength+1], 0, dependentsLength); 327 dependents[dependentsLength] = project; 328 } 329 ri.projectDependencies.put(key, dependents); 330 continue; 331 } 332 333 // root path 334 IPath path = entry.getPath(); 335 if (ri.roots.get(path) == null) { 336 ri.roots.put(path, new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry)); 337 } else { 338 List<RootInfo> rootList = ri.otherRoots.get(path); 339 if (rootList == null) { 340 rootList = new ArrayList<>(); 341 ri.otherRoots.put(path, rootList); 342 } 343 rootList.add(new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry)); 344 } 345 346 // source attachment path 347 if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY) continue; 348 String propertyString = null; 349 try { 350 propertyString = Util.getSourceAttachmentProperty(path); 351 } catch (JavaModelException e) { 352 e.printStackTrace(); 353 } 354 IPath sourceAttachmentPath; 355 if (propertyString != null) { 356 int index= propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER); 357 sourceAttachmentPath = (index < 0) ? new Path(propertyString) : new Path(propertyString.substring(0, index)); 358 } else { 359 sourceAttachmentPath = entry.getSourceAttachmentPath(); 360 } 361 if (sourceAttachmentPath != null) { 362 ri.sourceAttachments.put(sourceAttachmentPath, path); 363 } 364 } 365 } 366 return ri; 367 } 368 removeClasspathValidations()369 public synchronized ClasspathValidation[] removeClasspathValidations() { 370 int length = this.classpathValidations.size(); 371 if (length == 0) return null; 372 ClasspathValidation[] validations = new ClasspathValidation[length]; 373 this.classpathValidations.values().toArray(validations); 374 this.classpathValidations.clear(); 375 return validations; 376 } 377 removeExternalFolderChanges()378 public synchronized ExternalFolderChange[] removeExternalFolderChanges() { 379 int length = this.externalFolderChanges.size(); 380 if (length == 0) return null; 381 ExternalFolderChange[] updates = new ExternalFolderChange[length]; 382 this.externalFolderChanges.values().toArray(updates); 383 this.externalFolderChanges.clear(); 384 return updates; 385 } 386 removeProjectReferenceChanges()387 public synchronized Set<IJavaProject> removeProjectReferenceChanges() { 388 Set<IJavaProject> result = this.projectReferenceChanges; 389 this.projectReferenceChanges = new HashSet<>(); 390 return result; 391 } 392 removeExternalElementsToRefresh()393 public synchronized Set<IJavaElement> removeExternalElementsToRefresh() { 394 Set<IJavaElement> result = this.externalElementsToRefresh; 395 this.externalElementsToRefresh = null; 396 return result; 397 } 398 removeElementChangedListener(IElementChangedListener listener)399 public synchronized void removeElementChangedListener(IElementChangedListener listener) { 400 401 for (int i = 0; i < this.elementChangedListenerCount; i++){ 402 403 if (this.elementChangedListeners[i] == listener){ 404 405 // need to clone defensively since we might be in the middle of listener notifications (#fire) 406 int length = this.elementChangedListeners.length; 407 IElementChangedListener[] newListeners = new IElementChangedListener[length]; 408 System.arraycopy(this.elementChangedListeners, 0, newListeners, 0, i); 409 int[] newMasks = new int[length]; 410 System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, 0, i); 411 412 // copy trailing listeners 413 int trailingLength = this.elementChangedListenerCount - i - 1; 414 if (trailingLength > 0){ 415 System.arraycopy(this.elementChangedListeners, i+1, newListeners, i, trailingLength); 416 System.arraycopy(this.elementChangedListenerMasks, i+1, newMasks, i, trailingLength); 417 } 418 419 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto 420 // the original ones) 421 this.elementChangedListeners = newListeners; 422 this.elementChangedListenerMasks = newMasks; 423 this.elementChangedListenerCount--; 424 return; 425 } 426 } 427 } 428 removePreResourceChangedListener(IResourceChangeListener listener)429 public synchronized void removePreResourceChangedListener(IResourceChangeListener listener) { 430 431 for (int i = 0; i < this.preResourceChangeListenerCount; i++){ 432 433 if (this.preResourceChangeListeners[i] == listener){ 434 435 // need to clone defensively since we might be in the middle of listener notifications (#fire) 436 int length = this.preResourceChangeListeners.length; 437 IResourceChangeListener[] newListeners = new IResourceChangeListener[length]; 438 int[] newEventMasks = new int[length]; 439 System.arraycopy(this.preResourceChangeListeners, 0, newListeners, 0, i); 440 System.arraycopy(this.preResourceChangeEventMasks, 0, newEventMasks, 0, i); 441 442 // copy trailing listeners 443 int trailingLength = this.preResourceChangeListenerCount - i - 1; 444 if (trailingLength > 0) { 445 System.arraycopy(this.preResourceChangeListeners, i+1, newListeners, i, trailingLength); 446 System.arraycopy(this.preResourceChangeEventMasks, i+1, newEventMasks, i, trailingLength); 447 } 448 449 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto 450 // the original ones) 451 this.preResourceChangeListeners = newListeners; 452 this.preResourceChangeEventMasks = newEventMasks; 453 this.preResourceChangeListenerCount--; 454 return; 455 } 456 } 457 } 458 459 @Override resourceChanged(final IResourceChangeEvent event)460 public void resourceChanged(final IResourceChangeEvent event) { 461 for (int i = 0; i < this.preResourceChangeListenerCount; i++) { 462 // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief 463 final IResourceChangeListener listener = this.preResourceChangeListeners[i]; 464 if ((this.preResourceChangeEventMasks[i] & event.getType()) != 0) 465 SafeRunner.run(new ISafeRunnable() { 466 @Override 467 public void handleException(Throwable exception) { 468 Util.log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$ 469 } 470 @Override 471 public void run() throws Exception { 472 listener.resourceChanged(event); 473 } 474 }); 475 } 476 try { 477 getDeltaProcessor().resourceChanged(event); 478 } finally { 479 // TODO (jerome) see 47631, may want to get rid of following so as to reuse delta processor ? 480 if (event.getType() == IResourceChangeEvent.POST_CHANGE) { 481 this.deltaProcessors.set(null); 482 } else { 483 // If we are going to reuse the delta processor of this thread, don't hang on to state 484 // that isn't meant to be reused. https://bugs.eclipse.org/bugs/show_bug.cgi?id=273385 485 getDeltaProcessor().overridenEventType = -1; 486 } 487 } 488 489 } 490 getExternalLibTimeStamps()491 public Hashtable<IPath, Long> getExternalLibTimeStamps() { 492 if (this.externalTimeStamps == null) { 493 Hashtable<IPath, Long> timeStamps = new Hashtable<>(); 494 File timestampsFile = getTimeStampsFile(); 495 DataInputStream in = null; 496 try { 497 in = new DataInputStream(new BufferedInputStream(new FileInputStream(timestampsFile))); 498 int size = in.readInt(); 499 while (size-- > 0) { 500 String key = in.readUTF(); 501 long timestamp = in.readLong(); 502 timeStamps.put(Path.fromPortableString(key), Long.valueOf(timestamp)); 503 } 504 } catch (IOException e) { 505 if (timestampsFile.exists()) 506 Util.log(e, "Unable to read external time stamps"); //$NON-NLS-1$ 507 } finally { 508 if (in != null) { 509 try { 510 in.close(); 511 } catch (IOException e) { 512 // nothing we can do: ignore 513 } 514 } 515 } 516 this.externalTimeStamps = timeStamps; 517 } 518 return this.externalTimeStamps; 519 } 520 findJavaProject(String name)521 public IJavaProject findJavaProject(String name) { 522 if (getOldJavaProjecNames().contains(name)) 523 return JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(name); 524 return null; 525 } 526 527 /* 528 * Workaround for bug 15168 circular errors not reported 529 * Returns the list of java projects before resource delta processing 530 * has started. 531 */ getOldJavaProjecNames()532 public synchronized Set<String> getOldJavaProjecNames() { 533 if (this.javaProjectNamesCache == null) { 534 IJavaProject[] projects; 535 try { 536 projects = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProjects(); 537 } catch (JavaModelException e) { 538 return this.javaProjectNamesCache; 539 } 540 HashSet<String> result = new LinkedHashSet<>(); 541 for (int i = 0, length = projects.length; i < length; i++) { 542 IJavaProject project = projects[i]; 543 result.add(project.getElementName()); 544 } 545 return this.javaProjectNamesCache = result; 546 } 547 return this.javaProjectNamesCache; 548 } 549 resetOldJavaProjectNames()550 public synchronized void resetOldJavaProjectNames() { 551 this.javaProjectNamesCache = null; 552 } 553 getTimeStampsFile()554 private File getTimeStampsFile() { 555 return JavaCore.getPlugin().getStateLocation().append("externalLibsTimeStamps").toFile(); //$NON-NLS-1$ 556 } 557 saveExternalLibTimeStamps()558 public void saveExternalLibTimeStamps() throws CoreException { 559 if (this.externalTimeStamps == null) return; 560 561 // cleanup to avoid any leak ( https://bugs.eclipse.org/bugs/show_bug.cgi?id=244849 ) 562 HashSet<IPath> toRemove = new HashSet<>(); 563 if (this.roots != null) { 564 Enumeration<IPath> keys = this.externalTimeStamps.keys(); 565 while (keys.hasMoreElements()) { 566 IPath key = keys.nextElement(); 567 if (this.roots.get(key) == null) { 568 toRemove.add(key); 569 } 570 } 571 } 572 573 File timestamps = getTimeStampsFile(); 574 DataOutputStream out = null; 575 try { 576 out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(timestamps))); 577 out.writeInt(this.externalTimeStamps.size() - toRemove.size()); 578 Iterator<Entry<IPath, Long>> entries = this.externalTimeStamps.entrySet().iterator(); 579 while (entries.hasNext()) { 580 Entry<IPath, Long> entry = entries.next(); 581 IPath key = entry.getKey(); 582 if (!toRemove.contains(key)) { 583 out.writeUTF(key.toPortableString()); 584 Long timestamp = entry.getValue(); 585 out.writeLong(timestamp.longValue()); 586 } 587 } 588 } catch (IOException e) { 589 IStatus status = new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.ERROR, "Problems while saving timestamps", e); //$NON-NLS-1$ 590 throw new CoreException(status); 591 } finally { 592 if (out != null) { 593 try { 594 out.close(); 595 } catch (IOException e) { 596 // nothing we can do: ignore 597 } 598 } 599 } 600 } 601 602 /* 603 * Update the roots that are affected by the addition or the removal of the given container resource. 604 */ updateRoots(IPath containerPath, IResourceDelta containerDelta, DeltaProcessor deltaProcessor)605 public synchronized void updateRoots(IPath containerPath, IResourceDelta containerDelta, DeltaProcessor deltaProcessor) { 606 Map<IPath, RootInfo> updatedRoots; 607 Map<IPath, List<RootInfo>> otherUpdatedRoots; 608 if (containerDelta.getKind() == IResourceDelta.REMOVED) { 609 updatedRoots = this.oldRoots; 610 otherUpdatedRoots = this.oldOtherRoots; 611 } else { 612 updatedRoots = this.roots; 613 otherUpdatedRoots = this.otherRoots; 614 } 615 int containerSegmentCount = containerPath.segmentCount(); 616 boolean containerIsProject = containerSegmentCount == 1; 617 Iterator<Entry<IPath, RootInfo>> iterator = updatedRoots.entrySet().iterator(); 618 while (iterator.hasNext()) { 619 Entry<IPath, RootInfo> entry = iterator.next(); 620 IPath path = entry.getKey(); 621 if (containerPath.isPrefixOf(path) && !containerPath.equals(path)) { 622 IResourceDelta rootDelta = containerDelta.findMember(path.removeFirstSegments(containerSegmentCount)); 623 if (rootDelta == null) continue; 624 DeltaProcessor.RootInfo rootInfo = entry.getValue(); 625 626 if (!containerIsProject 627 || !rootInfo.project.getPath().isPrefixOf(path)) { // only consider folder roots that are not included in the container 628 deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); 629 } 630 631 List<RootInfo> rootList = otherUpdatedRoots.get(path); 632 if (rootList != null) { 633 Iterator<RootInfo> otherProjects = rootList.iterator(); 634 while (otherProjects.hasNext()) { 635 rootInfo = otherProjects.next(); 636 if (!containerIsProject 637 || !rootInfo.project.getPath().isPrefixOf(path)) { // only consider folder roots that are not included in the container 638 deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); 639 } 640 } 641 } 642 } 643 } 644 } 645 646 @Override consume(IndexerEvent event)647 public void consume(IndexerEvent event) { 648 if (JavaIndex.isEnabled()) { 649 DeltaProcessor processor = getDeltaProcessor(); 650 JavaElementDelta delta = (JavaElementDelta) event.getDelta(); 651 delta.ignoreFromTests = true; 652 processor.notifyAndFire(delta); 653 this.deltaProcessors.set(null); 654 } 655 } 656 657 private static final class RootInfos { 658 final Map<IPath, RootInfo> roots; 659 final Map<IPath, List<RootInfo>> otherRoots; 660 final Map<IPath, IPath> sourceAttachments; 661 final Map<IJavaProject, IJavaProject[]> projectDependencies; 662 RootInfos()663 public RootInfos() { 664 this.roots = new LinkedHashMap<>(); 665 this.otherRoots = new HashMap<>(); 666 this.sourceAttachments = new HashMap<>(); 667 this.projectDependencies = new HashMap<>(); 668 } 669 } 670 } 671