/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package net.sourceforge.phpdt.internal.core; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import net.sourceforge.phpdt.core.IClasspathEntry; import net.sourceforge.phpdt.core.IElementChangedListener; import net.sourceforge.phpdt.core.IJavaModel; import net.sourceforge.phpdt.core.IJavaProject; import net.sourceforge.phpdt.core.JavaModelException; import net.sourceforge.phpdt.internal.core.util.Util; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ISafeRunnable; //import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; /** * Keep the global states used during Java element delta processing. */ public class DeltaProcessingState implements IResourceChangeListener { /* * Collection of listeners for Java element deltas */ public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5]; public int[] elementChangedListenerMasks = new int[5]; public int elementChangedListenerCount = 0; /* * Collection of pre Java resource change listeners */ public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1]; public int preResourceChangeListenerCount = 0; /* * The delta processor for the current thread. */ private ThreadLocal deltaProcessors = new ThreadLocal(); /* A table from IPath (from a classpath entry) to RootInfo */ public HashMap roots = new HashMap(); /* * A table from IPath (from a classpath entry) to ArrayList of RootInfo Used * when an IPath corresponds to more than one root */ public HashMap otherRoots = new HashMap(); /* * A table from IPath (from a classpath entry) to RootInfo from the last * time the delta processor was invoked. */ public HashMap oldRoots = new HashMap(); /* * A table from IPath (from a classpath entry) to ArrayList of RootInfo from * the last time the delta processor was invoked. Used when an IPath * corresponds to more than one root */ public HashMap oldOtherRoots = new HashMap(); /* * A table from IPath (a source attachment path from a classpath entry) to * IPath (a root path) */ public HashMap sourceAttachments = new HashMap(); /* Whether the roots tables should be recomputed */ public boolean rootsAreStale = true; /* Threads that are currently running initializeRoots() */ private Set initializingThreads = Collections .synchronizedSet(new HashSet()); public Hashtable externalTimeStamps = new Hashtable(); public HashMap projectUpdates = new HashMap(); public static class ProjectUpdateInfo { JavaProject project; IClasspathEntry[] oldResolvedPath; IClasspathEntry[] newResolvedPath; IClasspathEntry[] newRawPath; /** * Update projects references so that the build order is consistent with * the classpath */ public void updateProjectReferencesIfNecessary() throws JavaModelException { String[] oldRequired = this.project .projectPrerequisites(this.oldResolvedPath); if (this.newResolvedPath == null) { this.newResolvedPath = this.project .getResolvedClasspath(this.newRawPath, null, true, true, null/* no reverse map */); } String[] newRequired = this.project .projectPrerequisites(this.newResolvedPath); try { IProject projectResource = this.project.getProject(); IProjectDescription description = projectResource .getDescription(); IProject[] projectReferences = description .getDynamicReferences(); HashSet oldReferences = new HashSet(projectReferences.length); for (int i = 0; i < projectReferences.length; i++) { String projectName = projectReferences[i].getName(); oldReferences.add(projectName); } HashSet newReferences = (HashSet) oldReferences.clone(); for (int i = 0; i < oldRequired.length; i++) { String projectName = oldRequired[i]; newReferences.remove(projectName); } for (int i = 0; i < newRequired.length; i++) { String projectName = newRequired[i]; newReferences.add(projectName); } Iterator iter; int newSize = newReferences.size(); checkIdentity: { if (oldReferences.size() == newSize) { iter = newReferences.iterator(); while (iter.hasNext()) { if (!oldReferences.contains(iter.next())) { break checkIdentity; } } return; } } String[] requiredProjectNames = new String[newSize]; int index = 0; iter = newReferences.iterator(); while (iter.hasNext()) { requiredProjectNames[index++] = (String) iter.next(); } Util.sort(requiredProjectNames); // ensure that if changed, // the order is consistent IProject[] requiredProjectArray = new IProject[newSize]; IWorkspaceRoot wksRoot = projectResource.getWorkspace() .getRoot(); for (int i = 0; i < newSize; i++) { requiredProjectArray[i] = wksRoot .getProject(requiredProjectNames[i]); } description.setDynamicReferences(requiredProjectArray); projectResource.setDescription(description, null); } catch (CoreException e) { throw new JavaModelException(e); } } } /** * Workaround for bug 15168 circular errors not reported This is a cache of * the projects before any project addition/deletion has started. */ public IJavaProject[] modelProjectsCache; /* * Need to clone defensively the listener information, in case some listener * is reacting to some notification iteration by adding/changing/removing * any of the other (for example, if it deregisters itself). */ // public void addElementChangedListener(IElementChangedListener listener, // int eventMask) { // for (int i = 0; i < this.elementChangedListenerCount; i++) { // if (this.elementChangedListeners[i].equals(listener)) { // // // only clone the masks, since we could be in the middle of // // notifications and one listener decide to change // // any event mask of another listeners (yet not notified). // int cloneLength = this.elementChangedListenerMasks.length; // System // .arraycopy( // this.elementChangedListenerMasks, // 0, // this.elementChangedListenerMasks = new int[cloneLength], // 0, cloneLength); // this.elementChangedListenerMasks[i] = eventMask; // could be // // different // return; // } // } // // may need to grow, no need to clone, since iterators will have cached // // original arrays and max boundary and we only add to the end. // int length; // if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount) { // System // .arraycopy( // this.elementChangedListeners, // 0, // this.elementChangedListeners = new IElementChangedListener[length * 2], // 0, length); // System.arraycopy(this.elementChangedListenerMasks, 0, // this.elementChangedListenerMasks = new int[length * 2], 0, // length); // } // this.elementChangedListeners[this.elementChangedListenerCount] = listener; // this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask; // this.elementChangedListenerCount++; // } public void addPreResourceChangedListener(IResourceChangeListener listener) { for (int i = 0; i < this.preResourceChangeListenerCount; i++) { if (this.preResourceChangeListeners[i].equals(listener)) { return; } } // may need to grow, no need to clone, since iterators will have cached // original arrays and max boundary and we only add to the end. int length; if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount) { System .arraycopy( this.preResourceChangeListeners, 0, this.preResourceChangeListeners = new IResourceChangeListener[length * 2], 0, length); } this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener; this.preResourceChangeListenerCount++; } public DeltaProcessor getDeltaProcessor() { DeltaProcessor deltaProcessor = (DeltaProcessor) this.deltaProcessors .get(); if (deltaProcessor != null) return deltaProcessor; deltaProcessor = new DeltaProcessor(this, JavaModelManager .getJavaModelManager()); this.deltaProcessors.set(deltaProcessor); return deltaProcessor; } // public void performClasspathResourceChange(JavaProject project, // IClasspathEntry[] oldResolvedPath, // IClasspathEntry[] newResolvedPath, IClasspathEntry[] newRawPath, // boolean canChangeResources) throws JavaModelException { // ProjectUpdateInfo info = new ProjectUpdateInfo(); // info.project = project; // info.oldResolvedPath = oldResolvedPath; // info.newResolvedPath = newResolvedPath; // info.newRawPath = newRawPath; // if (canChangeResources) { // this.projectUpdates.remove(project); // remove possibly awaiting // // one // info.updateProjectReferencesIfNecessary(); // return; // } // this.recordProjectUpdate(info); // } public void initializeRoots() { // recompute root infos only if necessary HashMap newRoots = null; HashMap newOtherRoots = null; HashMap newSourceAttachments = null; if (this.rootsAreStale) { Thread currentThread = Thread.currentThread(); boolean addedCurrentThread = false; try { // if reentering initialization (through a container initializer // for example) no need to compute roots again // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=47213 if (!this.initializingThreads.add(currentThread)) return; addedCurrentThread = true; newRoots = new HashMap(); newOtherRoots = new HashMap(); newSourceAttachments = new HashMap(); IJavaModel model = JavaModelManager.getJavaModelManager() .getJavaModel(); IJavaProject[] projects; try { projects = model.getJavaProjects(); } catch (JavaModelException e) { // nothing can be done return; } for (int i = 0, length = projects.length; i < length; i++) { //JavaProject project = (JavaProject) projects[i]; // IClasspathEntry[] classpath; // try { // classpath = // project.getResolvedClasspath(true/*ignoreUnresolvedEntry*/, // false/*don't generateMarkerOnError*/, false/*don't // returnResolutionInProgress*/); // } catch (JavaModelException e) { // // continue with next project // continue; // } // for (int j= 0, classpathLength = classpath.length; j < // classpathLength; j++) { // IClasspathEntry entry = classpath[j]; // if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) // continue; // // // root path // IPath path = entry.getPath(); // if (newRoots.get(path) == null) { // newRoots.put(path, new DeltaProcessor.RootInfo(project, // path, // ((ClasspathEntry)entry).fullInclusionPatternChars(), // ((ClasspathEntry)entry).fullExclusionPatternChars(), // entry.getEntryKind())); // } else { // ArrayList rootList = (ArrayList)newOtherRoots.get(path); // if (rootList == null) { // rootList = new ArrayList(); // newOtherRoots.put(path, rootList); // } // rootList.add(new DeltaProcessor.RootInfo(project, path, // ((ClasspathEntry)entry).fullInclusionPatternChars(), // ((ClasspathEntry)entry).fullExclusionPatternChars(), // entry.getEntryKind())); // } // // // source attachment path // if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY) // continue; // QualifiedName qName = new // QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " + // path.toOSString()); //$NON-NLS-1$; // String propertyString = null; // try { // propertyString = // ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName); // } catch (CoreException e) { // continue; // } // IPath sourceAttachmentPath; // if (propertyString != null) { // int index= // propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER); // sourceAttachmentPath = (index < 0) ? new // Path(propertyString) : new // Path(propertyString.substring(0, index)); // } else { // sourceAttachmentPath = entry.getSourceAttachmentPath(); // } // if (sourceAttachmentPath != null) { // newSourceAttachments.put(sourceAttachmentPath, path); // } // } } } finally { if (addedCurrentThread) { this.initializingThreads.remove(currentThread); } } } synchronized (this) { this.oldRoots = this.roots; this.oldOtherRoots = this.otherRoots; if (this.rootsAreStale && newRoots != null) { // double check // again this.roots = newRoots; this.otherRoots = newOtherRoots; this.sourceAttachments = newSourceAttachments; this.rootsAreStale = false; } } } // public synchronized void recordProjectUpdate(ProjectUpdateInfo newInfo) { // // JavaProject project = newInfo.project; // ProjectUpdateInfo oldInfo = (ProjectUpdateInfo) this.projectUpdates // .get(project); // if (oldInfo != null) { // refresh new classpath information // oldInfo.newRawPath = newInfo.newRawPath; // oldInfo.newResolvedPath = newInfo.newResolvedPath; // } else { // this.projectUpdates.put(project, newInfo); // } // } public synchronized ProjectUpdateInfo[] removeAllProjectUpdates() { int length = this.projectUpdates.size(); if (length == 0) return null; ProjectUpdateInfo[] updates = new ProjectUpdateInfo[length]; this.projectUpdates.values().toArray(updates); this.projectUpdates.clear(); return updates; } // public void removeElementChangedListener(IElementChangedListener listener) { // // for (int i = 0; i < this.elementChangedListenerCount; i++) { // // if (this.elementChangedListeners[i].equals(listener)) { // // // need to clone defensively since we might be in the middle of // // listener notifications (#fire) // int length = this.elementChangedListeners.length; // IElementChangedListener[] newListeners = new IElementChangedListener[length]; // System.arraycopy(this.elementChangedListeners, 0, newListeners, // 0, i); // int[] newMasks = new int[length]; // System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, // 0, i); // // // copy trailing listeners // int trailingLength = this.elementChangedListenerCount - i - 1; // if (trailingLength > 0) { // System.arraycopy(this.elementChangedListeners, i + 1, // newListeners, i, trailingLength); // System.arraycopy(this.elementChangedListenerMasks, i + 1, // newMasks, i, trailingLength); // } // // // update manager listener state (#fire need to iterate over // // original listeners through a local variable to hold onto // // the original ones) // this.elementChangedListeners = newListeners; // this.elementChangedListenerMasks = newMasks; // this.elementChangedListenerCount--; // return; // } // } // } // public void removePreResourceChangedListener( // IResourceChangeListener listener) { // // for (int i = 0; i < this.preResourceChangeListenerCount; i++) { // // if (this.preResourceChangeListeners[i].equals(listener)) { // // // need to clone defensively since we might be in the middle of // // listener notifications (#fire) // int length = this.preResourceChangeListeners.length; // IResourceChangeListener[] newListeners = new IResourceChangeListener[length]; // System.arraycopy(this.preResourceChangeListeners, 0, // newListeners, 0, i); // // // copy trailing listeners // int trailingLength = this.preResourceChangeListenerCount - i // - 1; // if (trailingLength > 0) { // System.arraycopy(this.preResourceChangeListeners, i + 1, // newListeners, i, trailingLength); // } // // // update manager listener state (#fire need to iterate over // // original listeners through a local variable to hold onto // // the original ones) // this.preResourceChangeListeners = newListeners; // this.preResourceChangeListenerCount--; // return; // } // } // } public void resourceChanged(final IResourceChangeEvent event) { boolean isPostChange = event.getType() == IResourceChangeEvent.POST_CHANGE; if (isPostChange) { for (int i = 0; i < this.preResourceChangeListenerCount; i++) { // wrap callbacks with Safe runnable for subsequent listeners to // be called when some are causing grief final IResourceChangeListener listener = this.preResourceChangeListeners[i]; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { Util .log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$ } public void run() throws Exception { listener.resourceChanged(event); } }); } } try { getDeltaProcessor().resourceChanged(event); } finally { // TODO (jerome) see 47631, may want to get rid of following so as // to reuse delta processor ? if (isPostChange) { this.deltaProcessors.set(null); } } } /* * Update the roots that are affected by the addition or the removal of the * given container resource. */ // public synchronized void updateRoots(IPath containerPath, IResourceDelta // containerDelta, DeltaProcessor deltaProcessor) { // Map updatedRoots; // Map otherUpdatedRoots; // if (containerDelta.getKind() == IResourceDelta.REMOVED) { // updatedRoots = this.oldRoots; // otherUpdatedRoots = this.oldOtherRoots; // } else { // updatedRoots = this.roots; // otherUpdatedRoots = this.otherRoots; // } // Iterator iterator = updatedRoots.keySet().iterator(); // while (iterator.hasNext()) { // IPath path = (IPath)iterator.next(); // if (containerPath.isPrefixOf(path) && !containerPath.equals(path)) { // IResourceDelta rootDelta = // containerDelta.findMember(path.removeFirstSegments(1)); // if (rootDelta == null) continue; // DeltaProcessor.RootInfo rootInfo = // (DeltaProcessor.RootInfo)updatedRoots.get(path); // // if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider // roots that are not included in the container // deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, // IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); // } // // ArrayList rootList = (ArrayList)otherUpdatedRoots.get(path); // if (rootList != null) { // Iterator otherProjects = rootList.iterator(); // while (otherProjects.hasNext()) { // rootInfo = (DeltaProcessor.RootInfo)otherProjects.next(); // if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider // roots that are not included in the container // deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, // IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo); // } // } // } // } // } // } }