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