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.ArrayList;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.Iterator;
22 import net.sourceforge.phpdt.core.IClasspathEntry;
23 import net.sourceforge.phpdt.core.IElementChangedListener;
24 import net.sourceforge.phpdt.core.IJavaElement;
25 import net.sourceforge.phpdt.core.IJavaModel;
26 import net.sourceforge.phpdt.core.IJavaProject;
27 import net.sourceforge.phpdt.core.JavaCore;
28 import net.sourceforge.phpdt.core.JavaModelException;
29 import net.sourceforge.phpdt.internal.core.util.Util;
31 import org.eclipse.core.resources.IProject;
32 import org.eclipse.core.resources.IProjectDescription;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.resources.IResourceChangeEvent;
35 import org.eclipse.core.resources.IResourceChangeListener;
36 import org.eclipse.core.resources.IResourceDelta;
37 import org.eclipse.core.resources.IWorkspace;
38 import org.eclipse.core.resources.IWorkspaceRoot;
39 import org.eclipse.core.resources.ResourcesPlugin;
40 import org.eclipse.core.runtime.CoreException;
41 import org.eclipse.core.runtime.IPath;
42 import org.eclipse.core.runtime.ISafeRunnable;
43 import org.eclipse.core.runtime.Path;
44 import org.eclipse.core.runtime.Platform;
45 import org.eclipse.core.runtime.QualifiedName;
46 import net.sourceforge.phpdt.internal.core.DeltaProcessor;
47 import net.sourceforge.phpdt.internal.core.JavaModelManager;
49 import net.sourceforge.phpdt.core.ElementChangedEvent;
50 import net.sourceforge.phpdt.core.IJavaElementDelta;
51 import net.sourceforge.phpdt.internal.core.builder.PHPBuilder;
54 * Keep the global states used during Java element delta processing.
56 public class DeltaProcessingState implements IResourceChangeListener {
59 * Collection of listeners for Java element deltas
61 public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5];
62 public int[] elementChangedListenerMasks = new int[5];
63 public int elementChangedListenerCount = 0;
66 * Collection of pre Java resource change listeners
68 public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1];
69 public int preResourceChangeListenerCount = 0;
72 * The delta processor for the current thread.
74 private ThreadLocal deltaProcessors = new ThreadLocal();
76 /* A table from IPath (from a classpath entry) to RootInfo */
77 public HashMap roots = new HashMap();
79 /* A table from IPath (from a classpath entry) to ArrayList of RootInfo
80 * Used when an IPath corresponds to more than one root */
81 public HashMap otherRoots = new HashMap();
83 /* A table from IPath (from a classpath entry) to RootInfo
84 * from the last time the delta processor was invoked. */
85 public HashMap oldRoots = new HashMap();
87 /* A table from IPath (from a classpath entry) to ArrayList of RootInfo
88 * from the last time the delta processor was invoked.
89 * Used when an IPath corresponds to more than one root */
90 public HashMap oldOtherRoots = new HashMap();
92 /* A table from IPath (a source attachment path from a classpath entry) to IPath (a root path) */
93 public HashMap sourceAttachments = new HashMap();
95 /* Whether the roots tables should be recomputed */
96 public boolean rootsAreStale = true;
98 /* Threads that are currently running initializeRoots() */
99 private Set initializingThreads = Collections.synchronizedSet(new HashSet());
101 public Hashtable externalTimeStamps = new Hashtable();
103 public HashMap projectUpdates = new HashMap();
105 public static class ProjectUpdateInfo {
107 IClasspathEntry[] oldResolvedPath;
108 IClasspathEntry[] newResolvedPath;
109 IClasspathEntry[] newRawPath;
112 * Update projects references so that the build order is consistent with the classpath
114 public void updateProjectReferencesIfNecessary() throws JavaModelException {
116 String[] oldRequired = this.project.projectPrerequisites(this.oldResolvedPath);
118 if (this.newResolvedPath == null) {
119 this.newResolvedPath = this.project.getResolvedClasspath(this.newRawPath, null, true, true, null/*no reverse map*/);
121 String[] newRequired = this.project.projectPrerequisites(this.newResolvedPath);
123 IProject projectResource = this.project.getProject();
124 IProjectDescription description = projectResource.getDescription();
126 IProject[] projectReferences = description.getDynamicReferences();
128 HashSet oldReferences = new HashSet(projectReferences.length);
129 for (int i = 0; i < projectReferences.length; i++){
130 String projectName = projectReferences[i].getName();
131 oldReferences.add(projectName);
133 HashSet newReferences = (HashSet)oldReferences.clone();
135 for (int i = 0; i < oldRequired.length; i++){
136 String projectName = oldRequired[i];
137 newReferences.remove(projectName);
139 for (int i = 0; i < newRequired.length; i++){
140 String projectName = newRequired[i];
141 newReferences.add(projectName);
145 int newSize = newReferences.size();
148 if (oldReferences.size() == newSize){
149 iter = newReferences.iterator();
150 while (iter.hasNext()){
151 if (!oldReferences.contains(iter.next())){
158 String[] requiredProjectNames = new String[newSize];
160 iter = newReferences.iterator();
161 while (iter.hasNext()){
162 requiredProjectNames[index++] = (String)iter.next();
164 Util.sort(requiredProjectNames); // ensure that if changed, the order is consistent
166 IProject[] requiredProjectArray = new IProject[newSize];
167 IWorkspaceRoot wksRoot = projectResource.getWorkspace().getRoot();
168 for (int i = 0; i < newSize; i++){
169 requiredProjectArray[i] = wksRoot.getProject(requiredProjectNames[i]);
171 description.setDynamicReferences(requiredProjectArray);
172 projectResource.setDescription(description, null);
174 } catch(CoreException e){
175 throw new JavaModelException(e);
182 * Workaround for bug 15168 circular errors not reported
183 * This is a cache of the projects before any project addition/deletion has started.
185 public IJavaProject[] modelProjectsCache;
188 * Need to clone defensively the listener information, in case some listener is reacting to some notification iteration by adding/changing/removing
189 * any of the other (for example, if it deregisters itself).
191 public void addElementChangedListener(IElementChangedListener listener, int eventMask) {
192 for (int i = 0; i < this.elementChangedListenerCount; i++){
193 if (this.elementChangedListeners[i].equals(listener)){
195 // only clone the masks, since we could be in the middle of notifications and one listener decide to change
196 // any event mask of another listeners (yet not notified).
197 int cloneLength = this.elementChangedListenerMasks.length;
198 System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[cloneLength], 0, cloneLength);
199 this.elementChangedListenerMasks[i] = eventMask; // could be different
203 // 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.
205 if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount){
206 System.arraycopy(this.elementChangedListeners, 0, this.elementChangedListeners = new IElementChangedListener[length*2], 0, length);
207 System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[length*2], 0, length);
209 this.elementChangedListeners[this.elementChangedListenerCount] = listener;
210 this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask;
211 this.elementChangedListenerCount++;
214 public void addPreResourceChangedListener(IResourceChangeListener listener) {
215 for (int i = 0; i < this.preResourceChangeListenerCount; i++){
216 if (this.preResourceChangeListeners[i].equals(listener)) {
220 // 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.
222 if ((length = this.preResourceChangeListeners.length) == this.preResourceChangeListenerCount){
223 System.arraycopy(this.preResourceChangeListeners, 0, this.preResourceChangeListeners = new IResourceChangeListener[length*2], 0, length);
225 this.preResourceChangeListeners[this.preResourceChangeListenerCount] = listener;
226 this.preResourceChangeListenerCount++;
229 public DeltaProcessor getDeltaProcessor() {
230 DeltaProcessor deltaProcessor = (DeltaProcessor)this.deltaProcessors.get();
231 if (deltaProcessor != null) return deltaProcessor;
232 deltaProcessor = new DeltaProcessor(this, JavaModelManager.getJavaModelManager());
233 this.deltaProcessors.set(deltaProcessor);
234 return deltaProcessor;
237 public void performClasspathResourceChange(JavaProject project, IClasspathEntry[] oldResolvedPath, IClasspathEntry[] newResolvedPath, IClasspathEntry[] newRawPath, boolean canChangeResources) throws JavaModelException {
238 ProjectUpdateInfo info = new ProjectUpdateInfo();
239 info.project = project;
240 info.oldResolvedPath = oldResolvedPath;
241 info.newResolvedPath = newResolvedPath;
242 info.newRawPath = newRawPath;
243 if (canChangeResources) {
244 this.projectUpdates.remove(project); // remove possibly awaiting one
245 info.updateProjectReferencesIfNecessary();
248 this.recordProjectUpdate(info);
251 public void initializeRoots() {
253 // recompute root infos only if necessary
254 HashMap newRoots = null;
255 HashMap newOtherRoots = null;
256 HashMap newSourceAttachments = null;
257 if (this.rootsAreStale) {
258 Thread currentThread = Thread.currentThread();
259 boolean addedCurrentThread = false;
261 // if reentering initialization (through a container initializer for example) no need to compute roots again
262 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=47213
263 if (!this.initializingThreads.add(currentThread)) return;
264 addedCurrentThread = true;
266 newRoots = new HashMap();
267 newOtherRoots = new HashMap();
268 newSourceAttachments = new HashMap();
270 IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel();
271 IJavaProject[] projects;
273 projects = model.getJavaProjects();
274 } catch (JavaModelException e) {
275 // nothing can be done
278 for (int i = 0, length = projects.length; i < length; i++) {
279 JavaProject project = (JavaProject) projects[i];
280 // IClasspathEntry[] classpath;
282 // classpath = project.getResolvedClasspath(true/*ignoreUnresolvedEntry*/, false/*don't generateMarkerOnError*/, false/*don't returnResolutionInProgress*/);
283 // } catch (JavaModelException e) {
284 // // continue with next project
287 // for (int j= 0, classpathLength = classpath.length; j < classpathLength; j++) {
288 // IClasspathEntry entry = classpath[j];
289 // if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) continue;
292 // IPath path = entry.getPath();
293 // if (newRoots.get(path) == null) {
294 // newRoots.put(path, new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry.getEntryKind()));
296 // ArrayList rootList = (ArrayList)newOtherRoots.get(path);
297 // if (rootList == null) {
298 // rootList = new ArrayList();
299 // newOtherRoots.put(path, rootList);
301 // rootList.add(new DeltaProcessor.RootInfo(project, path, ((ClasspathEntry)entry).fullInclusionPatternChars(), ((ClasspathEntry)entry).fullExclusionPatternChars(), entry.getEntryKind()));
304 // // source attachment path
305 // if (entry.getEntryKind() != IClasspathEntry.CPE_LIBRARY) continue;
306 // QualifiedName qName = new QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " + path.toOSString()); //$NON-NLS-1$;
307 // String propertyString = null;
309 // propertyString = ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName);
310 // } catch (CoreException e) {
313 // IPath sourceAttachmentPath;
314 // if (propertyString != null) {
315 // int index= propertyString.lastIndexOf(PackageFragmentRoot.ATTACHMENT_PROPERTY_DELIMITER);
316 // sourceAttachmentPath = (index < 0) ? new Path(propertyString) : new Path(propertyString.substring(0, index));
318 // sourceAttachmentPath = entry.getSourceAttachmentPath();
320 // if (sourceAttachmentPath != null) {
321 // newSourceAttachments.put(sourceAttachmentPath, path);
326 if (addedCurrentThread) {
327 this.initializingThreads.remove(currentThread);
332 this.oldRoots = this.roots;
333 this.oldOtherRoots = this.otherRoots;
334 if (this.rootsAreStale && newRoots != null) { // double check again
335 this.roots = newRoots;
336 this.otherRoots = newOtherRoots;
337 this.sourceAttachments = newSourceAttachments;
338 this.rootsAreStale = false;
343 public synchronized void recordProjectUpdate(ProjectUpdateInfo newInfo) {
345 JavaProject project = newInfo.project;
346 ProjectUpdateInfo oldInfo = (ProjectUpdateInfo) this.projectUpdates.get(project);
347 if (oldInfo != null) { // refresh new classpath information
348 oldInfo.newRawPath = newInfo.newRawPath;
349 oldInfo.newResolvedPath = newInfo.newResolvedPath;
351 this.projectUpdates.put(project, newInfo);
354 public synchronized ProjectUpdateInfo[] removeAllProjectUpdates() {
355 int length = this.projectUpdates.size();
356 if (length == 0) return null;
357 ProjectUpdateInfo[] updates = new ProjectUpdateInfo[length];
358 this.projectUpdates.values().toArray(updates);
359 this.projectUpdates.clear();
363 public void removeElementChangedListener(IElementChangedListener listener) {
365 for (int i = 0; i < this.elementChangedListenerCount; i++){
367 if (this.elementChangedListeners[i].equals(listener)){
369 // need to clone defensively since we might be in the middle of listener notifications (#fire)
370 int length = this.elementChangedListeners.length;
371 IElementChangedListener[] newListeners = new IElementChangedListener[length];
372 System.arraycopy(this.elementChangedListeners, 0, newListeners, 0, i);
373 int[] newMasks = new int[length];
374 System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, 0, i);
376 // copy trailing listeners
377 int trailingLength = this.elementChangedListenerCount - i - 1;
378 if (trailingLength > 0){
379 System.arraycopy(this.elementChangedListeners, i+1, newListeners, i, trailingLength);
380 System.arraycopy(this.elementChangedListenerMasks, i+1, newMasks, i, trailingLength);
383 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto
384 // the original ones)
385 this.elementChangedListeners = newListeners;
386 this.elementChangedListenerMasks = newMasks;
387 this.elementChangedListenerCount--;
393 public void removePreResourceChangedListener(IResourceChangeListener listener) {
395 for (int i = 0; i < this.preResourceChangeListenerCount; i++){
397 if (this.preResourceChangeListeners[i].equals(listener)){
399 // need to clone defensively since we might be in the middle of listener notifications (#fire)
400 int length = this.preResourceChangeListeners.length;
401 IResourceChangeListener[] newListeners = new IResourceChangeListener[length];
402 System.arraycopy(this.preResourceChangeListeners, 0, newListeners, 0, i);
404 // copy trailing listeners
405 int trailingLength = this.preResourceChangeListenerCount - i - 1;
406 if (trailingLength > 0){
407 System.arraycopy(this.preResourceChangeListeners, i+1, newListeners, i, trailingLength);
410 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto
411 // the original ones)
412 this.preResourceChangeListeners = newListeners;
413 this.preResourceChangeListenerCount--;
419 public void resourceChanged(final IResourceChangeEvent event) {
420 boolean isPostChange = event.getType() == IResourceChangeEvent.POST_CHANGE;
422 for (int i = 0; i < this.preResourceChangeListenerCount; i++) {
423 // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
424 final IResourceChangeListener listener = this.preResourceChangeListeners[i];
425 Platform.run(new ISafeRunnable() {
426 public void handleException(Throwable exception) {
427 Util.log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$
429 public void run() throws Exception {
430 listener.resourceChanged(event);
436 getDeltaProcessor().resourceChanged(event);
438 // TODO (jerome) see 47631, may want to get rid of following so as to reuse delta processor ?
440 this.deltaProcessors.set(null);
447 * Update the roots that are affected by the addition or the removal of the given container resource.
449 // public synchronized void updateRoots(IPath containerPath, IResourceDelta containerDelta, DeltaProcessor deltaProcessor) {
451 // Map otherUpdatedRoots;
452 // if (containerDelta.getKind() == IResourceDelta.REMOVED) {
453 // updatedRoots = this.oldRoots;
454 // otherUpdatedRoots = this.oldOtherRoots;
456 // updatedRoots = this.roots;
457 // otherUpdatedRoots = this.otherRoots;
459 // Iterator iterator = updatedRoots.keySet().iterator();
460 // while (iterator.hasNext()) {
461 // IPath path = (IPath)iterator.next();
462 // if (containerPath.isPrefixOf(path) && !containerPath.equals(path)) {
463 // IResourceDelta rootDelta = containerDelta.findMember(path.removeFirstSegments(1));
464 // if (rootDelta == null) continue;
465 // DeltaProcessor.RootInfo rootInfo = (DeltaProcessor.RootInfo)updatedRoots.get(path);
467 // if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider roots that are not included in the container
468 // deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo);
471 // ArrayList rootList = (ArrayList)otherUpdatedRoots.get(path);
472 // if (rootList != null) {
473 // Iterator otherProjects = rootList.iterator();
474 // while (otherProjects.hasNext()) {
475 // rootInfo = (DeltaProcessor.RootInfo)otherProjects.next();
476 // if (!rootInfo.project.getPath().isPrefixOf(path)) { // only consider roots that are not included in the container
477 // deltaProcessor.updateCurrentDeltaAndIndex(rootDelta, IJavaElement.PACKAGE_FRAGMENT_ROOT, rootInfo);