3m9 compatible;
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / DeltaProcessingState.java
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 (file)
index 0000000..23c4a00
--- /dev/null
@@ -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);
+//                                             }
+//                                     }
+//                             }
+//                     }
+//             }
+//     }
+
+}