3m9 compatible;
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / DeltaProcessor.java
index bbfe240..ae314b3 100644 (file)
@@ -12,17 +12,21 @@ package net.sourceforge.phpdt.internal.core;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 
 import net.sourceforge.phpdt.core.ElementChangedEvent;
 import net.sourceforge.phpdt.core.IClasspathEntry;
+import net.sourceforge.phpdt.core.IElementChangedListener;
 import net.sourceforge.phpdt.core.IJavaElement;
+import net.sourceforge.phpdt.core.IJavaElementDelta;
 import net.sourceforge.phpdt.core.IJavaModel;
 import net.sourceforge.phpdt.core.IJavaProject;
-import net.sourceforge.phpdt.core.JavaModelException;
 import net.sourceforge.phpdt.core.JavaCore;
+import net.sourceforge.phpdt.core.JavaModelException;
 import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil;
 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
 
@@ -37,7 +41,15 @@ import org.eclipse.core.resources.IWorkspace;
 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.Platform;
 import org.eclipse.core.runtime.QualifiedName;
+import net.sourceforge.phpdt.internal.core.builder.PHPBuilder;
+
+import net.sourceforge.phpdt.internal.core.DeltaProcessingState;
+import net.sourceforge.phpdt.internal.core.JavaElementDelta;
+import net.sourceforge.phpdt.internal.core.JavaModelManager;
+import net.sourceforge.phpdt.internal.core.util.Util;
 
 
 /**
@@ -58,8 +70,11 @@ public class DeltaProcessor implements IResourceChangeListener {
        final static String EXTERNAL_JAR_UNCHANGED = "external jar unchanged"; //$NON-NLS-1$
        final static String INTERNAL_JAR_IGNORE = "internal jar ignore"; //$NON-NLS-1$
        
-       final static int NON_JAVA_RESOURCE = -1;
+       private final static int NON_JAVA_RESOURCE = -1;
+       public static boolean DEBUG = false;
+       public static boolean VERBOSE = false;
        
+       public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with ElementChangedEvent event masks
        /**
         * The <code>JavaElementDelta</code> corresponding to the <code>IResourceDelta</code> being translated.
         */
