| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.core; |
| |
| import java.util.*; |
| import java.util.ArrayList; |
| import java.util.Hashtable; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.QualifiedName; |
| import org.eclipse.jdt.core.*; |
| import org.eclipse.jdt.core.IJavaModel; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaModelException; |
| 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 preResourceChangeListenerCount = 0; |
| |
| /* |
| * The delta processor for the current thread. |
| */ |
| private ThreadLocal deltaProcessors = new ThreadLocal(); |
| |
| /* A table from IPath (from a classpath entry) to RootInfo */ |
| public HashMap roots = new HashMap(); |
| |
| /* A table from IPath (from a classpath entry) to ArrayList of RootInfo |
| * Used when an IPath corresponds to more than one root */ |
| public HashMap otherRoots = new HashMap(); |
| |
| /* A table from IPath (from a classpath entry) to RootInfo |
| * from the last time the delta processor was invoked. */ |
| public HashMap oldRoots = new HashMap(); |
| |
| /* A table from IPath (from a classpath entry) to ArrayList of RootInfo |
| * from the last time the delta processor was invoked. |
| * Used when an IPath corresponds to more than one root */ |
| public HashMap oldOtherRoots = new HashMap(); |
| |
| /* A table from IPath (a source attachment path from a classpath entry) to IPath (a root path) */ |
| public HashMap sourceAttachments = new HashMap(); |
| |
| /* Whether the roots tables should be recomputed */ |
| public boolean rootsAreStale = true; |
| |
| /* Threads that are currently running initializeRoots() */ |
| private Set initializingThreads = Collections.synchronizedSet(new HashSet()); |
| |
| public Hashtable externalTimeStamps = new Hashtable(); |
| |
| public HashMap projectUpdates = new HashMap(); |
| |
| public static class ProjectUpdateInfo { |
| JavaProject project; |
| IClasspathEntry[] oldResolvedPath; |
| IClasspathEntry[] newResolvedPath; |
| IClasspathEntry[] newRawPath; |
| |
| /** |
| * Update projects references so that the build order is consistent with the classpath |
| */ |
| public void updateProjectReferencesIfNecessary() throws JavaModelException { |
| |
| String[] oldRequired = this.project.projectPrerequisites(this.oldResolvedPath); |
| |
| if (this.newResolvedPath == null) { |
| this.newResolvedPath = this.project.getResolvedClasspath(this.newRawPath, null, true, true, null/*no reverse map*/); |
| } |
| String[] newRequired = this.project.projectPrerequisites(this.newResolvedPath); |
| try { |
| IProject projectResource = this.project.getProject(); |
| IProjectDescription description = projectResource.getDescription(); |
| |
| IProject[] projectReferences = description.getDynamicReferences(); |
| |
| HashSet oldReferences = new HashSet(projectReferences.length); |
| for (int i = 0; i < projectReferences.length; i++){ |
| String projectName = projectReferences[i].getName(); |
| oldReferences.add(projectName); |
| } |
| HashSet newReferences = (HashSet)oldReferences.clone(); |
| |
| for (int i = 0; i < oldRequired.length; i++){ |
| String projectName = oldRequired[i]; |
| newReferences.remove(projectName); |
| } |
| for (int i = 0; i < newRequired.length; i++){ |
| String projectName = newRequired[i]; |
| newReferences.add(projectName); |
| } |
| |
| Iterator iter; |
| int newSize = newReferences.size(); |
| |
| checkIdentity: { |
| if (oldReferences.size() == newSize){ |
| iter = newReferences.iterator(); |
| while (iter.hasNext()){ |
| if (!oldReferences.contains(iter.next())){ |
| break checkIdentity; |
| } |
| } |
| return; |
| } |
| } |
| String[] requiredProjectNames = new String[newSize]; |
| int index = 0; |
| iter = newReferences.iterator(); |
| while (iter.hasNext()){ |
| requiredProjectNames[index++] = (String)iter.next(); |
| } |
| Util.sort(requiredProjectNames); // ensure that if changed, the order is consistent |
| |
| IProject[] requiredProjectArray = new IProject[newSize]; |
| IWorkspaceRoot wksRoot = projectResource.getWorkspace().getRoot(); |
| for (int i = 0; i < newSize; i++){ |
| requiredProjectArray[i] = wksRoot.getProject(requiredProjectNames[i]); |
| } |
| description.setDynamicReferences(requiredProjectArray); |
| projectResource.setDescription(description, null); |
| |
| } catch(CoreException e){ |
| throw new JavaModelException(e); |
| } |
| } |
| } |
| |
| /** |
| * Workaround for bug 15168 circular errors not reported |
| * This is a cache of the projects before any project addition/deletion has started. |
| */ |
| public IJavaProject[] modelProjectsCache; |
| |
| /* |
| * 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 void addElementChangedListener(IElementChangedListener listener, int eventMask) { |
| for (int i = 0; i < this.elementChangedListenerCount; i++){ |
| if (this.elementChangedListeners[i].equals(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++; |
| } |
| |
| public void addPreResourceChangedListener(IResourceChangeListener listener) { |
| for (int i = 0; i < this.preResourceChangeListenerCount; i++){ |
| if (this.preResourceChangeListeners[i].equals(listener)) { |
| 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); |
| } |
| this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener; |
| this.preResourceChangeListenerCount++; |
| } |
| |
| public DeltaProcessor getDeltaProcessor() { |
| DeltaProcessor deltaProcessor = (DeltaProcessor)this.deltaProcessors.get(); |
| if (deltaProcessor != null) return deltaProcessor; |
| deltaProcessor = new DeltaProcessor(this, JavaModelManager.getJavaModelManager()); |
| this.deltaProcessors.set(deltaProcessor); |
| return deltaProcessor; |
| } |
| |
| public void performClasspathResourceChange(JavaProject project, IClasspathEntry[] oldResolvedPath, IClasspathEntry[] newResolvedPath, IClasspathEntry[] newRawPath, boolean canChangeResources) throws JavaModelException { |
| ProjectUpdateInfo info = new ProjectUpdateInfo(); |
| info.project = project; |
| info.oldResolvedPath = oldResolvedPath; |
| info.newResolvedPath = newResolvedPath; |
| info.newRawPath = newRawPath; |
| if (canChangeResources) { |
| this.projectUpdates.remove(project); // remove possibly awaiting one |
| info.updateProjectReferencesIfNecessary(); |
| return; |
| } |
| this.recordProjectUpdate(info); |
| } |
| |
| public void initializeRoots() { |
| |
| // recompute root infos only if necessary |
| HashMap newRoots = null; |
| HashMap newOtherRoots = null; |
| HashMap newSourceAttachments = 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().batchContainerInitializations = true; |
| |
| newRoots = new HashMap(); |
| newOtherRoots = new HashMap(); |
| newSourceAttachments = new HashMap(); |
| |
| IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel(); |
| IJavaProject[] projects; |
| try { |
| projects = model.getJavaProjects(); |
| } catch (JavaModelException e) { |
| // nothing can be done |
| return; |
| } |
| for (int i = 0, length = projects.length; i < length; i++) { |
| JavaProject project = (JavaProject) projects[i]; |
| IClasspathEntry[] classpath; |
| try { |
| classpath = project.getResolvedClasspath(true/*ignoreUnresolvedEntry*/, false/*don't generateMarkerOnError*/, false/*don't returnResolutionInProgress*/); |
| } 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) continue; |
| |
| // root path |
| IPath path = entry.getPath(); |
| if (newRoots.get(path) == null) { |
| newRoots.put(path, new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry.getEntryKind())); |
| } else { |
| ArrayList rootList = (ArrayList)newOtherRoots.get(path); |
| if (rootList == null) { |
| rootList = new ArrayList(); |
| newOtherRoots.put(path, rootList); |
| } |
| rootList.add(new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry.getEntryKind())); |
| } |
| |
| // source attachment path |
| if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY) continue; |
| QualifiedName qName = new QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " + path.toOSString()); //$NON-NLS-1$; |
| String propertyString = null; |
| try { |
| propertyString = ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName); |
| } catch (CoreException e) { |
| continue; |
| } |
| 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) { |
| newSourceAttachments.put(sourceAttachmentPath, path); |
| } |
| } |
| } |
| } finally { |
| if (addedCurrentThread) { |
| this.initializingThreads.remove(currentThread); |
| } |
| } |
| } |
| synchronized(this) { |
| this.oldRoots = this.roots; |
| this.oldOtherRoots = this.otherRoots; |
| if (this.rootsAreStale && newRoots != null) { // double check again |
| this.roots = newRoots; |
| this.otherRoots = newOtherRoots; |
| this.sourceAttachments = newSourceAttachments; |
| this.rootsAreStale = false; |
| } |
| } |
| } |
| |
| public synchronized void recordProjectUpdate(ProjectUpdateInfo newInfo) { |
| |
| JavaProject project = newInfo.project; |
| ProjectUpdateInfo oldInfo = (ProjectUpdateInfo) this.projectUpdates.get(project); |
| if (oldInfo != null) { // refresh new classpath information |
| oldInfo.newRawPath = newInfo.newRawPath; |
| oldInfo.newResolvedPath = newInfo.newResolvedPath; |
| } else { |
| this.projectUpdates.put(project, newInfo); |
| } |
| } |
| public synchronized ProjectUpdateInfo[] removeAllProjectUpdates() { |
| int length = this.projectUpdates.size(); |
| if (length == 0) return null; |
| ProjectUpdateInfo[] updates = new ProjectUpdateInfo[length]; |
| this.projectUpdates.values().toArray(updates); |
| this.projectUpdates.clear(); |
| return updates; |
| } |
| |
| public void removeElementChangedListener(IElementChangedListener listener) { |
| |
| for (int i = 0; i < this.elementChangedListenerCount; i++){ |
| |
| if (this.elementChangedListeners[i].equals(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 void removePreResourceChangedListener(IResourceChangeListener listener) { |
| |
| for (int i = 0; i < this.preResourceChangeListenerCount; i++){ |
| |
| if (this.preResourceChangeListeners[i].equals(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]; |
| System.arraycopy(this.preResourceChangeListeners, 0, newListeners, 0, i); |
| |
| // copy trailing listeners |
| int trailingLength = this.preResourceChangeListenerCount - i - 1; |
| if (trailingLength > 0){ |
| System.arraycopy(this.preResourceChangeListeners, i+1, newListeners, 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.preResourceChangeListenerCount--; |
| return; |
| } |
| } |
| } |
| |
| public void resourceChanged(final IResourceChangeEvent event) { |
| boolean isPostChange = event.getType() == IResourceChangeEvent.POST_CHANGE; |
| if (isPostChange) { |
| 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]; |
| Platform.run(new ISafeRunnable() { |
| public void handleException(Throwable exception) { |
| Util.log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$ |
| } |
| 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 (isPostChange) { |
| this.deltaProcessors.set(null); |
| } |
| } |
| |
| } |
| |
| /* |
| * 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 updatedRoots; |
| Map otherUpdatedRoots; |
| if (containerDelta.getKind() == IResourceDelta.REMOVED) { |
| updatedRoots = this.oldRoots; |
| otherUpdatedRoots = this.oldOtherRoots; |
| } else { |
| updatedRoots = this.roots; |
| otherUpdatedRoots = this.otherRoots; |
| } |
| Iterator iterator = updatedRoots.keySet().iterator(); |
| while (iterator.hasNext()) { |
| IPath path = (IPath)iterator.next(); |
| if (containerPath.isPrefixOf(path) && !containerPath.equals(path)) { |
| IResourceDelta rootDelta = containerDelta.findMember(path.removeFirstSegments(1)); |
| if (rootDelta == null) continue; |
| DeltaProcessor.RootInfo rootInfo = (DeltaProcessor.RootInfo)updatedRoots.get(path); |
| |
| if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider roots that are not included in the container |
| deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); |
| } |
| |
| ArrayList rootList = (ArrayList)otherUpdatedRoots.get(path); |
| if (rootList != null) { |
| Iterator otherProjects = rootList.iterator(); |
| while (otherProjects.hasNext()) { |
| rootInfo = (DeltaProcessor.RootInfo)otherProjects.next(); |
| if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider roots that are not included in the container |
| deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| } |