--- /dev/null
+/*******************************************************************************
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import net.sourceforge.phpdt.core.IClasspathEntry;
+import net.sourceforge.phpdt.core.IElementChangedListener;
+import net.sourceforge.phpdt.core.IJavaElement;
+import net.sourceforge.phpdt.core.IJavaModel;
+import net.sourceforge.phpdt.core.IJavaProject;
+import net.sourceforge.phpdt.core.JavaCore;
+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.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.QualifiedName;
+import net.sourceforge.phpdt.internal.core.DeltaProcessor;
+import net.sourceforge.phpdt.internal.core.JavaModelManager;
+
+import net.sourceforge.phpdt.core.ElementChangedEvent;
+import net.sourceforge.phpdt.core.IJavaElementDelta;
+import net.sourceforge.phpdt.internal.core.builder.PHPBuilder;
+
+/**
+ * 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];
+ Platform.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);
+// }
+// }
+// }
+// }
+// }
+// }
+
+}