X-Git-Url: http://git.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/core/DeltaProcessingState.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/core/DeltaProcessingState.java new file mode 100644 index 0000000..23c4a00 --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/core/DeltaProcessingState.java @@ -0,0 +1,485 @@ +/******************************************************************************* + * 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 Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package net.sourceforge.phpdt.internal.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.phpdt.core.IClasspathEntry; +import net.sourceforge.phpdt.core.IElementChangedListener; +import net.sourceforge.phpdt.core.IJavaElement; +import net.sourceforge.phpdt.core.IJavaModel; +import net.sourceforge.phpdt.core.IJavaProject; +import net.sourceforge.phpdt.core.JavaCore; +import net.sourceforge.phpdt.core.JavaModelException; +import net.sourceforge.phpdt.internal.core.util.Util; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.QualifiedName; +import net.sourceforge.phpdt.internal.core.DeltaProcessor; +import net.sourceforge.phpdt.internal.core.JavaModelManager; + +import net.sourceforge.phpdt.core.ElementChangedEvent; +import net.sourceforge.phpdt.core.IJavaElementDelta; +import net.sourceforge.phpdt.internal.core.builder.PHPBuilder; + +/** + * 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; + + 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); +// } +// } +// } +// } +// } +// } + +}