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);