improved PHP parser
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / SetClasspathOperation.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.Map;
18
19 import net.sourceforge.phpdt.core.IClasspathEntry;
20 import net.sourceforge.phpdt.core.IJavaElement;
21 import net.sourceforge.phpdt.core.IJavaElementDelta;
22 import net.sourceforge.phpdt.core.IJavaModel;
23 import net.sourceforge.phpdt.core.IJavaModelStatus;
24 import net.sourceforge.phpdt.core.IJavaProject;
25 import net.sourceforge.phpdt.core.IPackageFragmentRoot;
26 import net.sourceforge.phpdt.core.JavaModelException;
27 import net.sourceforge.phpdt.internal.compiler.util.ObjectVector;
28 import net.sourceforge.phpdt.internal.core.util.Util;
29
30 import org.eclipse.core.resources.IFolder;
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.IWorkspaceRoot;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.core.runtime.IPath;
37 import org.eclipse.core.runtime.Path;
38
39 /**
40  * This operation sets an <code>IJavaProject</code>'s classpath.
41  *
42  * @see IJavaProject
43  */
44 public class SetClasspathOperation extends JavaModelOperation {
45
46         IClasspathEntry[] oldResolvedPath, newResolvedPath;
47         IClasspathEntry[] newRawPath;
48         boolean canChangeResources;
49         boolean classpathWasSaved;
50         boolean needCycleCheck;
51         boolean needValidation;
52         boolean needSave;
53         IPath newOutputLocation;
54         JavaProject project;
55         boolean identicalRoots;
56         
57         public static final IClasspathEntry[] ReuseClasspath = new IClasspathEntry[0];
58         public static final IClasspathEntry[] UpdateClasspath = new IClasspathEntry[0];
59         // if reusing output location, then also reuse clean flag
60         public static final IPath ReuseOutputLocation = new Path("Reuse Existing Output Location");  //$NON-NLS-1$
61         
62         /**
63          * When executed, this operation sets the classpath of the given project.
64          */
65         public SetClasspathOperation(
66                 JavaProject project,
67                 IClasspathEntry[] oldResolvedPath,
68                 IClasspathEntry[] newRawPath,
69                 IPath newOutputLocation,
70                 boolean canChangeResource,
71                 boolean needValidation,
72                 boolean needSave) {
73
74                 super(new IJavaElement[] { project });
75                 this.oldResolvedPath = oldResolvedPath;
76                 this.newRawPath = newRawPath;
77                 this.newOutputLocation = newOutputLocation;
78                 this.canChangeResources = canChangeResource;
79                 this.needValidation = needValidation;
80                 this.needSave = needSave;
81                 this.project = project;
82         }
83
84         /**
85          * Adds deltas for the given roots, with the specified change flag,
86          * and closes the root. Helper method for #setClasspath
87          */
88         protected void addClasspathDeltas(
89                 IPackageFragmentRoot[] roots,
90                 int flag,
91                 JavaElementDelta delta) {
92
93                 for (int i = 0; i < roots.length; i++) {
94                         IPackageFragmentRoot root = roots[i];
95                         delta.changed(root, flag);
96                         if ((flag & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) != 0 
97                                         || (flag & IJavaElementDelta.F_SOURCEATTACHED) != 0
98                                         || (flag & IJavaElementDelta.F_SOURCEDETACHED) != 0){
99                                 try {
100                                         root.close();
101                                 } catch (JavaModelException e) {
102                                 }
103                                 // force detach source on jar package fragment roots (source will be lazily computed when needed)
104                                 ((PackageFragmentRoot) root).setSourceAttachmentProperty(null);// loose info - will be recomputed
105                         }
106                 }
107         }
108
109
110
111         /**
112          * Returns the index of the item in the list if the given list contains the specified entry. If the list does
113          * not contain the entry, -1 is returned.
114          * A helper method for #setClasspath
115          */
116         protected int classpathContains(
117                 IClasspathEntry[] list,
118                 IClasspathEntry entry) {
119
120                 IPath[] exclusionPatterns = entry.getExclusionPatterns();
121                 nextEntry: for (int i = 0; i < list.length; i++) {
122                         IClasspathEntry other = list[i];
123                         if (other.getContentKind() == entry.getContentKind()
124                                 && other.getEntryKind() == entry.getEntryKind()
125                                 && other.isExported() == entry.isExported()
126                                 && other.getPath().equals(entry.getPath())) {
127                                         // check custom outputs
128                                         IPath entryOutput = entry.getOutputLocation();
129                                         IPath otherOutput = other.getOutputLocation();
130                                         if (entryOutput == null) {
131                                                 if (otherOutput != null)
132                                                         continue;
133                                         } else {
134                                                 if (!entryOutput.equals(otherOutput))
135                                                         continue;
136                                         }
137                                         
138                                         // check exclusion patterns
139                                         IPath[] otherExcludes = other.getExclusionPatterns();
140                                         if (exclusionPatterns != otherExcludes) {
141                                                 int excludeLength = exclusionPatterns.length;
142                                                 if (otherExcludes.length != excludeLength)
143                                                         continue;
144                                                 for (int j = 0; j < excludeLength; j++) {
145                                                         // compare toStrings instead of IPaths 
146                                                         // since IPath.equals is specified to ignore trailing separators
147                                                         if (!exclusionPatterns[j].toString().equals(otherExcludes[j].toString()))
148                                                                 continue nextEntry;
149                                                 }
150                                         }
151                                         return i;
152                         }
153                 }
154                 return -1;
155         }
156
157         /**
158          * Recursively adds all subfolders of <code>folder</code> to the given collection.
159          */
160         protected void collectAllSubfolders(IFolder folder, ArrayList collection) throws JavaModelException {
161                 try {
162                         IResource[] members= folder.members();
163                         for (int i = 0, max = members.length; i < max; i++) {
164                                 IResource r= members[i];
165                                 if (r.getType() == IResource.FOLDER) {
166                                         collection.add(r);
167                                         collectAllSubfolders((IFolder)r, collection);
168                                 }
169                         }       
170                 } catch (CoreException e) {
171                         throw new JavaModelException(e);
172                 }
173         }
174
175         /**
176          * Returns a collection of package fragments that have been added/removed
177          * as the result of changing the output location to/from the given
178          * location. The collection is empty if no package fragments are
179          * affected.
180          */
181 //      protected ArrayList determineAffectedPackageFragments(IPath location) throws JavaModelException {
182 //              ArrayList fragments = new ArrayList();
183 //              JavaProject project =getProject();
184 //      
185 //              // see if this will cause any package fragments to be affected
186 //              IWorkspace workspace = ResourcesPlugin.getWorkspace();
187 //              IResource resource = null;
188 //              if (location != null) {
189 //                      resource = workspace.getRoot().findMember(location);
190 //              }
191 //              if (resource != null && resource.getType() == IResource.FOLDER) {
192 //                      IFolder folder = (IFolder) resource;
193 //                      // only changes if it actually existed
194 //                      IClasspathEntry[] classpath = project.getExpandedClasspath(true);
195 //                      for (int i = 0; i < classpath.length; i++) {
196 //                              IClasspathEntry entry = classpath[i];
197 //                              IPath path = classpath[i].getPath();
198 //                              if (entry.getEntryKind() != IClasspathEntry.CPE_PROJECT && path.isPrefixOf(location) && !path.equals(location)) {
199 //                                      IPackageFragmentRoot[] roots = project.computePackageFragmentRoots(classpath[i]);
200 //                                      IPackageFragmentRoot root = roots[0];
201 //                                      // now the output location becomes a package fragment - along with any subfolders
202 //                                      ArrayList folders = new ArrayList();
203 //                                      folders.add(folder);
204 //                                      collectAllSubfolders(folder, folders);
205 //                                      Iterator elements = folders.iterator();
206 //                                      int segments = path.segmentCount();
207 //                                      while (elements.hasNext()) {
208 //                                              IFolder f = (IFolder) elements.next();
209 //                                              IPath relativePath = f.getFullPath().removeFirstSegments(segments);
210 //                                              String name = relativePath.toOSString();
211 //                                              name = name.replace(File.pathSeparatorChar, '.');
212 //                                              if (name.endsWith(".")) { //$NON-NLS-1$
213 //                                                      name = name.substring(0, name.length() - 1);
214 //                                              }
215 //                                              IPackageFragment pkg = root.getPackageFragment(name);
216 //                                              fragments.add(pkg);
217 //                                      }
218 //                              }
219 //                      }
220 //              }
221 //              return fragments;
222 //      }
223         /**
224          * Sets the classpath of the pre-specified project.
225          */
226         protected void executeOperation() throws JavaModelException {
227                 // project reference updated - may throw an exception if unable to write .project file
228                 updateProjectReferencesIfNecessary();
229
230                 // classpath file updated - may throw an exception if unable to write .classpath file
231                 saveClasspathIfNecessary();
232                 
233                 // perform classpath and output location updates, if exception occurs in classpath update,
234                 // make sure the output location is updated before surfacing the exception (in case the output
235                 // location update also throws an exception, give priority to the classpath update one).
236                 JavaModelException originalException = null;
237
238                 try {
239                         if (this.newRawPath == UpdateClasspath) this.newRawPath = project.getRawClasspath();
240                         if (this.newRawPath != ReuseClasspath){
241                                 updateClasspath();
242                                 project.updatePackageFragmentRoots();
243                                 JavaModelManager.getJavaModelManager().getDeltaProcessor().addForRefresh(project);
244                         }
245
246                 } catch(JavaModelException e){
247                         originalException = e;
248                         throw e;
249
250                 } finally { // if traversed by an exception we still need to update the output location when necessary
251
252                         try {
253                                 if (this.newOutputLocation != ReuseOutputLocation) updateOutputLocation();
254
255                         } catch(JavaModelException e){
256                                 if (originalException != null) throw originalException; 
257                                 throw e;
258                         } finally {
259                                 // ensures the project is getting rebuilt if only variable is modified
260                                 if (!this.identicalRoots && this.canChangeResources) {
261                                         try {
262                                                 this.project.getProject().touch(this.progressMonitor);
263                                         } catch (CoreException e) {
264                                                 if (JavaModelManager.CP_RESOLVE_VERBOSE){
265                                                         Util.verbose("CPContainer INIT - FAILED to touch project: "+ this.project.getElementName(), System.err); //$NON-NLS-1$
266                                                         e.printStackTrace();
267                                                 }
268                                         }
269                                 }                               
270                         }
271                 }
272                 done();
273         }
274         
275         /**
276          * Generates the delta of removed/added/reordered roots.
277          * Use three deltas in case the same root is removed/added/reordered (for
278          * instance, if it is changed from K_SOURCE to K_BINARY or vice versa)
279          */
280         protected void generateClasspathChangeDeltas() {
281
282                 JavaModelManager manager = JavaModelManager.getJavaModelManager();
283                 boolean needToUpdateDependents = false;
284                 JavaElementDelta delta = new JavaElementDelta(getJavaModel());
285                 boolean hasDelta = false;
286                 if (this.classpathWasSaved) {
287                         delta.changed(this.project, IJavaElementDelta.F_CLASSPATH_CHANGED);
288                         hasDelta = true;
289                 }
290                 int oldLength = oldResolvedPath.length;
291                 int newLength = newResolvedPath.length;
292                         
293 //              final IndexManager indexManager = manager.getIndexManager();
294                 Map oldRoots = null;
295                 IPackageFragmentRoot[] roots = null;
296                 if (project.isOpen()) {
297                         try {
298                                 roots = project.getPackageFragmentRoots();
299                         } catch (JavaModelException e) {
300                                 // ignore
301                         }
302                 } else {
303                         Map allRemovedRoots ;
304                         if ((allRemovedRoots = manager.getDeltaProcessor().removedRoots) != null) {
305                                 roots = (IPackageFragmentRoot[]) allRemovedRoots.get(project);
306                         }
307                 }
308                 if (roots != null) {
309                         oldRoots = new HashMap();
310                         for (int i = 0; i < roots.length; i++) {
311                                 IPackageFragmentRoot root = roots[i];
312                                 oldRoots.put(root.getPath(), root);
313                         }
314                 }
315                 for (int i = 0; i < oldLength; i++) {
316                         
317                         int index = classpathContains(newResolvedPath, oldResolvedPath[i]);
318                         if (index == -1) {
319                                 // do not notify remote project changes
320                                 if (oldResolvedPath[i].getEntryKind() == IClasspathEntry.CPE_PROJECT){
321                                         needToUpdateDependents = true;
322                                         this.needCycleCheck = true;
323                                         continue; 
324                                 }
325
326                                 IPackageFragmentRoot[] pkgFragmentRoots = null;
327                                 if (oldRoots != null) {
328                                         IPackageFragmentRoot oldRoot = (IPackageFragmentRoot)  oldRoots.get(oldResolvedPath[i].getPath());
329                                         if (oldRoot != null) { // use old root if any (could be none if entry wasn't bound)
330                                                 pkgFragmentRoots = new IPackageFragmentRoot[] { oldRoot };
331                                         }
332                                 }
333                                 if (pkgFragmentRoots == null) {
334                                         try {
335                                                 ObjectVector accumulatedRoots = new ObjectVector();
336                                                 HashSet rootIDs = new HashSet(5);
337                                                 rootIDs.add(project.rootID());
338                                                 project.computePackageFragmentRoots(
339                                                         oldResolvedPath[i], 
340                                                         accumulatedRoots, 
341                                                         rootIDs,
342                                                         true, // inside original project
343                                                         false, // don't check existency
344                                                         false); // don't retrieve exported roots
345                                                 pkgFragmentRoots = new IPackageFragmentRoot[accumulatedRoots.size()];
346                                                 accumulatedRoots.copyInto(pkgFragmentRoots);
347                                         } catch (JavaModelException e) {
348                                                 pkgFragmentRoots =  new IPackageFragmentRoot[] {};
349                                         }
350                                 }
351                                 addClasspathDeltas(pkgFragmentRoots, IJavaElementDelta.F_REMOVED_FROM_CLASSPATH, delta);
352                                 
353                                 int changeKind = oldResolvedPath[i].getEntryKind();
354                                 needToUpdateDependents |= (changeKind == IClasspathEntry.CPE_SOURCE) || oldResolvedPath[i].isExported();
355
356                                 // Remove the .java files from the index for a source folder
357                                 // For a lib folder or a .jar file, remove the corresponding index if not shared.
358 //                              if (indexManager != null) {
359 //                                      IClasspathEntry oldEntry = oldResolvedPath[i];
360 //                                      final IPath path = oldEntry.getPath();
361 //                                      switch (changeKind) {
362 //                                              case IClasspathEntry.CPE_SOURCE:
363 //                                                      final char[][] inclusionPatterns = null; //((ClasspathEntry)oldEntry).fullInclusionPatternChars();
364 //                                                      final char[][] exclusionPatterns = ((ClasspathEntry)oldEntry).fullExclusionPatternChars();
365 //                                                      postAction(new IPostAction() {
366 //                                                              public String getID() {
367 //                                                                      return path.toString();
368 //                                                              }
369 //                                                              public void run() /* throws JavaModelException */ {
370 //                                                                      indexManager.removeSourceFolderFromIndex(project, path, inclusionPatterns, exclusionPatterns);
371 //                                                              }
372 //                                                      }, 
373 //                                                      REMOVEALL_APPEND);
374 //                                                      break;
375 //                                              case IClasspathEntry.CPE_LIBRARY:
376 //                                                      final DeltaProcessingState deltaState = manager.deltaState;
377 //                                                      postAction(new IPostAction() {
378 //                                                              public String getID() {
379 //                                                                      return path.toString();
380 //                                                              }
381 //                                                              public void run() /* throws JavaModelException */ {
382 //                                                                      if (deltaState.otherRoots.get(path) == null) { // if root was not shared
383 //                                                                              indexManager.discardJobs(path.toString());
384 //                                                                              indexManager.removeIndex(path);
385 //                                                                              // TODO (kent) we could just remove the in-memory index and have the indexing check for timestamps
386 //                                                                      }
387 //                                                              }
388 //                                                      }, 
389 //                                                      REMOVEALL_APPEND);
390 //                                                      break;
391 //                                      }               
392 //                              }
393                                 hasDelta = true;
394
395                         } else {
396                                 // do not notify remote project changes
397                                 if (oldResolvedPath[i].getEntryKind() == IClasspathEntry.CPE_PROJECT){
398                                         this.needCycleCheck |= (oldResolvedPath[i].isExported() != newResolvedPath[index].isExported());
399                                         continue; 
400                                 }                               
401                                 needToUpdateDependents |= (oldResolvedPath[i].isExported() != newResolvedPath[index].isExported());
402                                 if (index != i) { //reordering of the classpath
403                                                 addClasspathDeltas(
404                                                         project.computePackageFragmentRoots(oldResolvedPath[i]),
405                                                         IJavaElementDelta.F_REORDER,
406                                                         delta);
407                                                 int changeKind = oldResolvedPath[i].getEntryKind();
408                                                 needToUpdateDependents |= (changeKind == IClasspathEntry.CPE_SOURCE);
409                 
410                                                 hasDelta = true;
411                                 }
412                                 
413                                 // check source attachment
414                                 IPath newSourcePath = newResolvedPath[index].getSourceAttachmentPath();
415                                 int sourceAttachmentFlags = 
416                                         this.getSourceAttachmentDeltaFlag(
417                                                 oldResolvedPath[i].getSourceAttachmentPath(),
418                                                 newSourcePath);
419                                 IPath oldRootPath = oldResolvedPath[i].getSourceAttachmentRootPath();
420                                 IPath newRootPath = newResolvedPath[index].getSourceAttachmentRootPath();
421                                 int sourceAttachmentRootFlags = getSourceAttachmentDeltaFlag(oldRootPath, newRootPath);
422                                 int flags = sourceAttachmentFlags | sourceAttachmentRootFlags;
423                                 if (flags != 0) {
424                                         addClasspathDeltas(project.computePackageFragmentRoots(oldResolvedPath[i]), flags, delta);
425                                         hasDelta = true;
426                                 } else {
427                                         if (oldRootPath == null && newRootPath == null) {
428                                                 // if source path is specified and no root path, it needs to be recomputed dynamically
429                                                 // force detach source on jar package fragment roots (source will be lazily computed when needed)
430                                                 IPackageFragmentRoot[] computedRoots = project.computePackageFragmentRoots(oldResolvedPath[i]);
431                                                 for (int j = 0; j < computedRoots.length; j++) {
432                                                         IPackageFragmentRoot root = computedRoots[j];
433                                                         // force detach source on jar package fragment roots (source will be lazily computed when needed)
434                                                         try {
435                                                                 root.close();
436                                                         } catch (JavaModelException e) {
437                                                                 // ignore
438                                                         }
439                                                         ((PackageFragmentRoot) root).setSourceAttachmentProperty(null);// loose info - will be recomputed
440                                                 }
441                                         }
442                                 }
443                         }
444                 }
445
446                 for (int i = 0; i < newLength; i++) {
447
448                         int index = classpathContains(oldResolvedPath, newResolvedPath[i]);
449                         if (index == -1) {
450                                 // do not notify remote project changes
451                                 if (newResolvedPath[i].getEntryKind() == IClasspathEntry.CPE_PROJECT){
452                                         needToUpdateDependents = true;
453                                         this.needCycleCheck = true;
454                                         continue; 
455                                 }
456                                 addClasspathDeltas(
457                                         project.computePackageFragmentRoots(newResolvedPath[i]),
458                                         IJavaElementDelta.F_ADDED_TO_CLASSPATH,
459                                         delta);
460                                 int changeKind = newResolvedPath[i].getEntryKind();
461                                 
462                                 // Request indexing
463 //                              if (indexManager != null) {
464 //                                      switch (changeKind) {
465 //                                              case IClasspathEntry.CPE_LIBRARY:
466 //                                                      boolean pathHasChanged = true;
467 //                                                      final IPath newPath = newResolvedPath[i].getPath();
468 //                                                      for (int j = 0; j < oldLength; j++) {
469 //                                                              IClasspathEntry oldEntry = oldResolvedPath[j];
470 //                                                              if (oldEntry.getPath().equals(newPath)) {
471 //                                                                      pathHasChanged = false;
472 //                                                                      break;
473 //                                                              }
474 //                                                      }
475 //                                                      if (pathHasChanged) {
476 //                                                              postAction(new IPostAction() {
477 //                                                                      public String getID() {
478 //                                                                              return newPath.toString();
479 //                                                                      }
480 //                                                                      public void run() /* throws JavaModelException */ {
481 //                                                                              indexManager.indexLibrary(newPath, project.getProject());
482 //                                                                      }
483 //                                                              }, 
484 //                                                              REMOVEALL_APPEND);
485 //                                                      }
486 //                                                      break;
487 //                                              case IClasspathEntry.CPE_SOURCE:
488 //                                                      IClasspathEntry entry = newResolvedPath[i];
489 //                                                      final IPath path = entry.getPath();
490 //                                                      final char[][] inclusionPatterns = null; //((ClasspathEntry)entry).fullInclusionPatternChars();
491 //                                                      final char[][] exclusionPatterns = ((ClasspathEntry)entry).fullExclusionPatternChars();
492 //                                                      postAction(new IPostAction() {
493 //                                                              public String getID() {
494 //                                                                      return path.toString();
495 //                                                              }
496 //                                                              public void run() /* throws JavaModelException */ {
497 //                                                                      indexManager.indexSourceFolder(project, path, inclusionPatterns, exclusionPatterns);
498 //                                                              }
499 //                                                      }, 
500 //                                                      APPEND); // append so that a removeSourceFolder action is not removed
501 //                                                      break;
502 //                                      }
503 //                              }
504                                 
505                                 needToUpdateDependents |= (changeKind == IClasspathEntry.CPE_SOURCE) || newResolvedPath[i].isExported();
506                                 hasDelta = true;
507
508                         } // classpath reordering has already been generated in previous loop
509                 }
510
511                 if (hasDelta) {
512                         this.addDelta(delta);
513                 } else {
514                         this.identicalRoots = true;
515                 }
516                 if (needToUpdateDependents){
517                         updateAffectedProjects(project.getProject().getFullPath());
518                 }
519         }
520         protected void saveClasspathIfNecessary() throws JavaModelException {
521                 
522                 if (!this.canChangeResources || !this.needSave) return;
523                                 
524                 IClasspathEntry[] classpathForSave;
525                 if (this.newRawPath == ReuseClasspath || this.newRawPath == UpdateClasspath){
526                         classpathForSave = project.getRawClasspath();
527                 } else {
528                         classpathForSave = this.newRawPath;
529                 }
530                 IPath outputLocationForSave;
531                 if (this.newOutputLocation == ReuseOutputLocation){
532                         outputLocationForSave = project.getOutputLocation();
533                 } else {
534                         outputLocationForSave = this.newOutputLocation;
535                 }
536                 // if read-only .classpath, then the classpath setting will never been performed completely
537                 if (project.saveClasspath(classpathForSave, outputLocationForSave)) {
538                         this.classpathWasSaved = true;
539                         this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); 
540                 }
541         }
542         protected JavaProject getProject() {
543                 return ((JavaProject) getElementsToProcess()[0]);
544         }
545
546         /*
547          * Returns the source attachment flag for the delta between the 2 give source paths.
548          * Returns either F_SOURCEATTACHED, F_SOURCEDETACHED, F_SOURCEATTACHED | F_SOURCEDETACHED
549          * or 0 if there is no difference.
550          */
551         private int getSourceAttachmentDeltaFlag(IPath oldPath, IPath newPath, IPath sourcePath) {
552                 if (oldPath == null) {
553                         if (newPath != null) {
554                                 return IJavaElementDelta.F_SOURCEATTACHED;
555                         } else {
556                                 if (sourcePath != null) {
557                                         // if source path is specified and no root path, it needs to be recomputed dynamically
558                                         return IJavaElementDelta.F_SOURCEATTACHED | IJavaElementDelta.F_SOURCEDETACHED;
559                                 } else {
560                                         return 0;
561                                 }
562                         }
563                 } else if (newPath == null) {
564                         return IJavaElementDelta.F_SOURCEDETACHED;
565                 } else if (!oldPath.equals(newPath)) {
566                         return IJavaElementDelta.F_SOURCEATTACHED | IJavaElementDelta.F_SOURCEDETACHED;
567                 } else {
568                         return 0;
569                 }
570         }
571
572         /*
573          * Returns the source attachment flag for the delta between the 2 give source paths.
574          * Returns either F_SOURCEATTACHED, F_SOURCEDETACHED, F_SOURCEATTACHED | F_SOURCEDETACHED
575          * or 0 if there is no difference.
576          */
577         private int getSourceAttachmentDeltaFlag(IPath oldPath, IPath newPath) {
578                 if (oldPath == null) {
579                         if (newPath != null) {
580                                 return IJavaElementDelta.F_SOURCEATTACHED;
581                         } else {
582                                 return 0;
583                         }
584                 } else if (newPath == null) {
585                         return IJavaElementDelta.F_SOURCEDETACHED;
586                 } else if (!oldPath.equals(newPath)) {
587                         return IJavaElementDelta.F_SOURCEATTACHED | IJavaElementDelta.F_SOURCEDETACHED;
588                 } else {
589                         return 0;
590                 }
591         }
592         
593         /**
594          * Returns <code>true</code> if this operation performs no resource modifications,
595          * otherwise <code>false</code>. Subclasses must override.
596          */
597         public boolean isReadOnly() {
598                 return !this.canChangeResources;
599         }
600
601 //      protected void saveClasspathIfNecessary() throws JavaModelException {
602 //              
603 //              if (!this.canChangeResources || !this.needSave) return;
604 //                              
605 //              IClasspathEntry[] classpathForSave;
606 //              JavaProject project = getProject();
607 //              if (this.newRawPath == ReuseClasspath || this.newRawPath == UpdateClasspath){
608 //                      classpathForSave = project.getRawClasspath();
609 //              } else {
610 //                      classpathForSave = this.newRawPath;
611 //              }
612 //              IPath outputLocationForSave;
613 //              if (this.newOutputLocation == ReuseOutputLocation){
614 //                      outputLocationForSave = project.getOutputLocation();
615 //              } else {
616 //                      outputLocationForSave = this.newOutputLocation;
617 //              }
618 //              // if read-only .classpath, then the classpath setting will never been performed completely
619 //              if (project.saveClasspath(classpathForSave, outputLocationForSave)) {
620 //                      this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); 
621 //              }
622 //      }
623         
624         public String toString(){
625                 StringBuffer buffer = new StringBuffer(20);
626                 buffer.append("SetClasspathOperation\n"); //$NON-NLS-1$
627                 buffer.append(" - classpath : "); //$NON-NLS-1$
628                 if (this.newRawPath == ReuseClasspath){
629                         buffer.append("<Reuse Existing Classpath>"); //$NON-NLS-1$
630                 } else {
631                         buffer.append("{"); //$NON-NLS-1$
632                         for (int i = 0; i < this.newRawPath.length; i++) {
633                                 if (i > 0) buffer.append(","); //$NON-NLS-1$
634                                 IClasspathEntry element = this.newRawPath[i];
635                                 buffer.append(" ").append(element.toString()); //$NON-NLS-1$
636                         }
637                 }
638                 buffer.append("\n - output location : ");  //$NON-NLS-1$
639                 if (this.newOutputLocation == ReuseOutputLocation){
640                         buffer.append("<Reuse Existing Output Location>"); //$NON-NLS-1$
641                 } else {
642                         buffer.append(this.newOutputLocation.toString()); //$NON-NLS-1$
643                 }
644                 return buffer.toString();
645         }
646
647 //      private void updateClasspath() throws JavaModelException {
648 //
649 //              JavaProject project = ((JavaProject) getElementsToProcess()[0]);
650 //
651 //              beginTask(Util.bind("classpath.settingProgress", project.getElementName()), 2); //$NON-NLS-1$
652 //
653 //              // SIDE-EFFECT: from thereon, the classpath got modified
654 //              project.setRawClasspath0(this.newRawPath);
655 //
656 //              // resolve new path (asking for marker creation if problems)
657 //              if (this.newResolvedPath == null) {
658 //                      this.newResolvedPath = project.getResolvedClasspath(true, this.canChangeResources);
659 //              }
660 //              
661 ////            if (this.oldResolvedPath != null) {
662 ////                    generateClasspathChangeDeltas(
663 ////                            this.oldResolvedPath,
664 ////                            this.newResolvedPath,
665 ////                            project);
666 ////            } else {
667 //                      this.needCycleCheck = true;
668 //                      updateAffectedProjects(project.getProject().getFullPath());
669 ////            }
670 //              
671 //              updateCycleMarkersIfNecessary(newResolvedPath);
672 //      }
673         private void updateClasspath() throws JavaModelException {
674
675                 beginTask(Util.bind("classpath.settingProgress", project.getElementName()), 2); //$NON-NLS-1$
676
677                 // SIDE-EFFECT: from thereon, the classpath got modified
678                 project.getPerProjectInfo().updateClasspathInformation(this.newRawPath);
679
680                 // resolve new path (asking for marker creation if problems)
681                 if (this.newResolvedPath == null) {
682                         this.newResolvedPath = project.getResolvedClasspath(true, this.canChangeResources, false/*don't returnResolutionInProgress*/);
683                 }
684                 
685                 if (this.oldResolvedPath != null) {
686                         generateClasspathChangeDeltas();
687                 } else {
688                         this.needCycleCheck = true;
689                         updateAffectedProjects(project.getProject().getFullPath());
690                 }
691                 
692                 updateCycleMarkersIfNecessary();
693         }
694
695         /**
696          * Update projects which are affected by this classpath change:
697          * those which refers to the current project as source
698          */
699         protected void updateAffectedProjects(IPath prerequisiteProjectPath) {
700
701                 try {
702                         IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel();
703                         IJavaProject originatingProject = getProject();
704                         IJavaProject[] projects = model.getJavaProjects();
705                         for (int i = 0, projectCount = projects.length; i < projectCount; i++) {
706                                 try {
707                                         JavaProject project = (JavaProject) projects[i];
708                                         if (project.equals(originatingProject)) continue; // skip itself
709                                         
710                                         // consider ALL dependents (even indirect ones), since they may need to
711                                         // flush their respective namelookup caches (all pkg fragment roots).
712
713                                         IClasspathEntry[] classpath = project.getExpandedClasspath(true);
714                                         for (int j = 0, entryCount = classpath.length; j < entryCount; j++) {
715                                                 IClasspathEntry entry = classpath[j];
716                                                 if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT
717                                                         && entry.getPath().equals(prerequisiteProjectPath)) {
718                                                         project.setRawClasspath(
719                                                                 UpdateClasspath, 
720                                                                 SetClasspathOperation.ReuseOutputLocation, 
721                                                                 this.progressMonitor, 
722                                                                 this.canChangeResources,  
723                                                                 project.getResolvedClasspath(true), 
724                                                                 false, // updating only - no validation
725                                                                 false); // updating only - no need to save
726                                                         break;
727                                                 }
728                                         }
729                                 } catch (JavaModelException e) {
730                                 }
731                         }
732                 } catch (JavaModelException e) {
733                 }
734                 
735         }
736
737         /**
738          * Update cycle markers
739          */
740         protected void updateCycleMarkersIfNecessary() {
741
742                 if (!this.needCycleCheck) return;
743                 if (!this.canChangeResources) return;
744                  
745                 if (!project.hasCycleMarker() && !project.hasClasspathCycle(newResolvedPath)){
746                         return;
747                 }
748         
749                 postAction(
750                         new IPostAction() {
751                                 public String getID() {
752                                         return "updateCycleMarkers";  //$NON-NLS-1$
753                                 }
754                                 public void run() throws JavaModelException {
755                                         JavaProject.updateAllCycleMarkers();
756                                 }
757                         },
758                         REMOVEALL_APPEND);
759         }
760 //      /**
761 //       * Update cycle markers
762 //       */
763 //      protected void updateCycleMarkersIfNecessary(IClasspathEntry[] newResolvedPath) {
764 //
765 //              if (!this.needCycleCheck) return;
766 //              if (!this.canChangeResources) return;
767 //               
768 //              try {
769 //                      JavaProject project = getProject();
770 //                      if (!project.hasCycleMarker() && !project.hasClasspathCycle(project.getResolvedClasspath(true))){
771 //                              return;
772 //                      }
773 //              
774 //                      postAction(
775 //                              new IPostAction() {
776 //                                      public String getID() {
777 //                                              return "updateCycleMarkers";  //$NON-NLS-1$
778 //                                      }
779 //                                      public void run() throws JavaModelException {
780 //                                              JavaProject.updateAllCycleMarkers();
781 //                                      }
782 //                              },
783 //                              REMOVEALL_APPEND);
784 //              } catch(JavaModelException e){
785 //              }
786 //      }
787
788         /**
789          * Sets the output location of the pre-specified project.
790          *
791          * <p>This can cause changes in package fragments, in case either  the
792          * old or new output location folder are considered as a package fragment.
793          */
794         protected void updateOutputLocation() throws JavaModelException {
795                 
796                 JavaProject project= ((JavaProject) getElementsToProcess()[0]);
797
798                 beginTask(Util.bind("classpath.settingOutputLocationProgress", project.getElementName()), 2); //$NON-NLS-1$
799                 
800                 IPath oldLocation= project.getOutputLocation();
801         
802                 // see if this will cause any package fragments to be added
803                 boolean deltaToFire= false;
804                 JavaElementDelta delta = newJavaElementDelta();
805 //              ArrayList added= determineAffectedPackageFragments(oldLocation);
806 //              Iterator iter = added.iterator();
807 //              while (iter.hasNext()){
808 //                      IPackageFragment frag= (IPackageFragment)iter.next();
809 //                      ((IPackageFragmentRoot)frag.getParent()).close();
810 //                      if (!ProjectPrefUtil.isExcluded(frag)) {
811 //                              delta.added(frag);
812 //                              deltaToFire = true;
813 //                      }
814 //              }
815         
816                 // see if this will cause any package fragments to be removed
817 //              ArrayList removed= determineAffectedPackageFragments(this.newOutputLocation);
818 //              iter = removed.iterator();
819 //              while (iter.hasNext()){
820 //                      IPackageFragment frag= (IPackageFragment)iter.next();
821 //                      ((IPackageFragmentRoot)frag.getParent()).close(); 
822 //                      if (!ProjectPrefUtil.isExcluded(frag)) {
823 //                              delta.removed(frag);
824 //                              deltaToFire = true;
825 //                      }
826 //              }
827
828                 JavaModelManager.PerProjectInfo perProjectInfo = JavaModelManager.getJavaModelManager().getPerProjectInfoCheckExistence(project.getProject());
829                 synchronized (perProjectInfo) {
830                         perProjectInfo.outputLocation = this.newOutputLocation;
831                 }
832                                 
833                 if (deltaToFire) {
834                         addDelta(delta);        
835                 }
836                 worked(1);
837         }
838         
839         /**
840          * Update projects references so that the build order is consistent with the classpath
841          */
842         protected void updateProjectReferencesIfNecessary() throws JavaModelException {
843                 
844                 if (!this.canChangeResources) return;
845                 if (this.newRawPath == ReuseClasspath || this.newRawPath == UpdateClasspath) return;
846         
847                 JavaProject jproject = getProject();
848                 String[] oldRequired = jproject.projectPrerequisites(this.oldResolvedPath);
849
850                 if (this.newResolvedPath == null) {
851                         this.newResolvedPath = jproject.getResolvedClasspath(this.newRawPath, null, true, this.needValidation, null /*no reverse map*/);
852                 }
853                 String[] newRequired = jproject.projectPrerequisites(this.newResolvedPath);
854         
855                 try {           
856                         IProject project = jproject.getProject();
857                         IProjectDescription description = project.getDescription();
858                          
859                         IProject[] projectReferences = description.getReferencedProjects();
860                         
861                         HashSet oldReferences = new HashSet(projectReferences.length);
862                         for (int i = 0; i < projectReferences.length; i++){
863                                 String projectName = projectReferences[i].getName();
864                                 oldReferences.add(projectName);
865                         }
866                         HashSet newReferences = (HashSet)oldReferences.clone();
867         
868                         for (int i = 0; i < oldRequired.length; i++){
869                                 String projectName = oldRequired[i];
870                                 newReferences.remove(projectName);
871                         }
872                         for (int i = 0; i < newRequired.length; i++){
873                                 String projectName = newRequired[i];
874                                 newReferences.add(projectName);
875                         }
876         
877                         Iterator iter;
878                         int newSize = newReferences.size();
879                         
880                         checkIdentity: {
881                                 if (oldReferences.size() == newSize){
882                                         iter = newReferences.iterator();
883                                         while (iter.hasNext()){
884                                                 if (!oldReferences.contains(iter.next())){
885                                                         break checkIdentity;
886                                                 }
887                                         }
888                                         return;
889                                 }
890                         }
891                         String[] requiredProjectNames = new String[newSize];
892                         int index = 0;
893                         iter = newReferences.iterator();
894                         while (iter.hasNext()){
895                                 requiredProjectNames[index++] = (String)iter.next();
896                         }
897                         Util.sort(requiredProjectNames); // ensure that if changed, the order is consistent
898                         
899                         IProject[] requiredProjectArray = new IProject[newSize];
900                         IWorkspaceRoot wksRoot = project.getWorkspace().getRoot();
901                         for (int i = 0; i < newSize; i++){
902                                 requiredProjectArray[i] = wksRoot.getProject(requiredProjectNames[i]);
903                         }
904         
905                         description.setReferencedProjects(requiredProjectArray);
906                         project.setDescription(description, this.progressMonitor);
907         
908                 } catch(CoreException e){
909                         throw new JavaModelException(e);
910                 }
911         }
912
913         public IJavaModelStatus verify() {
914
915                 IJavaModelStatus status = super.verify();
916                 if (!status.isOK()) {
917                         return status;
918                 }
919
920                 if (needValidation) {
921                         IJavaProject project = (IJavaProject) getElementToProcess();
922                         // retrieve classpath 
923                         IClasspathEntry[] entries = this.newRawPath;
924                         if (entries == ReuseClasspath){
925                                 try {
926                                         entries = project.getRawClasspath();                    
927                                 } catch (JavaModelException e) {
928                                         return e.getJavaModelStatus();
929                                 }
930                         }               
931                         // retrieve output location
932                         IPath outputLocation = this.newOutputLocation;
933                         if (outputLocation == ReuseOutputLocation){
934                                 try {
935                                         outputLocation = project.getOutputLocation();
936                                 } catch (JavaModelException e) {
937                                         return e.getJavaModelStatus();
938                                 }
939                         }
940                                         
941                         // perform validation
942 //                      return JavaConventions.validateClasspath(
943 //                              project,
944 //                              entries,
945 //                              outputLocation);
946                 }
947                 
948                 return JavaModelStatus.VERIFIED_OK;
949         }
950 }