| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.core; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jdt.core.IClasspathEntry; |
| import org.eclipse.jdt.core.IElementChangedListener; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaModel; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.internal.core.DeltaProcessor.RootInfo; |
| import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo; |
| import org.eclipse.jdt.internal.core.util.Util; |
| |
| /** |
| * Keep the global states used during Java element delta processing. |
| */ |
| public class DeltaProcessingState implements IResourceChangeListener { |
| |
| /* |
| * Collection of listeners for Java element deltas |
| */ |
| public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5]; |
| public int[] elementChangedListenerMasks = new int[5]; |
| public int elementChangedListenerCount = 0; |
| |
| /* |
| * Collection of pre Java resource change listeners |
| */ |
| public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1]; |
| public int[] preResourceChangeEventMasks = new int[1]; |
| public int preResourceChangeListenerCount = 0; |
| |
| /* |
| * The delta processor for the current thread. |
| */ |
| private ThreadLocal<DeltaProcessor> deltaProcessors = new ThreadLocal<>(); |
| |
| public void doNotUse() { |
| // reset the delta processor of the current thread to avoid to keep it in memory |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=269476 |
| this.deltaProcessors.remove(); |
| } |
| |
| /* A table from IPath (from a classpath entry) to DeltaProcessor.RootInfo */ |
| public Map<IPath, RootInfo> roots = new LinkedHashMap<>(); |
| |
| /* A table from IPath (from a classpath entry) to ArrayList of DeltaProcessor.RootInfo |
| * Used when an IPath corresponds to more than one root */ |
| public Map<IPath, List<RootInfo>> otherRoots = new HashMap<>(); |
| |
| /* A table from IPath (from a classpath entry) to DeltaProcessor.RootInfo |
| * from the last time the delta processor was invoked. */ |
| public Map<IPath, RootInfo> oldRoots = new LinkedHashMap<>(); |
| |
| /* A table from IPath (from a classpath entry) to ArrayList of DeltaProcessor.RootInfo |
| * from the last time the delta processor was invoked. |
| * Used when an IPath corresponds to more than one root */ |
| public Map<IPath, List<RootInfo>> oldOtherRoots = new HashMap<>(); |
| |
| /* A table from IPath (a source attachment path from a classpath entry) to IPath (a root path) */ |
| public Map<IPath, IPath> sourceAttachments = new HashMap<>(); |
| |
| /* A table from IJavaProject to IJavaProject[] (the list of direct dependent of the key) */ |
| public Map<IJavaProject, IJavaProject[]> projectDependencies = new HashMap<>(); |
| |
| /* Whether the roots tables should be recomputed */ |
| public boolean rootsAreStale = true; |
| |
| /* Threads that are currently running initializeRoots() */ |
| private Set<Thread> initializingThreads = Collections.synchronizedSet(new HashSet<>()); |
| |
| /* A table from file system absoulte path (String) to timestamp (Long) */ |
| public Hashtable<IPath, Long> externalTimeStamps; |
| |
| /* |
| * Map from IProject to ClasspathChange |
| * Note these changes need to be kept on the delta processing state to ensure we don't loose them |
| * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=271102 Java model corrupt after switching target platform) |
| */ |
| private Map<IProject, ClasspathChange> classpathChanges = new LinkedHashMap<>(); |
| |
| /* A table from JavaProject to ClasspathValidation */ |
| private Map<JavaProject, ClasspathValidation> classpathValidations = new LinkedHashMap<>(); |
| |
| /* A table from JavaProject to ProjectReferenceChange */ |
| private Set<IJavaProject> projectReferenceChanges = new LinkedHashSet<>(); |
| |
| /* A table from JavaProject to ExternalFolderChange */ |
| private Map<JavaProject, ExternalFolderChange> externalFolderChanges = new LinkedHashMap<>(); |
| |
| /** |
| * Workaround for bug 15168 circular errors not reported |
| * This is a cache of the projects before any project addition/deletion has started. |
| */ |
| private Set<String> javaProjectNamesCache; |
| |
| /* |
| * A list of IJavaElement used as a scope for external archives refresh during POST_CHANGE. |
| * This is null if no refresh is needed. |
| */ |
| private Set<IJavaElement> externalElementsToRefresh; |
| private final Object mutex = new Object(); |
| |
| /* |
| * Need to clone defensively the listener information, in case some listener is reacting to some notification iteration by adding/changing/removing |
| * any of the other (for example, if it deregisters itself). |
| */ |
| public synchronized void addElementChangedListener(IElementChangedListener listener, int eventMask) { |
| for (int i = 0; i < this.elementChangedListenerCount; i++){ |
| if (this.elementChangedListeners[i] == listener){ |
| |
| // only clone the masks, since we could be in the middle of notifications and one listener decide to change |
| // any event mask of another listeners (yet not notified). |
| int cloneLength = this.elementChangedListenerMasks.length; |
| System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[cloneLength], 0, cloneLength); |
| this.elementChangedListenerMasks[i] |= eventMask; // could be different |
| return; |
| } |
| } |
| // 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. |
| int length; |
| if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount){ |
| System.arraycopy(this.elementChangedListeners, 0, this.elementChangedListeners = new IElementChangedListener[length*2], 0, length); |
| System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[length*2], 0, length); |
| } |
| this.elementChangedListeners[this.elementChangedListenerCount] = listener; |
| this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask; |
| this.elementChangedListenerCount++; |
| } |
| |
| /* |
| * Adds the given element to the list of elements used as a scope for external jars refresh. |
| */ |
| public synchronized void addForRefresh(IJavaElement externalElement) { |
| if (this.externalElementsToRefresh == null) { |
| this.externalElementsToRefresh = new LinkedHashSet<>(); |
| } |
| this.externalElementsToRefresh.add(externalElement); |
| } |
| |
| public synchronized void addPreResourceChangedListener(IResourceChangeListener listener, int eventMask) { |
| for (int i = 0; i < this.preResourceChangeListenerCount; i++){ |
| if (this.preResourceChangeListeners[i] == listener) { |
| this.preResourceChangeEventMasks[i] |= eventMask; |
| return; |
| } |
| } |
| // 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. |
| int length; |
| if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount) { |
| System.arraycopy(this.preResourceChangeListeners, 0, this.preResourceChangeListeners = new IResourceChangeListener[length*2], 0, length); |
| System.arraycopy(this.preResourceChangeEventMasks, 0, this.preResourceChangeEventMasks = new int[length*2], 0, length); |
| } |
| this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener; |
| this.preResourceChangeEventMasks[this.preResourceChangeListenerCount] = eventMask; |
| this.preResourceChangeListenerCount++; |
| } |
| |
| public DeltaProcessor getDeltaProcessor() { |
| DeltaProcessor deltaProcessor = this.deltaProcessors.get(); |
| if (deltaProcessor != null) return deltaProcessor; |
| deltaProcessor = new DeltaProcessor(this, JavaModelManager.getJavaModelManager()); |
| this.deltaProcessors.set(deltaProcessor); |
| return deltaProcessor; |
| } |
| |
| public ClasspathChange addClasspathChange(IProject project, IClasspathEntry[] oldRawClasspath, IPath oldOutputLocation, IClasspathEntry[] oldResolvedClasspath) { |
| synchronized (this.mutex) { |
| ClasspathChange change = this.classpathChanges.get(project); |
| if (change == null) { |
| change = new ClasspathChange((JavaProject) JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(project), oldRawClasspath, oldOutputLocation, oldResolvedClasspath); |
| this.classpathChanges.put(project, change); |
| } else { |
| if (change.oldRawClasspath == null) |
| change.oldRawClasspath = oldRawClasspath; |
| if (change.oldOutputLocation == null) |
| change.oldOutputLocation = oldOutputLocation; |
| if (change.oldResolvedClasspath == null) |
| change.oldResolvedClasspath = oldResolvedClasspath; |
| } |
| return change; |
| } |
| } |
| |
| public ClasspathChange getClasspathChange(IProject project) { |
| synchronized (this.mutex) { |
| return this.classpathChanges.get(project); |
| } |
| } |
| |
| public Map<IProject, ClasspathChange> removeAllClasspathChanges() { |
| synchronized (this.mutex) { |
| Map<IProject, ClasspathChange> result = this.classpathChanges; |
| this.classpathChanges = new LinkedHashMap<>(result.size()); |
| return result; |
| } |
| } |
| |
| public synchronized ClasspathValidation addClasspathValidation(JavaProject project) { |
| ClasspathValidation validation = this.classpathValidations.get(project); |
| if (validation == null) { |
| validation = new ClasspathValidation(project); |
| this.classpathValidations.put(project, validation); |
| } |
| return validation; |
| } |
| |
| public synchronized void addExternalFolderChange(JavaProject project, IClasspathEntry[] oldResolvedClasspath) { |
| ExternalFolderChange change = this.externalFolderChanges.get(project); |
| if (change == null) { |
| change = new ExternalFolderChange(project, oldResolvedClasspath); |
| this.externalFolderChanges.put(project, change); |
| } |
| } |
| |
| public synchronized void addProjectReferenceChange(IJavaProject project) { |
| this.projectReferenceChanges.add(project); |
| } |
| |
| public void initializeRoots(boolean initAfterLoad) { |
| |
| // recompute root infos only if necessary |
| RootInfos rootInfos = null; |
| if (this.rootsAreStale) { |
| Thread currentThread = Thread.currentThread(); |
| boolean addedCurrentThread = false; |
| try { |
| // if reentering initialization (through a container initializer for example) no need to compute roots again |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=47213 |
| if (!this.initializingThreads.add(currentThread)) return; |
| addedCurrentThread = true; |
| |
| // all classpaths in the workspace are going to be resolved |
| // ensure that containers are initialized in one batch |
| JavaModelManager.getJavaModelManager().forceBatchInitializations(initAfterLoad); |
| |
| rootInfos = getRootInfos(false/*don't use previous session values*/); |
| |
| } finally { |
| if (addedCurrentThread) { |
| this.initializingThreads.remove(currentThread); |
| } |
| } |
| } |
| synchronized(this) { |
| this.oldRoots = this.roots; |
| this.oldOtherRoots = this.otherRoots; |
| if (this.rootsAreStale && rootInfos != null) { // double check again |
| this.roots = rootInfos.roots; |
| this.otherRoots = rootInfos.otherRoots; |
| this.sourceAttachments = rootInfos.sourceAttachments; |
| this.projectDependencies = rootInfos.projectDependencies; |
| this.rootsAreStale = false; |
| } |
| } |
| } |
| |
| synchronized void initializeRootsWithPreviousSession() { |
| RootInfos rootInfos = getRootInfos(true/*use previous session values*/); |
| if (rootInfos != null) { |
| this.roots = rootInfos.roots; |
| this.otherRoots = rootInfos.otherRoots; |
| this.sourceAttachments = rootInfos.sourceAttachments; |
| this.projectDependencies = rootInfos.projectDependencies; |
| this.rootsAreStale = false; |
| } |
| } |
| |
| private RootInfos getRootInfos(boolean usePreviousSession) { |
| RootInfos ri = new RootInfos(); |
| |
| IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel(); |
| IJavaProject[] projects; |
| try { |
| projects = model.getJavaProjects(); |
| } catch (JavaModelException e) { |
| // nothing can be done |
| return null; |
| } |
| for (int i = 0, length = projects.length; i < length; i++) { |
| JavaProject project = (JavaProject) projects[i]; |
| IClasspathEntry[] classpath; |
| try { |
| if (usePreviousSession) { |
| PerProjectInfo perProjectInfo = project.getPerProjectInfo(); |
| project.resolveClasspath(perProjectInfo, true/*use previous session values*/, false/*don't add classpath change*/); |
| classpath = perProjectInfo.resolvedClasspath; |
| } else { |
| classpath = project.getResolvedClasspath(); |
| } |
| } catch (JavaModelException e) { |
| // continue with next project |
| continue; |
| } |
| for (int j= 0, classpathLength = classpath.length; j < classpathLength; j++) { |
| IClasspathEntry entry = classpath[j]; |
| if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { |
| IJavaProject key = model.getJavaProject(entry.getPath().segment(0)); // TODO (jerome) reuse handle |
| IJavaProject[] dependents = ri.projectDependencies.get(key); |
| if (dependents == null) { |
| dependents = new IJavaProject[] {project}; |
| } else { |
| int dependentsLength = dependents.length; |
| System.arraycopy(dependents, 0, dependents = new IJavaProject[dependentsLength+1], 0, dependentsLength); |
| dependents[dependentsLength] = project; |
| } |
| ri.projectDependencies.put(key, dependents); |
| continue; |
| } |
| |
| // root path |
| IPath path = entry.getPath(); |
| if (ri.roots.get(path) == null) { |
| ri.roots.put(path, new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry)); |
| } else { |
| List<RootInfo> rootList = ri.otherRoots.get(path); |
| if (rootList == null) { |
| rootList = new ArrayList<>(); |
| ri.otherRoots.put(path, rootList); |
| } |
| rootList.add(new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry)); |
| } |
| |
| // source attachment path |
| if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY) continue; |
| String propertyString = null; |
| try { |
| propertyString = Util.getSourceAttachmentProperty(path); |
| } catch (JavaModelException e) { |
| e.printStackTrace(); |
| } |
| IPath sourceAttachmentPath; |
| if (propertyString != null) { |
| int index= propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER); |
| sourceAttachmentPath = (index < 0) ? new Path(propertyString) : new Path(propertyString.substring(0, index)); |
| } else { |
| sourceAttachmentPath = entry.getSourceAttachmentPath(); |
| } |
| if (sourceAttachmentPath != null) { |
| ri.sourceAttachments.put(sourceAttachmentPath, path); |
| } |
| } |
| } |
| return ri; |
| } |
| |
| public synchronized ClasspathValidation[] removeClasspathValidations() { |
| int length = this.classpathValidations.size(); |
| if (length == 0) return null; |
| ClasspathValidation[] validations = new ClasspathValidation[length]; |
| this.classpathValidations.values().toArray(validations); |
| this.classpathValidations.clear(); |
| return validations; |
| } |
| |
| public synchronized ExternalFolderChange[] removeExternalFolderChanges() { |
| int length = this.externalFolderChanges.size(); |
| if (length == 0) return null; |
| ExternalFolderChange[] updates = new ExternalFolderChange[length]; |
| this.externalFolderChanges.values().toArray(updates); |
| this.externalFolderChanges.clear(); |
| return updates; |
| } |
| |
| public synchronized Set<IJavaProject> removeProjectReferenceChanges() { |
| Set<IJavaProject> result = this.projectReferenceChanges; |
| this.projectReferenceChanges = new HashSet<>(); |
| return result; |
| } |
| |
| public synchronized Set<IJavaElement> removeExternalElementsToRefresh() { |
| Set<IJavaElement> result = this.externalElementsToRefresh; |
| this.externalElementsToRefresh = null; |
| return result; |
| } |
| |
| public synchronized void removeElementChangedListener(IElementChangedListener listener) { |
| |
| for (int i = 0; i < this.elementChangedListenerCount; i++){ |
| |
| if (this.elementChangedListeners[i] == listener){ |
| |
| // need to clone defensively since we might be in the middle of listener notifications (#fire) |
| int length = this.elementChangedListeners.length; |
| IElementChangedListener[] newListeners = new IElementChangedListener[length]; |
| System.arraycopy(this.elementChangedListeners, 0, newListeners, 0, i); |
| int[] newMasks = new int[length]; |
| System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, 0, i); |
| |
| // copy trailing listeners |
| int trailingLength = this.elementChangedListenerCount - i - 1; |
| if (trailingLength > 0){ |
| System.arraycopy(this.elementChangedListeners, i+1, newListeners, i, trailingLength); |
| System.arraycopy(this.elementChangedListenerMasks, i+1, newMasks, i, trailingLength); |
| } |
| |
| // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto |
| // the original ones) |
| this.elementChangedListeners = newListeners; |
| this.elementChangedListenerMasks = newMasks; |
| this.elementChangedListenerCount--; |
| return; |
| } |
| } |
| } |
| |
| public synchronized void removePreResourceChangedListener(IResourceChangeListener listener) { |
| |
| for (int i = 0; i < this.preResourceChangeListenerCount; i++){ |
| |
| if (this.preResourceChangeListeners[i] == listener){ |
| |
| // need to clone defensively since we might be in the middle of listener notifications (#fire) |
| int length = this.preResourceChangeListeners.length; |
| IResourceChangeListener[] newListeners = new IResourceChangeListener[length]; |
| int[] newEventMasks = new int[length]; |
| System.arraycopy(this.preResourceChangeListeners, 0, newListeners, 0, i); |
| System.arraycopy(this.preResourceChangeEventMasks, 0, newEventMasks, 0, i); |
| |
| // copy trailing listeners |
| int trailingLength = this.preResourceChangeListenerCount - i - 1; |
| if (trailingLength > 0) { |
| System.arraycopy(this.preResourceChangeListeners, i+1, newListeners, i, trailingLength); |
| System.arraycopy(this.preResourceChangeEventMasks, i+1, newEventMasks, i, trailingLength); |
| } |
| |
| // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto |
| // the original ones) |
| this.preResourceChangeListeners = newListeners; |
| this.preResourceChangeEventMasks = newEventMasks; |
| this.preResourceChangeListenerCount--; |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public void resourceChanged(final IResourceChangeEvent event) { |
| for (int i = 0; i < this.preResourceChangeListenerCount; i++) { |
| // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief |
| final IResourceChangeListener listener = this.preResourceChangeListeners[i]; |
| if ((this.preResourceChangeEventMasks[i] & event.getType()) != 0) |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void handleException(Throwable exception) { |
| Util.log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$ |
| } |
| @Override |
| public void run() throws Exception { |
| listener.resourceChanged(event); |
| } |
| }); |
| } |
| try { |
| getDeltaProcessor().resourceChanged(event); |
| } finally { |
| // TODO (jerome) see 47631, may want to get rid of following so as to reuse delta processor ? |
| if (event.getType() == IResourceChangeEvent.POST_CHANGE) { |
| this.deltaProcessors.set(null); |
| } else { |
| // If we are going to reuse the delta processor of this thread, don't hang on to state |
| // that isn't meant to be reused. https://bugs.eclipse.org/bugs/show_bug.cgi?id=273385 |
| getDeltaProcessor().overridenEventType = -1; |
| } |
| } |
| |
| } |
| |
| public Hashtable<IPath, Long> getExternalLibTimeStamps() { |
| if (this.externalTimeStamps == null) { |
| Hashtable<IPath, Long> timeStamps = new Hashtable<>(); |
| File timestampsFile = getTimeStampsFile(); |
| DataInputStream in = null; |
| try { |
| in = new DataInputStream(new BufferedInputStream(new FileInputStream(timestampsFile))); |
| int size = in.readInt(); |
| while (size-- > 0) { |
| String key = in.readUTF(); |
| long timestamp = in.readLong(); |
| timeStamps.put(Path.fromPortableString(key), Long.valueOf(timestamp)); |
| } |
| } catch (IOException e) { |
| if (timestampsFile.exists()) |
| Util.log(e, "Unable to read external time stamps"); //$NON-NLS-1$ |
| } finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| // nothing we can do: ignore |
| } |
| } |
| } |
| this.externalTimeStamps = timeStamps; |
| } |
| return this.externalTimeStamps; |
| } |
| |
| public IJavaProject findJavaProject(String name) { |
| if (getOldJavaProjecNames().contains(name)) |
| return JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(name); |
| return null; |
| } |
| |
| /* |
| * Workaround for bug 15168 circular errors not reported |
| * Returns the list of java projects before resource delta processing |
| * has started. |
| */ |
| public synchronized Set<String> getOldJavaProjecNames() { |
| if (this.javaProjectNamesCache == null) { |
| IJavaProject[] projects; |
| try { |
| projects = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProjects(); |
| } catch (JavaModelException e) { |
| return this.javaProjectNamesCache; |
| } |
| HashSet<String> result = new LinkedHashSet<>(); |
| for (int i = 0, length = projects.length; i < length; i++) { |
| IJavaProject project = projects[i]; |
| result.add(project.getElementName()); |
| } |
| return this.javaProjectNamesCache = result; |
| } |
| return this.javaProjectNamesCache; |
| } |
| |
| public synchronized void resetOldJavaProjectNames() { |
| this.javaProjectNamesCache = null; |
| } |
| |
| private File getTimeStampsFile() { |
| return JavaCore.getPlugin().getStateLocation().append("externalLibsTimeStamps").toFile(); //$NON-NLS-1$ |
| } |
| |
| public void saveExternalLibTimeStamps() throws CoreException { |
| if (this.externalTimeStamps == null) return; |
| |
| // cleanup to avoid any leak ( https://bugs.eclipse.org/bugs/show_bug.cgi?id=244849 ) |
| HashSet<IPath> toRemove = new HashSet<>(); |
| if (this.roots != null) { |
| Enumeration<IPath> keys = this.externalTimeStamps.keys(); |
| while (keys.hasMoreElements()) { |
| IPath key = keys.nextElement(); |
| if (this.roots.get(key) == null) { |
| toRemove.add(key); |
| } |
| } |
| } |
| |
| File timestamps = getTimeStampsFile(); |
| try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(timestamps)))){ |
| out.writeInt(this.externalTimeStamps.size() - toRemove.size()); |
| Iterator<Entry<IPath, Long>> entries = this.externalTimeStamps.entrySet().iterator(); |
| while (entries.hasNext()) { |
| Entry<IPath, Long> entry = entries.next(); |
| IPath key = entry.getKey(); |
| if (!toRemove.contains(key)) { |
| out.writeUTF(key.toPortableString()); |
| Long timestamp = entry.getValue(); |
| out.writeLong(timestamp.longValue()); |
| } |
| } |
| } catch (IOException e) { |
| IStatus status = new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.ERROR, "Problems while saving timestamps", e); //$NON-NLS-1$ |
| throw new CoreException(status); |
| } |
| } |
| |
| /* |
| * Update the roots that are affected by the addition or the removal of the given container resource. |
| */ |
| public synchronized void updateRoots(IPath containerPath, IResourceDelta containerDelta, DeltaProcessor deltaProcessor) { |
| Map<IPath, RootInfo> updatedRoots; |
| Map<IPath, List<RootInfo>> otherUpdatedRoots; |
| if (containerDelta.getKind() == IResourceDelta.REMOVED) { |
| updatedRoots = this.oldRoots; |
| otherUpdatedRoots = this.oldOtherRoots; |
| } else { |
| updatedRoots = this.roots; |
| otherUpdatedRoots = this.otherRoots; |
| } |
| int containerSegmentCount = containerPath.segmentCount(); |
| boolean containerIsProject = containerSegmentCount == 1; |
| Iterator<Entry<IPath, RootInfo>> iterator = updatedRoots.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Entry<IPath, RootInfo> entry = iterator.next(); |
| IPath path = entry.getKey(); |
| if (containerPath.isPrefixOf(path) && !containerPath.equals(path)) { |
| IResourceDelta rootDelta = containerDelta.findMember(path.removeFirstSegments(containerSegmentCount)); |
| if (rootDelta == null) continue; |
| DeltaProcessor.RootInfo rootInfo = entry.getValue(); |
| |
| if (!containerIsProject |
| || !rootInfo.project.getPath().isPrefixOf(path)) { // only consider folder roots that are not included in the container |
| deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); |
| } |
| |
| List<RootInfo> rootList = otherUpdatedRoots.get(path); |
| if (rootList != null) { |
| Iterator<RootInfo> otherProjects = rootList.iterator(); |
| while (otherProjects.hasNext()) { |
| rootInfo = otherProjects.next(); |
| if (!containerIsProject |
| || !rootInfo.project.getPath().isPrefixOf(path)) { // only consider folder roots that are not included in the container |
| deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static final class RootInfos { |
| final Map<IPath, RootInfo> roots; |
| final Map<IPath, List<RootInfo>> otherRoots; |
| final Map<IPath, IPath> sourceAttachments; |
| final Map<IJavaProject, IJavaProject[]> projectDependencies; |
| |
| public RootInfos() { |
| this.roots = new LinkedHashMap<>(); |
| this.otherRoots = new HashMap<>(); |
| this.sourceAttachments = new HashMap<>(); |
| this.projectDependencies = new HashMap<>(); |
| } |
| } |
| } |