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;
37 * Keep the global states used during Java element delta processing.
39 public class DeltaProcessingState implements IResourceChangeListener {
42 * Collection of listeners for Java element deltas
44 public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5];
46 public int[] elementChangedListenerMasks = new int[5];
48 public int elementChangedListenerCount = 0;
51 * Collection of pre Java resource change listeners
53 public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1];
55 public int preResourceChangeListenerCount = 0;
58 * The delta processor for the current thread.
60 private ThreadLocal deltaProcessors = new ThreadLocal();
62 /* A table from IPath (from a classpath entry) to RootInfo */
63 public HashMap roots = new HashMap();
66 * A table from IPath (from a classpath entry) to ArrayList of RootInfo Used
67 * when an IPath corresponds to more than one root
69 public HashMap otherRoots = new HashMap();
72 * A table from IPath (from a classpath entry) to RootInfo from the last
73 * time the delta processor was invoked.
75 public HashMap oldRoots = new HashMap();
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
82 public HashMap oldOtherRoots = new HashMap();
85 * A table from IPath (a source attachment path from a classpath entry) to
88 public HashMap sourceAttachments = new HashMap();
90 /* Whether the roots tables should be recomputed */
91 public boolean rootsAreStale = true;
93 /* Threads that are currently running initializeRoots() */
94 private Set initializingThreads = Collections
95 .synchronizedSet(new HashSet());
97 public Hashtable externalTimeStamps = new Hashtable();
99 public HashMap projectUpdates = new HashMap();
101 public static class ProjectUpdateInfo {
104 IClasspathEntry[] oldResolvedPath;
106 IClasspathEntry[] newResolvedPath;
108 IClasspathEntry[] newRawPath;
111 * Update projects references so that the build order is consistent with
114 public void updateProjectReferencesIfNecessary()
115 throws JavaModelException {
117 String[] oldRequired = this.project
118 .projectPrerequisites(this.oldResolvedPath);
120 if (this.newResolvedPath == null) {
121 this.newResolvedPath = this.project
122 .getResolvedClasspath(this.newRawPath, null, true,
123 true, null/* no reverse map */);
125 String[] newRequired = this.project
126 .projectPrerequisites(this.newResolvedPath);
128 IProject projectResource = this.project.getProject();
129 IProjectDescription description = projectResource
132 IProject[] projectReferences = description
133 .getDynamicReferences();
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);
140 HashSet newReferences = (HashSet) oldReferences.clone();
142 for (int i = 0; i < oldRequired.length; i++) {
143 String projectName = oldRequired[i];
144 newReferences.remove(projectName);
146 for (int i = 0; i < newRequired.length; i++) {
147 String projectName = newRequired[i];
148 newReferences.add(projectName);
152 int newSize = newReferences.size();
155 if (oldReferences.size() == newSize) {
156 iter = newReferences.iterator();
157 while (iter.hasNext()) {
158 if (!oldReferences.contains(iter.next())) {
165 String[] requiredProjectNames = new String[newSize];
167 iter = newReferences.iterator();
168 while (iter.hasNext()) {
169 requiredProjectNames[index++] = (String) iter.next();
171 Util.sort(requiredProjectNames); // ensure that if changed,
172 // the order is consistent
174 IProject[] requiredProjectArray = new IProject[newSize];
175 IWorkspaceRoot wksRoot = projectResource.getWorkspace()
177 for (int i = 0; i < newSize; i++) {
178 requiredProjectArray[i] = wksRoot
179 .getProject(requiredProjectNames[i]);
181 description.setDynamicReferences(requiredProjectArray);
182 projectResource.setDescription(description, null);
184 } catch (CoreException e) {
185 throw new JavaModelException(e);
191 * Workaround for bug 15168 circular errors not reported This is a cache of
192 * the projects before any project addition/deletion has started.
194 public IJavaProject[] modelProjectsCache;
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).
201 public void addElementChangedListener(IElementChangedListener listener,
203 for (int i = 0; i < this.elementChangedListenerCount; i++) {
204 if (this.elementChangedListeners[i].equals(listener)) {
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;
212 this.elementChangedListenerMasks,
214 this.elementChangedListenerMasks = new int[cloneLength],
216 this.elementChangedListenerMasks[i] = eventMask; // could be
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.
224 if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount) {
227 this.elementChangedListeners,
229 this.elementChangedListeners = new IElementChangedListener[length * 2],
231 System.arraycopy(this.elementChangedListenerMasks, 0,
232 this.elementChangedListenerMasks = new int[length * 2], 0,
235 this.elementChangedListeners[this.elementChangedListenerCount] = listener;
236 this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask;
237 this.elementChangedListenerCount++;
240 public void addPreResourceChangedListener(IResourceChangeListener listener) {
241 for (int i = 0; i < this.preResourceChangeListenerCount; i++) {
242 if (this.preResourceChangeListeners[i].equals(listener)) {
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.
249 if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount) {
252 this.preResourceChangeListeners,
254 this.preResourceChangeListeners = new IResourceChangeListener[length * 2],
257 this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener;
258 this.preResourceChangeListenerCount++;
261 public DeltaProcessor getDeltaProcessor() {
262 DeltaProcessor deltaProcessor = (DeltaProcessor) this.deltaProcessors
264 if (deltaProcessor != null)
265 return deltaProcessor;
266 deltaProcessor = new DeltaProcessor(this, JavaModelManager
267 .getJavaModelManager());
268 this.deltaProcessors.set(deltaProcessor);
269 return deltaProcessor;
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
284 info.updateProjectReferencesIfNecessary();
287 this.recordProjectUpdate(info);
290 public void initializeRoots() {
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;
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))
305 addedCurrentThread = true;
307 newRoots = new HashMap();
308 newOtherRoots = new HashMap();
309 newSourceAttachments = new HashMap();
311 IJavaModel model = JavaModelManager.getJavaModelManager()
313 IJavaProject[] projects;
315 projects = model.getJavaProjects();
316 } catch (JavaModelException e) {
317 // nothing can be done
320 for (int i = 0, length = projects.length; i < length; i++) {
321 JavaProject project = (JavaProject) projects[i];
322 // IClasspathEntry[] 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
332 // for (int j= 0, classpathLength = classpath.length; j <
333 // classpathLength; j++) {
334 // IClasspathEntry entry = classpath[j];
335 // if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT)
339 // IPath path = entry.getPath();
340 // if (newRoots.get(path) == null) {
341 // newRoots.put(path, new DeltaProcessor.RootInfo(project,
343 // ((ClasspathEntry)entry).fullInclusionPatternChars(),
344 // ((ClasspathEntry)entry).fullExclusionPatternChars(),
345 // entry.getEntryKind()));
347 // ArrayList rootList = (ArrayList)newOtherRoots.get(path);
348 // if (rootList == null) {
349 // rootList = new ArrayList();
350 // newOtherRoots.put(path, rootList);
352 // rootList.add(new DeltaProcessor.RootInfo(project, path,
353 // ((ClasspathEntry)entry).fullInclusionPatternChars(),
354 // ((ClasspathEntry)entry).fullExclusionPatternChars(),
355 // entry.getEntryKind()));
358 // // source attachment path
359 // if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY)
361 // QualifiedName qName = new
362 // QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " +
363 // path.toOSString()); //$NON-NLS-1$;
364 // String propertyString = null;
367 // ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName);
368 // } catch (CoreException e) {
371 // IPath sourceAttachmentPath;
372 // if (propertyString != null) {
374 // propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER);
375 // sourceAttachmentPath = (index < 0) ? new
376 // Path(propertyString) : new
377 // Path(propertyString.substring(0, index));
379 // sourceAttachmentPath = entry.getSourceAttachmentPath();
381 // if (sourceAttachmentPath != null) {
382 // newSourceAttachments.put(sourceAttachmentPath, path);
387 if (addedCurrentThread) {
388 this.initializingThreads.remove(currentThread);
392 synchronized (this) {
393 this.oldRoots = this.roots;
394 this.oldOtherRoots = this.otherRoots;
395 if (this.rootsAreStale && newRoots != null) { // double check
397 this.roots = newRoots;
398 this.otherRoots = newOtherRoots;
399 this.sourceAttachments = newSourceAttachments;
400 this.rootsAreStale = false;
405 public synchronized void recordProjectUpdate(ProjectUpdateInfo newInfo) {
407 JavaProject project = newInfo.project;
408 ProjectUpdateInfo oldInfo = (ProjectUpdateInfo) this.projectUpdates
410 if (oldInfo != null) { // refresh new classpath information
411 oldInfo.newRawPath = newInfo.newRawPath;
412 oldInfo.newResolvedPath = newInfo.newResolvedPath;
414 this.projectUpdates.put(project, newInfo);
418 public synchronized ProjectUpdateInfo[] removeAllProjectUpdates() {
419 int length = this.projectUpdates.size();
422 ProjectUpdateInfo[] updates = new ProjectUpdateInfo[length];
423 this.projectUpdates.values().toArray(updates);
424 this.projectUpdates.clear();
428 public void removeElementChangedListener(IElementChangedListener listener) {
430 for (int i = 0; i < this.elementChangedListenerCount; i++) {
432 if (this.elementChangedListeners[i].equals(listener)) {
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,
440 int[] newMasks = new int[length];
441 System.arraycopy(this.elementChangedListenerMasks, 0, newMasks,
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);
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--;
464 public void removePreResourceChangedListener(
465 IResourceChangeListener listener) {
467 for (int i = 0; i < this.preResourceChangeListenerCount; i++) {
469 if (this.preResourceChangeListeners[i].equals(listener)) {
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,
478 // copy trailing listeners
479 int trailingLength = this.preResourceChangeListenerCount - i
481 if (trailingLength > 0) {
482 System.arraycopy(this.preResourceChangeListeners, i + 1,
483 newListeners, i, trailingLength);
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--;
496 public void resourceChanged(final IResourceChangeEvent event) {
497 boolean isPostChange = event.getType() == IResourceChangeEvent.POST_CHANGE;
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) {
507 "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$
510 public void run() throws Exception {
511 listener.resourceChanged(event);
517 getDeltaProcessor().resourceChanged(event);
519 // TODO (jerome) see 47631, may want to get rid of following so as
520 // to reuse delta processor ?
522 this.deltaProcessors.set(null);
529 * Update the roots that are affected by the addition or the removal of the
530 * given container resource.
532 // public synchronized void updateRoots(IPath containerPath, IResourceDelta
533 // containerDelta, DeltaProcessor deltaProcessor) {
535 // Map otherUpdatedRoots;
536 // if (containerDelta.getKind() == IResourceDelta.REMOVED) {
537 // updatedRoots = this.oldRoots;
538 // otherUpdatedRoots = this.oldOtherRoots;
540 // updatedRoots = this.roots;
541 // otherUpdatedRoots = this.otherRoots;
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);
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);
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);