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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
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;
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;
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;
38 * Keep the global states used during Java element delta processing.
40 public class DeltaProcessingState implements IResourceChangeListener {
43 * Collection of listeners for Java element deltas
45 public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5];
47 public int[] elementChangedListenerMasks = new int[5];
49 public int elementChangedListenerCount = 0;
52 * Collection of pre Java resource change listeners
54 public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1];
56 public int preResourceChangeListenerCount = 0;
59 * The delta processor for the current thread.
61 private ThreadLocal deltaProcessors = new ThreadLocal();
63 /* A table from IPath (from a classpath entry) to RootInfo */
64 public HashMap roots = new HashMap();
67 * A table from IPath (from a classpath entry) to ArrayList of RootInfo Used
68 * when an IPath corresponds to more than one root
70 public HashMap otherRoots = new HashMap();
73 * A table from IPath (from a classpath entry) to RootInfo from the last
74 * time the delta processor was invoked.
76 public HashMap oldRoots = new HashMap();
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
83 public HashMap oldOtherRoots = new HashMap();
86 * A table from IPath (a source attachment path from a classpath entry) to
89 public HashMap sourceAttachments = new HashMap();
91 /* Whether the roots tables should be recomputed */
92 public boolean rootsAreStale = true;
94 /* Threads that are currently running initializeRoots() */
95 private Set initializingThreads = Collections
96 .synchronizedSet(new HashSet());
98 public Hashtable externalTimeStamps = new Hashtable();
100 public HashMap projectUpdates = new HashMap();
102 public static class ProjectUpdateInfo {
105 IClasspathEntry[] oldResolvedPath;
107 IClasspathEntry[] newResolvedPath;
109 IClasspathEntry[] newRawPath;
112 * Update projects references so that the build order is consistent with
115 public void updateProjectReferencesIfNecessary()
116 throws JavaModelException {
118 String[] oldRequired = this.project
119 .projectPrerequisites(this.oldResolvedPath);
121 if (this.newResolvedPath == null) {
122 this.newResolvedPath = this.project
123 .getResolvedClasspath(this.newRawPath, null, true,
124 true, null/* no reverse map */);
126 String[] newRequired = this.project
127 .projectPrerequisites(this.newResolvedPath);
129 IProject projectResource = this.project.getProject();
130 IProjectDescription description = projectResource
133 IProject[] projectReferences = description
134 .getDynamicReferences();
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);
141 HashSet newReferences = (HashSet) oldReferences.clone();
143 for (int i = 0; i < oldRequired.length; i++) {
144 String projectName = oldRequired[i];
145 newReferences.remove(projectName);
147 for (int i = 0; i < newRequired.length; i++) {
148 String projectName = newRequired[i];
149 newReferences.add(projectName);
153 int newSize = newReferences.size();
156 if (oldReferences.size() == newSize) {
157 iter = newReferences.iterator();
158 while (iter.hasNext()) {
159 if (!oldReferences.contains(iter.next())) {
166 String[] requiredProjectNames = new String[newSize];
168 iter = newReferences.iterator();
169 while (iter.hasNext()) {
170 requiredProjectNames[index++] = (String) iter.next();
172 Util.sort(requiredProjectNames); // ensure that if changed,
173 // the order is consistent
175 IProject[] requiredProjectArray = new IProject[newSize];
176 IWorkspaceRoot wksRoot = projectResource.getWorkspace()
178 for (int i = 0; i < newSize; i++) {
179 requiredProjectArray[i] = wksRoot
180 .getProject(requiredProjectNames[i]);
182 description.setDynamicReferences(requiredProjectArray);
183 projectResource.setDescription(description, null);
185 } catch (CoreException e) {
186 throw new JavaModelException(e);
192 * Workaround for bug 15168 circular errors not reported This is a cache of
193 * the projects before any project addition/deletion has started.
195 public IJavaProject[] modelProjectsCache;
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).
202 public void addElementChangedListener(IElementChangedListener listener,
204 for (int i = 0; i < this.elementChangedListenerCount; i++) {
205 if (this.elementChangedListeners[i].equals(listener)) {
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;
213 this.elementChangedListenerMasks,
215 this.elementChangedListenerMasks = new int[cloneLength],
217 this.elementChangedListenerMasks[i] = eventMask; // could be
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.
225 if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount) {
228 this.elementChangedListeners,
230 this.elementChangedListeners = new IElementChangedListener[length * 2],
232 System.arraycopy(this.elementChangedListenerMasks, 0,
233 this.elementChangedListenerMasks = new int[length * 2], 0,
236 this.elementChangedListeners[this.elementChangedListenerCount] = listener;
237 this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask;
238 this.elementChangedListenerCount++;
241 public void addPreResourceChangedListener(IResourceChangeListener listener) {
242 for (int i = 0; i < this.preResourceChangeListenerCount; i++) {
243 if (this.preResourceChangeListeners[i].equals(listener)) {
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.
250 if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount) {
253 this.preResourceChangeListeners,
255 this.preResourceChangeListeners = new IResourceChangeListener[length * 2],
258 this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener;
259 this.preResourceChangeListenerCount++;
262 public DeltaProcessor getDeltaProcessor() {
263 DeltaProcessor deltaProcessor = (DeltaProcessor) this.deltaProcessors
265 if (deltaProcessor != null)
266 return deltaProcessor;
267 deltaProcessor = new DeltaProcessor(this, JavaModelManager
268 .getJavaModelManager());
269 this.deltaProcessors.set(deltaProcessor);
270 return deltaProcessor;
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
285 info.updateProjectReferencesIfNecessary();
288 this.recordProjectUpdate(info);
291 public void initializeRoots() {
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;
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))
306 addedCurrentThread = true;
308 newRoots = new HashMap();
309 newOtherRoots = new HashMap();
310 newSourceAttachments = new HashMap();
312 IJavaModel model = JavaModelManager.getJavaModelManager()
314 IJavaProject[] projects;
316 projects = model.getJavaProjects();
317 } catch (JavaModelException e) {
318 // nothing can be done
321 for (int i = 0, length = projects.length; i < length; i++) {
322 JavaProject project = (JavaProject) projects[i];
323 // IClasspathEntry[] 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
333 // for (int j= 0, classpathLength = classpath.length; j <
334 // classpathLength; j++) {
335 // IClasspathEntry entry = classpath[j];
336 // if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT)
340 // IPath path = entry.getPath();
341 // if (newRoots.get(path) == null) {
342 // newRoots.put(path, new DeltaProcessor.RootInfo(project,
344 // ((ClasspathEntry)entry).fullInclusionPatternChars(),
345 // ((ClasspathEntry)entry).fullExclusionPatternChars(),
346 // entry.getEntryKind()));
348 // ArrayList rootList = (ArrayList)newOtherRoots.get(path);
349 // if (rootList == null) {
350 // rootList = new ArrayList();
351 // newOtherRoots.put(path, rootList);
353 // rootList.add(new DeltaProcessor.RootInfo(project, path,
354 // ((ClasspathEntry)entry).fullInclusionPatternChars(),
355 // ((ClasspathEntry)entry).fullExclusionPatternChars(),
356 // entry.getEntryKind()));
359 // // source attachment path
360 // if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY)
362 // QualifiedName qName = new
363 // QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " +
364 // path.toOSString()); //$NON-NLS-1$;
365 // String propertyString = null;
368 // ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName);
369 // } catch (CoreException e) {
372 // IPath sourceAttachmentPath;
373 // if (propertyString != null) {
375 // propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER);
376 // sourceAttachmentPath = (index < 0) ? new
377 // Path(propertyString) : new
378 // Path(propertyString.substring(0, index));
380 // sourceAttachmentPath = entry.getSourceAttachmentPath();
382 // if (sourceAttachmentPath != null) {
383 // newSourceAttachments.put(sourceAttachmentPath, path);
388 if (addedCurrentThread) {
389 this.initializingThreads.remove(currentThread);
393 synchronized (this) {
394 this.oldRoots = this.roots;
395 this.oldOtherRoots = this.otherRoots;
396 if (this.rootsAreStale && newRoots != null) { // double check
398 this.roots = newRoots;
399 this.otherRoots = newOtherRoots;
400 this.sourceAttachments = newSourceAttachments;
401 this.rootsAreStale = false;
406 public synchronized void recordProjectUpdate(ProjectUpdateInfo newInfo) {
408 JavaProject project = newInfo.project;
409 ProjectUpdateInfo oldInfo = (ProjectUpdateInfo) this.projectUpdates
411 if (oldInfo != null) { // refresh new classpath information
412 oldInfo.newRawPath = newInfo.newRawPath;
413 oldInfo.newResolvedPath = newInfo.newResolvedPath;
415 this.projectUpdates.put(project, newInfo);
419 public synchronized ProjectUpdateInfo[] removeAllProjectUpdates() {
420 int length = this.projectUpdates.size();
423 ProjectUpdateInfo[] updates = new ProjectUpdateInfo[length];
424 this.projectUpdates.values().toArray(updates);
425 this.projectUpdates.clear();
429 public void removeElementChangedListener(IElementChangedListener listener) {
431 for (int i = 0; i < this.elementChangedListenerCount; i++) {
433 if (this.elementChangedListeners[i].equals(listener)) {
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,
441 int[] newMasks = new int[length];
442 System.arraycopy(this.elementChangedListenerMasks, 0, newMasks,
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);
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--;
465 public void removePreResourceChangedListener(
466 IResourceChangeListener listener) {
468 for (int i = 0; i < this.preResourceChangeListenerCount; i++) {
470 if (this.preResourceChangeListeners[i].equals(listener)) {
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,
479 // copy trailing listeners
480 int trailingLength = this.preResourceChangeListenerCount - i
482 if (trailingLength > 0) {
483 System.arraycopy(this.preResourceChangeListeners, i + 1,
484 newListeners, i, trailingLength);
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--;
497 public void resourceChanged(final IResourceChangeEvent event) {
498 boolean isPostChange = event.getType() == IResourceChangeEvent.POST_CHANGE;
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) {
508 "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$
511 public void run() throws Exception {
512 listener.resourceChanged(event);
518 getDeltaProcessor().resourceChanged(event);
520 // TODO (jerome) see 47631, may want to get rid of following so as
521 // to reuse delta processor ?
523 this.deltaProcessors.set(null);
530 * Update the roots that are affected by the addition or the removal of the
531 * given container resource.
533 // public synchronized void updateRoots(IPath containerPath, IResourceDelta
534 // containerDelta, DeltaProcessor deltaProcessor) {
536 // Map otherUpdatedRoots;
537 // if (containerDelta.getKind() == IResourceDelta.REMOVED) {
538 // updatedRoots = this.oldRoots;
539 // otherUpdatedRoots = this.oldOtherRoots;
541 // updatedRoots = this.roots;
542 // otherUpdatedRoots = this.otherRoots;
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);
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);
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);