@@ -93,14 +108,38 @@ public class DeltaProcessor implements IResourceChangeListener {
         * This is used as a stack of java elements (using getParent() to pop it, and 
         * using the various get*(...) to push it. */
        Openable currentElement;
-               
+       /*
+        * Queue of deltas created explicily by the Java Model that
+        * have yet to be fired.
+        */
+       public ArrayList javaModelDeltas= new ArrayList();
+       /*
+        * Queue of reconcile deltas on working copies that have yet to be fired.
+        * This is a table form IWorkingCopy to IJavaElementDelta
+        */
+       public HashMap reconcileDeltas = new HashMap();
+
+       /*
+        * Turns delta firing on/off. By default it is on.
+        */
+       private boolean isFiring= true;
+       
+       
        public HashMap externalTimeStamps = new HashMap();
        public HashSet projectsToUpdate = new HashSet();
        // list of root projects which namelookup caches need to be updated for dependents
        // TODO: (jerome) is it needed? projectsToUpdate might be sufficient
        public HashSet projectsForDependentNamelookupRefresh = new HashSet();  
        
-       JavaModelManager manager;
+       /*
+        * The global state of delta processing.
+        */
+       private DeltaProcessingState state;
+       
+       /*
+        * The Java model manager
+        */
+       private JavaModelManager manager;
        
        /* A table from IJavaProject to an array of IPackageFragmentRoot.
         * This table contains the pkg fragment roots of the project that are being deleted.
@@ -112,7 +151,6 @@ public class DeltaProcessor implements IResourceChangeListener {
         * This is null if no refresh is needed.
         */
        HashSet refreshedElements;
-       public static boolean VERBOSE = false;
        
        class OutputsInfo {
                IPath[] paths;
@@ -190,10 +228,19 @@ public class DeltaProcessor implements IResourceChangeListener {
                }
        }
 
-       DeltaProcessor(JavaModelManager manager) {
+//     DeltaProcessor(JavaModelManager manager) {
+//             this.manager = manager;
+//     }
+
+       /*
+        * Type of event that should be processed no matter what the real event type is.
+        */
+       public int overridenEventType = -1;
+       
+       public DeltaProcessor(DeltaProcessingState state, JavaModelManager manager) {
+               this.state = state;
                this.manager = manager;
        }
-
        /*
         * Adds the dependents of the given project to the list of the projects
         * to update.
@@ -1203,7 +1250,73 @@ public class DeltaProcessor implements IResourceChangeListener {
                }
                return false;
        }
-
+       /*
+        * Merges all awaiting deltas.
+        */
+       private IJavaElementDelta mergeDeltas(Collection deltas) {
+               if (deltas.size() == 0) return null;
+               if (deltas.size() == 1) return (IJavaElementDelta)deltas.iterator().next();
+               
+               if (VERBOSE) {
+                       System.out.println("MERGING " + deltas.size() + " DELTAS ["+Thread.currentThread()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+               }
+               
+               Iterator iterator = deltas.iterator();
+               JavaElementDelta rootDelta = new JavaElementDelta(this.manager.javaModel);
+               boolean insertedTree = false;
+               while (iterator.hasNext()) {
+                       JavaElementDelta delta = (JavaElementDelta)iterator.next();
+                       if (VERBOSE) {
+                               System.out.println(delta.toString());
+                       }
+                       IJavaElement element = delta.getElement();
+                       if (this.manager.javaModel.equals(element)) {
+                               IJavaElementDelta[] children = delta.getAffectedChildren();
+                               for (int j = 0; j < children.length; j++) {
+                                       JavaElementDelta projectDelta = (JavaElementDelta) children[j];
+                                       rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta);
+                                       insertedTree = true;
+                               }
+                               IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
+                               if (resourceDeltas != null) {
+                                       for (int i = 0, length = resourceDeltas.length; i < length; i++) {
+                                               rootDelta.addResourceDelta(resourceDeltas[i]);
+                                               insertedTree = true;
+                                       }
+                               }
+                       } else {
+                               rootDelta.insertDeltaTree(element, delta);
+                               insertedTree = true;
+                       }
+               }
+               if (insertedTree) return rootDelta;
+               return null;
+       }       
+       private void notifyListeners(IJavaElementDelta deltaToNotify, int eventType, IElementChangedListener[] listeners, int[] listenerMask, int listenerCount) {
+               final ElementChangedEvent extraEvent = new ElementChangedEvent(deltaToNotify, eventType);
+               for (int i= 0; i < listenerCount; i++) {
+                       if ((listenerMask[i] & eventType) != 0){
+                               final IElementChangedListener listener = listeners[i];
+                               long start = -1;
+                               if (VERBOSE) {
+                                       System.out.print("Listener #" + (i+1) + "=" + listener.toString());//$NON-NLS-1$//$NON-NLS-2$
+                                       start = System.currentTimeMillis();
+                               }
+                               // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
+                               Platform.run(new ISafeRunnable() {
+                                       public void handleException(Throwable exception) {
+                                               Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
+                                       }
+                                       public void run() throws Exception {
+                                               listener.elementChanged(extraEvent);
+                                       }
+                               });
+                               if (VERBOSE) {
+                                       System.out.println(" -> " + (System.currentTimeMillis()-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+                               }
+                       }
+               }
+       }
        /**
         * Generic processing for elements with changed contents:<ul>
         * <li>The element is closed such that any subsequent accesses will re-open
@@ -1632,7 +1745,12 @@ public class DeltaProcessor implements IResourceChangeListener {
 //                             //fCurrentDelta.changed(project, IJavaElementDelta.F_OPTIONS_CHANGED);                          
 //             }
 //     }
-
+       /*
+        * Registers the given delta with this delta processor.
+        */
+       public void registerJavaModelDelta(IJavaElementDelta delta) {
+               this.javaModelDeltas.add(delta);
+       }
        /**
         * Removes the given element from its parents cache of children. If the
         * element does not have a parent, or the parent is not currently open,
@@ -1650,6 +1768,7 @@ public class DeltaProcessor implements IResourceChangeListener {
                        }
                }
        }
+       
        /**
         * Notification that some resource changes have happened
         * on the platform, and that the Java Model should update any required
@@ -1660,17 +1779,18 @@ public class DeltaProcessor implements IResourceChangeListener {
         * @see IResource 
         */
        public void resourceChanged(IResourceChangeEvent event) {
-       
+               // jsurfer TODO compare 3.0 sources
                if (event.getSource() instanceof IWorkspace) {
+                       int eventType = this.overridenEventType == -1 ? event.getType() : this.overridenEventType;
                        IResource resource = event.getResource();
                        IResourceDelta delta = event.getDelta();
                        
-                       switch(event.getType()){
+                       switch(eventType){
                                case IResourceChangeEvent.PRE_DELETE :
                                        try {
                                                if(resource.getType() == IResource.PROJECT 
                                                        && ((IProject) resource).hasNature(PHPeclipsePlugin.PHP_NATURE_ID)) {
-                                               // TODO khartlage temp-del
+                                               // TODO jsurfer temp-del
 //                                                     this.deleting((IProject)resource);
                                                }
                                        } catch(CoreException e){
@@ -1678,7 +1798,7 @@ public class DeltaProcessor implements IResourceChangeListener {
                                        return;
                                        
                                case IResourceChangeEvent.PRE_AUTO_BUILD :
-//                     TODO khartlage temp-del
+//                     TODO jsurfer temp-del
 //                                     if(isAffectedBy(delta)) { // avoid populating for SYNC or MARKER deltas
 //                                             this.checkProjectsBeingAddedOrRemoved(delta);
 //                                             
@@ -1699,12 +1819,12 @@ public class DeltaProcessor implements IResourceChangeListener {
                                        break;
 
                                case IResourceChangeEvent.POST_AUTO_BUILD :
-//                     TODO khartlage temp-del
+//                     TODO jsurfer temp-del
 //                                     JavaBuilder.finishedBuilding(event);
                                        break;
                                        
                                case IResourceChangeEvent.POST_CHANGE :
-//                     TODO khartlage temp-del
+//                     TODO jsurfer temp-del
 //                                     if (isAffectedBy(delta)) {
 //                                             try {
 //                                                     if (this.refreshedElements != null) {
@@ -1729,6 +1849,12 @@ public class DeltaProcessor implements IResourceChangeListener {
                }
        }
        /*
+        * Flushes all deltas without firing them.
+        */
+       public void flush() {
+               this.javaModelDeltas = new ArrayList();
+       }
+       /*
         * Finds the root info this path is included in.
         * Returns null if not found.
         */
@@ -1741,6 +1867,89 @@ public class DeltaProcessor implements IResourceChangeListener {
                return null;
        }
        /*
+        * Fire Java Model delta, flushing them after the fact after post_change notification.
+        * If the firing mode has been turned off, this has no effect. 
+        */
+       public void fire(IJavaElementDelta customDelta, int eventType) {
+               if (!this.isFiring) return;
+               
+               if (DEBUG) {
+                       System.out.println("-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$
+               }
+
+               IJavaElementDelta deltaToNotify;
+               if (customDelta == null){
+                       deltaToNotify = this.mergeDeltas(this.javaModelDeltas);
+               } else {
+                       deltaToNotify = customDelta;
+               }
+                       
+               // Refresh internal scopes
+//             if (deltaToNotify != null) {
+//                     Iterator scopes = this.manager.searchScopes.keySet().iterator();
+//                     while (scopes.hasNext()) {
+//                             AbstractSearchScope scope = (AbstractSearchScope)scopes.next();
+//                             scope.processDelta(deltaToNotify);
+//                     }
+//             }
+                       
+               // Notification
+       
+               // Important: if any listener reacts to notification by updating the listeners list or mask, these lists will
+               // be duplicated, so it is necessary to remember original lists in a variable (since field values may change under us)
+               IElementChangedListener[] listeners = this.state.elementChangedListeners;
+               int[] listenerMask = this.state.elementChangedListenerMasks;
+               int listenerCount = this.state.elementChangedListenerCount;
+
+               switch (eventType) {
+                       case DEFAULT_CHANGE_EVENT:
+                               firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
+                               fireReconcileDelta(listeners, listenerMask, listenerCount);
+                               break;
+                       case ElementChangedEvent.POST_CHANGE:
+                               firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
+                               fireReconcileDelta(listeners, listenerMask, listenerCount);
+                               break;
+               }
+       }
+       
+       private void firePostChangeDelta(
+                       IJavaElementDelta deltaToNotify,
+                       IElementChangedListener[] listeners,
+                       int[] listenerMask,
+                       int listenerCount) {
+                               
+                       // post change deltas
+                       if (DEBUG){
+                               System.out.println("FIRING POST_CHANGE Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
+                               System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
+                       }
+                       if (deltaToNotify != null) {
+                               // flush now so as to keep listener reactions to post their own deltas for subsequent iteration
+                               this.flush();
+                               
+                               notifyListeners(deltaToNotify, ElementChangedEvent.POST_CHANGE, listeners, listenerMask, listenerCount);
+                       } 
+               }               
+               private void fireReconcileDelta(
+                       IElementChangedListener[] listeners,
+                       int[] listenerMask,
+                       int listenerCount) {
+
+
+                       IJavaElementDelta deltaToNotify = mergeDeltas(this.reconcileDeltas.values());
+                       if (DEBUG){
+                               System.out.println("FIRING POST_RECONCILE Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
+                               System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
+                       }
+                       if (deltaToNotify != null) {
+                               // flush now so as to keep listener reactions to post their own deltas for subsequent iteration
+                               this.reconcileDeltas = new HashMap();
+                       
+                               notifyListeners(deltaToNotify, ElementChangedEvent.POST_RECONCILE, listeners, listenerMask, listenerCount);
+                       } 
+               }
+       /*
         * Returns the root info for the given path. Look in the old roots table if kind is REMOVED.
         */
        RootInfo rootInfo(IPath path, int kind) {