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