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