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