23c4a00903c3cc5bf60925a7160d02742a4af6f1
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / DeltaProcessingState.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
12
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.Iterator;
19 import java.util.Map;
20 import java.util.Set;
21
22 import net.sourceforge.phpdt.core.IClasspathEntry;
23 import net.sourceforge.phpdt.core.IElementChangedListener;
24 import net.sourceforge.phpdt.core.IJavaElement;
25 import net.sourceforge.phpdt.core.IJavaModel;
26 import net.sourceforge.phpdt.core.IJavaProject;
27 import net.sourceforge.phpdt.core.JavaCore;
28 import net.sourceforge.phpdt.core.JavaModelException;
29 import net.sourceforge.phpdt.internal.core.util.Util;
30
31 import org.eclipse.core.resources.IProject;
32 import org.eclipse.core.resources.IProjectDescription;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.resources.IResourceChangeEvent;
35 import org.eclipse.core.resources.IResourceChangeListener;
36 import org.eclipse.core.resources.IResourceDelta;
37 import org.eclipse.core.resources.IWorkspace;
38 import org.eclipse.core.resources.IWorkspaceRoot;
39 import org.eclipse.core.resources.ResourcesPlugin;
40 import org.eclipse.core.runtime.CoreException;
41 import org.eclipse.core.runtime.IPath;
42 import org.eclipse.core.runtime.ISafeRunnable;
43 import org.eclipse.core.runtime.Path;
44 import org.eclipse.core.runtime.Platform;
45 import org.eclipse.core.runtime.QualifiedName;
46 import net.sourceforge.phpdt.internal.core.DeltaProcessor;
47 import net.sourceforge.phpdt.internal.core.JavaModelManager;
48
49 import net.sourceforge.phpdt.core.ElementChangedEvent;
50 import net.sourceforge.phpdt.core.IJavaElementDelta;
51 import net.sourceforge.phpdt.internal.core.builder.PHPBuilder;
52
53 /**
54  * Keep the global states used during Java element delta processing.
55  */
56 public class DeltaProcessingState implements IResourceChangeListener {
57         
58         /*
59          * Collection of listeners for Java element deltas
60          */
61         public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5];
62         public int[] elementChangedListenerMasks = new int[5];
63         public int elementChangedListenerCount = 0;
64         
65         /*
66          * Collection of pre Java resource change listeners
67          */
68         public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1];
69         public int preResourceChangeListenerCount = 0;
70
71         /*
72          * The delta processor for the current thread.
73          */
74         private ThreadLocal deltaProcessors = new ThreadLocal();
75         
76         /* A table from IPath (from a classpath entry) to RootInfo */
77         public HashMap roots = new HashMap();
78         
79         /* A table from IPath (from a classpath entry) to ArrayList of RootInfo
80          * Used when an IPath corresponds to more than one root */
81         public HashMap otherRoots = new HashMap();
82         
83         /* A table from IPath (from a classpath entry) to RootInfo
84          * from the last time the delta processor was invoked. */
85         public HashMap oldRoots = new HashMap();
86         
87         /* A table from IPath (from a classpath entry) to ArrayList of RootInfo
88          * from the last time the delta processor was invoked.
89          * Used when an IPath corresponds to more than one root */
90         public HashMap oldOtherRoots = new HashMap();
91         
92         /* A table from IPath (a source attachment path from a classpath entry) to IPath (a root path) */
93         public HashMap sourceAttachments = new HashMap();
94
95         /* Whether the roots tables should be recomputed */
96         public boolean rootsAreStale = true;
97         
98         /* Threads that are currently running initializeRoots() */
99         private Set initializingThreads = Collections.synchronizedSet(new HashSet());   
100         
101         public Hashtable externalTimeStamps = new Hashtable();
102         
103         public HashMap projectUpdates = new HashMap();
104
105         public static class ProjectUpdateInfo {
106                 JavaProject project;
107                 IClasspathEntry[] oldResolvedPath;
108                 IClasspathEntry[] newResolvedPath;
109                 IClasspathEntry[] newRawPath;
110                 
111                 /**
112                  * Update projects references so that the build order is consistent with the classpath
113                  */
114                 public void updateProjectReferencesIfNecessary() throws JavaModelException {
115                         
116                         String[] oldRequired = this.project.projectPrerequisites(this.oldResolvedPath);
117         
118                         if (this.newResolvedPath == null) {
119                                 this.newResolvedPath = this.project.getResolvedClasspath(this.newRawPath, null, true, true, null/*no reverse map*/);
120                         }
121                         String[] newRequired = this.project.projectPrerequisites(this.newResolvedPath);
122                         try {
123                                 IProject projectResource = this.project.getProject();
124                                 IProjectDescription description = projectResource.getDescription();
125                                  
126                                 IProject[] projectReferences = description.getDynamicReferences();
127                                 
128                                 HashSet oldReferences = new HashSet(projectReferences.length);
129                                 for (int i = 0; i < projectReferences.length; i++){
130                                         String projectName = projectReferences[i].getName();
131                                         oldReferences.add(projectName);
132                                 }
133                                 HashSet newReferences = (HashSet)oldReferences.clone();
134                 
135                                 for (int i = 0; i < oldRequired.length; i++){
136                                         String projectName = oldRequired[i];
137                                         newReferences.remove(projectName);
138                                 }
139                                 for (int i = 0; i < newRequired.length; i++){
140                                         String projectName = newRequired[i];
141                                         newReferences.add(projectName);
142                                 }
143                 
144                                 Iterator iter;
145                                 int newSize = newReferences.size();
146                                 
147                                 checkIdentity: {
148                                         if (oldReferences.size() == newSize){
149                                                 iter = newReferences.iterator();
150                                                 while (iter.hasNext()){
151                                                         if (!oldReferences.contains(iter.next())){
152                                                                 break checkIdentity;
153                                                         }
154                                                 }
155                                                 return;
156                                         }
157                                 }
158                                 String[] requiredProjectNames = new String[newSize];
159                                 int index = 0;
160                                 iter = newReferences.iterator();
161                                 while (iter.hasNext()){
162                                         requiredProjectNames[index++] = (String)iter.next();
163                                 }
164                                 Util.sort(requiredProjectNames); // ensure that if changed, the order is consistent
165                                 
166                                 IProject[] requiredProjectArray = new IProject[newSize];
167                                 IWorkspaceRoot wksRoot = projectResource.getWorkspace().getRoot();
168                                 for (int i = 0; i < newSize; i++){
169                                         requiredProjectArray[i] = wksRoot.getProject(requiredProjectNames[i]);
170                                 }
171                                 description.setDynamicReferences(requiredProjectArray);
172                                 projectResource.setDescription(description, null);
173                 
174                         } catch(CoreException e){
175                                 throw new JavaModelException(e);
176                         }
177                 }
178         }
179         
180         
181         /**
182          * Workaround for bug 15168 circular errors not reported  
183          * This is a cache of the projects before any project addition/deletion has started.
184          */
185         public IJavaProject[] modelProjectsCache;
186         
187         /*
188          * Need to clone defensively the listener information, in case some listener is reacting to some notification iteration by adding/changing/removing
189          * any of the other (for example, if it deregisters itself).
190          */
191         public void addElementChangedListener(IElementChangedListener listener, int eventMask) {
192                 for (int i = 0; i < this.elementChangedListenerCount; i++){
193                         if (this.elementChangedListeners[i].equals(listener)){
194                                 
195                                 // only clone the masks, since we could be in the middle of notifications and one listener decide to change
196                                 // any event mask of another listeners (yet not notified).
197                                 int cloneLength = this.elementChangedListenerMasks.length;
198                                 System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[cloneLength], 0, cloneLength);
199                                 this.elementChangedListenerMasks[i] = eventMask; // could be different
200                                 return;
201                         }
202                 }
203                 // 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.
204                 int length;
205                 if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount){
206                         System.arraycopy(this.elementChangedListeners, 0, this.elementChangedListeners = new IElementChangedListener[length*2], 0, length);
207                         System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[length*2], 0, length);
208                 }
209                 this.elementChangedListeners[this.elementChangedListenerCount] = listener;
210                 this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask;
211                 this.elementChangedListenerCount++;
212         }
213
214         public void addPreResourceChangedListener(IResourceChangeListener listener) {
215                 for (int i = 0; i < this.preResourceChangeListenerCount; i++){
216                         if (this.preResourceChangeListeners[i].equals(listener)) {
217                                 return;
218                         }
219                 }
220                 // 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.
221                 int length;
222                 if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount){
223                         System.arraycopy(this.preResourceChangeListeners, 0, this.preResourceChangeListeners = new IResourceChangeListener[length*2], 0, length);
224                 }
225                 this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener;
226                 this.preResourceChangeListenerCount++;
227         }
228
229         public DeltaProcessor getDeltaProcessor() {
230                 DeltaProcessor deltaProcessor = (DeltaProcessor)this.deltaProcessors.get();
231                 if (deltaProcessor != null) return deltaProcessor;
232                 deltaProcessor = new DeltaProcessor(this, JavaModelManager.getJavaModelManager());
233                 this.deltaProcessors.set(deltaProcessor);
234                 return deltaProcessor;
235         }
236
237         public void performClasspathResourceChange(JavaProject project, IClasspathEntry[] oldResolvedPath, IClasspathEntry[] newResolvedPath, IClasspathEntry[] newRawPath, boolean canChangeResources) throws JavaModelException {
238             ProjectUpdateInfo info = new ProjectUpdateInfo();
239             info.project = project;
240             info.oldResolvedPath = oldResolvedPath;
241             info.newResolvedPath = newResolvedPath;
242             info.newRawPath = newRawPath;
243             if (canChangeResources) {
244             this.projectUpdates.remove(project); // remove possibly awaiting one
245                 info.updateProjectReferencesIfNecessary();
246                 return;
247             }
248             this.recordProjectUpdate(info);
249         }
250         
251         public void initializeRoots() {
252                 
253                 // recompute root infos only if necessary
254                 HashMap newRoots = null;
255                 HashMap newOtherRoots = null;
256                 HashMap newSourceAttachments = null;
257                 if (this.rootsAreStale) {
258                         Thread currentThread = Thread.currentThread();
259                         boolean addedCurrentThread = false;                     
260                         try {
261                                 // if reentering initialization (through a container initializer for example) no need to compute roots again
262                                 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=47213
263                                 if (!this.initializingThreads.add(currentThread)) return;
264                                 addedCurrentThread = true;
265
266                                 newRoots = new HashMap();
267                                 newOtherRoots = new HashMap();
268                                 newSourceAttachments = new HashMap();
269                 
270                                 IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel();
271                                 IJavaProject[] projects;
272                                 try {
273                                         projects = model.getJavaProjects();
274                                 } catch (JavaModelException e) {
275                                         // nothing can be done
276                                         return;
277                                 }
278                                 for (int i = 0, length = projects.length; i < length; i++) {
279                                         JavaProject project = (JavaProject) projects[i];
280 //                                      IClasspathEntry[] classpath;
281 //                                      try {
282 //                                              classpath = project.getResolvedClasspath(true/*ignoreUnresolvedEntry*/, false/*don't generateMarkerOnError*/, false/*don't returnResolutionInProgress*/);
283 //                                      } catch (JavaModelException e) {
284 //                                              // continue with next project
285 //                                              continue;
286 //                                      }
287 //                                      for (int j= 0, classpathLength = classpath.length; j < classpathLength; j++) {
288 //                                              IClasspathEntry entry = classpath[j];
289 //                                              if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) continue;
290 //                                              
291 //                                              // root path
292 //                                              IPath path = entry.getPath();
293 //                                              if (newRoots.get(path) == null) {
294 //                                                      newRoots.put(path, new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry.getEntryKind()));
295 //                                              } else {
296 //                                                      ArrayList rootList = (ArrayList)newOtherRoots.get(path);
297 //                                                      if (rootList == null) {
298 //                                                              rootList = new ArrayList();
299 //                                                              newOtherRoots.put(path, rootList);
300 //                                                      }
301 //                                                      rootList.add(new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry.getEntryKind()));
302 //                                              }
303 //                                              
304 //                                              // source attachment path
305 //                                              if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY) continue;
306 //                                              QualifiedName qName = new QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " + path.toOSString()); //$NON-NLS-1$;
307 //                                              String propertyString = null;
308 //                                              try {
309 //                                                      propertyString = ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName);
310 //                                              } catch (CoreException e) {
311 //                                                      continue;
312 //                                              }
313 //                                              IPath sourceAttachmentPath;
314 //                                              if (propertyString != null) {
315 //                                                      int index= propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER);
316 //                                                      sourceAttachmentPath = (index < 0) ?  new Path(propertyString) : new Path(propertyString.substring(0, index));
317 //                                              } else {
318 //                                                      sourceAttachmentPath = entry.getSourceAttachmentPath();
319 //                                              }
320 //                                              if (sourceAttachmentPath != null) {
321 //                                                      newSourceAttachments.put(sourceAttachmentPath, path);
322 //                                              }
323 //                                      }
324                                 }
325                         } finally {
326                                 if (addedCurrentThread) {
327                                         this.initializingThreads.remove(currentThread);
328                                 }
329                         }
330                 }
331                 synchronized(this) {
332                         this.oldRoots = this.roots;
333                         this.oldOtherRoots = this.otherRoots;                   
334                         if (this.rootsAreStale && newRoots != null) { // double check again
335                                 this.roots = newRoots;
336                                 this.otherRoots = newOtherRoots;
337                                 this.sourceAttachments = newSourceAttachments;
338                                 this.rootsAreStale = false;
339                         }
340                 }
341         }
342
343         public synchronized void recordProjectUpdate(ProjectUpdateInfo newInfo) {
344             
345             JavaProject project = newInfo.project;
346             ProjectUpdateInfo oldInfo = (ProjectUpdateInfo) this.projectUpdates.get(project);
347             if (oldInfo != null) { // refresh new classpath information
348                 oldInfo.newRawPath = newInfo.newRawPath;
349                 oldInfo.newResolvedPath = newInfo.newResolvedPath;
350             } else {
351                 this.projectUpdates.put(project, newInfo);
352             }
353         }
354         public synchronized ProjectUpdateInfo[] removeAllProjectUpdates() {
355             int length = this.projectUpdates.size();
356             if (length == 0) return null;
357             ProjectUpdateInfo[]  updates = new ProjectUpdateInfo[length];
358             this.projectUpdates.values().toArray(updates);
359             this.projectUpdates.clear();
360             return updates;
361         }
362         
363         public void removeElementChangedListener(IElementChangedListener listener) {
364                 
365                 for (int i = 0; i < this.elementChangedListenerCount; i++){
366                         
367                         if (this.elementChangedListeners[i].equals(listener)){
368                                 
369                                 // need to clone defensively since we might be in the middle of listener notifications (#fire)
370                                 int length = this.elementChangedListeners.length;
371                                 IElementChangedListener[] newListeners = new IElementChangedListener[length];
372                                 System.arraycopy(this.elementChangedListeners, 0, newListeners, 0, i);
373                                 int[] newMasks = new int[length];
374                                 System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, 0, i);
375                                 
376                                 // copy trailing listeners
377                                 int trailingLength = this.elementChangedListenerCount - i - 1;
378                                 if (trailingLength > 0){
379                                         System.arraycopy(this.elementChangedListeners, i+1, newListeners, i, trailingLength);
380                                         System.arraycopy(this.elementChangedListenerMasks, i+1, newMasks, i, trailingLength);
381                                 }
382                                 
383                                 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto
384                                 // the original ones)
385                                 this.elementChangedListeners = newListeners;
386                                 this.elementChangedListenerMasks = newMasks;
387                                 this.elementChangedListenerCount--;
388                                 return;
389                         }
390                 }
391         }
392
393         public void removePreResourceChangedListener(IResourceChangeListener listener) {
394                 
395                 for (int i = 0; i < this.preResourceChangeListenerCount; i++){
396                         
397                         if (this.preResourceChangeListeners[i].equals(listener)){
398                                 
399                                 // need to clone defensively since we might be in the middle of listener notifications (#fire)
400                                 int length = this.preResourceChangeListeners.length;
401                                 IResourceChangeListener[] newListeners = new IResourceChangeListener[length];
402                                 System.arraycopy(this.preResourceChangeListeners, 0, newListeners, 0, i);
403                                 
404                                 // copy trailing listeners
405                                 int trailingLength = this.preResourceChangeListenerCount - i - 1;
406                                 if (trailingLength > 0){
407                                         System.arraycopy(this.preResourceChangeListeners, i+1, newListeners, i, trailingLength);
408                                 }
409                                 
410                                 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto
411                                 // the original ones)
412                                 this.preResourceChangeListeners = newListeners;
413                                 this.preResourceChangeListenerCount--;
414                                 return;
415                         }
416                 }
417         }
418
419         public void resourceChanged(final IResourceChangeEvent event) {
420                 boolean isPostChange = event.getType() == IResourceChangeEvent.POST_CHANGE;
421                 if (isPostChange) {
422                         for (int i = 0; i < this.preResourceChangeListenerCount; i++) {
423                                 // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
424                                 final IResourceChangeListener listener = this.preResourceChangeListeners[i];
425                                 Platform.run(new ISafeRunnable() {
426                                         public void handleException(Throwable exception) {
427                                                 Util.log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$
428                                         }
429                                         public void run() throws Exception {
430                                                 listener.resourceChanged(event);
431                                         }
432                                 });
433                         }
434                 }
435                 try {
436                         getDeltaProcessor().resourceChanged(event);
437                 } finally {
438                         // TODO (jerome) see 47631, may want to get rid of following so as to reuse delta processor ? 
439                         if (isPostChange) {
440                                 this.deltaProcessors.set(null);
441                         }
442                 }
443
444         }
445         
446         /*
447          * Update the roots that are affected by the addition or the removal of the given container resource.
448          */
449 //      public synchronized void updateRoots(IPath containerPath, IResourceDelta containerDelta, DeltaProcessor deltaProcessor) {
450 //              Map updatedRoots;
451 //              Map otherUpdatedRoots;
452 //              if (containerDelta.getKind() == IResourceDelta.REMOVED) {
453 //                      updatedRoots = this.oldRoots;
454 //                      otherUpdatedRoots = this.oldOtherRoots;
455 //              } else {
456 //                      updatedRoots = this.roots;
457 //                      otherUpdatedRoots = this.otherRoots;
458 //              }
459 //              Iterator iterator = updatedRoots.keySet().iterator();
460 //              while (iterator.hasNext()) {
461 //                      IPath path = (IPath)iterator.next();
462 //                      if (containerPath.isPrefixOf(path) && !containerPath.equals(path)) {
463 //                              IResourceDelta rootDelta = containerDelta.findMember(path.removeFirstSegments(1));
464 //                              if (rootDelta == null) continue;
465 //                              DeltaProcessor.RootInfo rootInfo = (DeltaProcessor.RootInfo)updatedRoots.get(path);
466 //      
467 //                              if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider roots that are not included in the container
468 //                                      deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo);
469 //                              }
470 //                              
471 //                              ArrayList rootList = (ArrayList)otherUpdatedRoots.get(path);
472 //                              if (rootList != null) {
473 //                                      Iterator otherProjects = rootList.iterator();
474 //                                      while (otherProjects.hasNext()) {
475 //                                              rootInfo = (DeltaProcessor.RootInfo)otherProjects.next();
476 //                                              if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider roots that are not included in the container
477 //                                                      deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo);
478 //                                              }
479 //                                      }
480 //                              }
481 //                      }
482 //              }
483 //      }
484
485 }