new version with WorkingCopy Management
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / builder / PHPBuilder.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.builder;
12
13 import java.io.DataInputStream;
14 import java.io.DataOutputStream;
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.Date;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.Map;
21
22 import net.sourceforge.phpdt.core.IClasspathEntry;
23 import net.sourceforge.phpdt.core.IJavaModelMarker;
24 import net.sourceforge.phpdt.core.JavaModelException;
25 import net.sourceforge.phpdt.core.compiler.CharOperation;
26 import net.sourceforge.phpdt.internal.core.JavaModel;
27 import net.sourceforge.phpdt.internal.core.JavaModelManager;
28 import net.sourceforge.phpdt.internal.core.JavaProject;
29 import net.sourceforge.phpdt.internal.core.Util;
30 import net.sourceforge.phpdt.internal.core.util.SimpleLookupTable;
31 import net.sourceforge.phpeclipse.PHPCore;
32
33 import org.eclipse.core.resources.IMarker;
34 import org.eclipse.core.resources.IProject;
35 import org.eclipse.core.resources.IResource;
36 import org.eclipse.core.resources.IResourceChangeEvent;
37 import org.eclipse.core.resources.IResourceDelta;
38 import org.eclipse.core.resources.IWorkspaceRoot;
39 import org.eclipse.core.resources.IncrementalProjectBuilder;
40 import org.eclipse.core.runtime.CoreException;
41 import org.eclipse.core.runtime.IPath;
42 import org.eclipse.core.runtime.IProgressMonitor;
43
44 public class PHPBuilder extends IncrementalProjectBuilder {
45
46   IProject currentProject;
47   JavaProject javaProject;
48   IWorkspaceRoot workspaceRoot;
49   NameEnvironment nameEnvironment;
50   SimpleLookupTable binaryLocationsPerProject; // maps a project to its binary resources (output folders, class folders, zip/jar files)
51   State lastState;
52   BuildNotifier notifier;
53   char[][] extraResourceFileFilters;
54   String[] extraResourceFolderFilters;
55
56   public static final String CLASS_EXTENSION = "class"; //$NON-NLS-1$
57
58   public static boolean DEBUG = true;
59
60   /**
61    * A list of project names that have been built.
62    * This list is used to reset the JavaModel.existingExternalFiles cache when a build cycle begins
63    * so that deleted external jars are discovered.
64    */
65   static ArrayList builtProjects = null;
66
67   public static IMarker[] getProblemsFor(IResource resource) {
68     try {
69       if (resource != null && resource.exists())
70         return resource.findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
71     } catch (CoreException e) {
72     } // assume there are no problems
73     return new IMarker[0];
74   }
75
76   public static IMarker[] getTasksFor(IResource resource) {
77     try {
78       if (resource != null && resource.exists())
79         return resource.findMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
80     } catch (CoreException e) {
81     } // assume there are no tasks
82     return new IMarker[0];
83   }
84
85   public static void finishedBuilding(IResourceChangeEvent event) {
86     BuildNotifier.resetProblemCounters();
87   }
88
89   public static void removeProblemsFor(IResource resource) {
90     try {
91       if (resource != null && resource.exists())
92         resource.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
93     } catch (CoreException e) {
94     } // assume there were no problems
95   }
96
97   public static void removeTasksFor(IResource resource) {
98     try {
99       if (resource != null && resource.exists())
100         resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
101     } catch (CoreException e) {
102     } // assume there were no problems
103   }
104
105   public static void removeProblemsAndTasksFor(IResource resource) {
106     try {
107       if (resource != null && resource.exists()) {
108         resource.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
109         resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
110       }
111     } catch (CoreException e) {
112     } // assume there were no problems
113   }
114
115   public static State readState(IProject project, DataInputStream in) throws IOException {
116     return State.read(project, in);
117   }
118
119   public static void writeState(Object state, DataOutputStream out) throws IOException {
120     ((State) state).write(out);
121   }
122
123   public PHPBuilder() {
124   }
125
126   protected IProject[] build(int kind, Map ignored, IProgressMonitor monitor) throws CoreException {
127     this.currentProject = getProject();
128     if (currentProject == null || !currentProject.isAccessible())
129       return new IProject[0];
130
131     if (DEBUG)
132       System.out.println("\nStarting build of " + currentProject.getName() //$NON-NLS-1$
133       +" @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$
134     this.notifier = new BuildNotifier(monitor, currentProject);
135     notifier.begin();
136     boolean ok = false;
137     try {
138       notifier.checkCancel();
139       initializeBuilder();
140
141       if (isWorthBuilding()) {
142         if (kind == FULL_BUILD) {
143           buildAll();
144         } else {
145           if ((this.lastState = getLastState(currentProject)) == null) {
146             if (DEBUG)
147               System.out.println("Performing full build since last saved state was not found"); //$NON-NLS-1$
148             buildAll();
149           } else if (hasClasspathChanged()) {
150             // if the output location changes, do not delete the binary files from old location
151             // the user may be trying something
152             buildAll();
153           } else if (nameEnvironment.sourceLocations.length > 0) {
154             // if there is no source to compile & no classpath changes then we are done
155             SimpleLookupTable deltas = findDeltas();
156             if (deltas == null)
157               buildAll();
158             else if (deltas.elementSize > 0)
159               buildDeltas(deltas);
160             else if (DEBUG)
161               System.out.println("Nothing to build since deltas were empty"); //$NON-NLS-1$
162           } else {
163             if (hasStructuralDelta()) { // double check that a jar file didn't get replaced in a binary project
164               buildAll();
165             } else {
166               if (DEBUG)
167                 System.out.println("Nothing to build since there are no source folders and no deltas"); //$NON-NLS-1$
168               lastState.tagAsNoopBuild();
169             }
170           }
171         }
172         ok = true;
173       }
174     } catch (CoreException e) {
175       Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
176       IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
177       marker.setAttribute(IMarker.MESSAGE, Util.bind("build.inconsistentProject", e.getLocalizedMessage())); //$NON-NLS-1$
178       marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
179     } catch (ImageBuilderInternalException e) {
180       Util.log(e.getThrowable(), "JavaBuilder handling ImageBuilderInternalException"); //$NON-NLS-1$
181       IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
182       marker.setAttribute(IMarker.MESSAGE, Util.bind("build.inconsistentProject", e.coreException.getLocalizedMessage())); //$NON-NLS-1$
183       marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
184     } catch (MissingClassFileException e) {
185       // do not log this exception since its thrown to handle aborted compiles because of missing class files
186       if (DEBUG)
187         System.out.println(Util.bind("build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$
188       IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
189       marker.setAttribute(IMarker.MESSAGE, Util.bind("build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$
190       marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
191     } catch (MissingSourceFileException e) {
192       // do not log this exception since its thrown to handle aborted compiles because of missing source files
193       if (DEBUG)
194         System.out.println(Util.bind("build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$
195       removeProblemsAndTasksFor(currentProject); // make this the only problem for this project
196       IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
197       marker.setAttribute(IMarker.MESSAGE, Util.bind("build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$
198       marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
199     } finally {
200       if (!ok)
201         // If the build failed, clear the previously built state, forcing a full build next time.
202         clearLastState();
203       notifier.done();
204       cleanup();
205     }
206     IProject[] requiredProjects = getRequiredProjects(true);
207     if (DEBUG)
208       System.out.println("Finished build of " + currentProject.getName() //$NON-NLS-1$
209       +" @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$
210     return requiredProjects;
211   }
212
213   private void buildAll() {
214     notifier.checkCancel();
215     notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$
216     if (DEBUG && lastState != null)
217       System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$
218     clearLastState();
219     BatchImageBuilder imageBuilder = new BatchImageBuilder(this);
220     imageBuilder.build();
221     recordNewState(imageBuilder.newState);
222   }
223
224   private void buildDeltas(SimpleLookupTable deltas) {
225     notifier.checkCancel();
226     notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$
227     if (DEBUG && lastState != null)
228       System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$
229     clearLastState(); // clear the previously built state so if the build fails, a full build will occur next time
230     IncrementalImageBuilder imageBuilder = new IncrementalImageBuilder(this);
231     if (imageBuilder.build(deltas))
232       recordNewState(imageBuilder.newState);
233     else
234       buildAll();
235   }
236
237   private void cleanup() {
238     this.nameEnvironment = null;
239     this.binaryLocationsPerProject = null;
240     this.lastState = null;
241     this.notifier = null;
242     this.extraResourceFileFilters = null;
243     this.extraResourceFolderFilters = null;
244   }
245
246   private void clearLastState() {
247     JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, null);
248   }
249
250   boolean filterExtraResource(IResource resource) {
251     if (extraResourceFileFilters != null) {
252       char[] name = resource.getName().toCharArray();
253       for (int i = 0, l = extraResourceFileFilters.length; i < l; i++)
254         if (CharOperation.match(extraResourceFileFilters[i], name, true))
255           return true;
256     }
257     if (extraResourceFolderFilters != null) {
258       IPath path = resource.getProjectRelativePath();
259       String pathName = path.toString();
260       int count = path.segmentCount();
261       if (resource.getType() == IResource.FILE)
262         count--;
263       for (int i = 0, l = extraResourceFolderFilters.length; i < l; i++)
264         if (pathName.indexOf(extraResourceFolderFilters[i]) != -1)
265           for (int j = 0; j < count; j++)
266             if (extraResourceFolderFilters[i].equals(path.segment(j)))
267               return true;
268     }
269     return false;
270   }
271
272   private SimpleLookupTable findDeltas() {
273     notifier.subTask(Util.bind("build.readingDelta", currentProject.getName())); //$NON-NLS-1$
274     IResourceDelta delta = getDelta(currentProject);
275     SimpleLookupTable deltas = new SimpleLookupTable(3);
276     if (delta != null) {
277       if (delta.getKind() != IResourceDelta.NO_CHANGE) {
278         if (DEBUG)
279           System.out.println("Found source delta for: " + currentProject.getName()); //$NON-NLS-1$
280         deltas.put(currentProject, delta);
281       }
282     } else {
283       if (DEBUG)
284         System.out.println("Missing delta for: " + currentProject.getName()); //$NON-NLS-1$
285       notifier.subTask(""); //$NON-NLS-1$
286       return null;
287     }
288
289     Object[] keyTable = binaryLocationsPerProject.keyTable;
290     Object[] valueTable = binaryLocationsPerProject.valueTable;
291     nextProject : for (int i = 0, l = keyTable.length; i < l; i++) {
292       IProject p = (IProject) keyTable[i];
293       if (p != null && p != currentProject) {
294         State s = getLastState(p);
295         if (!lastState.wasStructurallyChanged(p, s)) { // see if we can skip its delta
296           if (s.wasNoopBuild())
297             continue nextProject; // project has no source folders and can be skipped
298           //                            ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) valueTable[i];
299           boolean canSkip = true;
300           //                            for (int j = 0, m = classFoldersAndJars.length; j < m; j++) {
301           //                                    if (classFoldersAndJars[j].isOutputFolder())
302           //                                            classFoldersAndJars[j] = null; // can ignore output folder since project was not structurally changed
303           //                                    else
304           //                                            canSkip = false;
305           //                            }
306           if (canSkip)
307             continue nextProject; // project has no structural changes in its output folders
308         }
309
310         notifier.subTask(Util.bind("build.readingDelta", p.getName())); //$NON-NLS-1$
311         delta = getDelta(p);
312         if (delta != null) {
313           if (delta.getKind() != IResourceDelta.NO_CHANGE) {
314             if (DEBUG)
315               System.out.println("Found binary delta for: " + p.getName()); //$NON-NLS-1$
316             deltas.put(p, delta);
317           }
318         } else {
319           if (DEBUG)
320             System.out.println("Missing delta for: " + p.getName()); //$NON-NLS-1$
321           notifier.subTask(""); //$NON-NLS-1$
322           return null;
323         }
324       }
325     }
326     notifier.subTask(""); //$NON-NLS-1$
327     return deltas;
328   }
329
330   private State getLastState(IProject project) {
331     return (State) JavaModelManager.getJavaModelManager().getLastBuiltState(project, notifier.monitor);
332   }
333
334   /* Return the list of projects for which it requires a resource delta. This builder's project
335   * is implicitly included and need not be specified. Builders must re-specify the list 
336   * of interesting projects every time they are run as this is not carried forward
337   * beyond the next build. Missing projects should be specified but will be ignored until
338   * they are added to the workspace.
339   */
340   private IProject[] getRequiredProjects(boolean includeBinaryPrerequisites) {
341     if (javaProject == null || workspaceRoot == null)
342       return new IProject[0];
343
344     ArrayList projects = new ArrayList();
345     try {
346       IClasspathEntry[] entries = javaProject.getExpandedClasspath(true);
347       for (int i = 0, l = entries.length; i < l; i++) {
348         IClasspathEntry entry = entries[i];
349         IPath path = entry.getPath();
350         IProject p = null;
351         switch (entry.getEntryKind()) {
352           case IClasspathEntry.CPE_PROJECT :
353             p = workspaceRoot.getProject(path.lastSegment()); // missing projects are considered too
354             break;
355           case IClasspathEntry.CPE_LIBRARY :
356             if (includeBinaryPrerequisites && path.segmentCount() > 1) {
357               // some binary resources on the class path can come from projects that are not included in the project references
358               IResource resource = workspaceRoot.findMember(path.segment(0));
359               if (resource instanceof IProject)
360                 p = (IProject) resource;
361             }
362         }
363         if (p != null && !projects.contains(p))
364           projects.add(p);
365       }
366     } catch (JavaModelException e) {
367       return new IProject[0];
368     }
369     IProject[] result = new IProject[projects.size()];
370     projects.toArray(result);
371     return result;
372   }
373
374   private boolean hasClasspathChanged() {
375     ClasspathMultiDirectory[] newSourceLocations = nameEnvironment.sourceLocations;
376     ClasspathMultiDirectory[] oldSourceLocations = lastState.sourceLocations;
377     int newLength = newSourceLocations.length;
378     int oldLength = oldSourceLocations.length;
379     int n, o;
380     for (n = o = 0; n < newLength && o < oldLength; n++, o++) {
381       if (newSourceLocations[n].equals(oldSourceLocations[o]))
382         continue; // checks source & output folders
383       try {
384         if (newSourceLocations[n].sourceFolder.members().length == 0) { // added new empty source folder
385           o--;
386           continue;
387         }
388       } catch (CoreException ignore) {
389       }
390       if (DEBUG)
391         System.out.println(newSourceLocations[n] + " != " + oldSourceLocations[o]); //$NON-NLS-1$
392       return true;
393     }
394     while (n < newLength) {
395       try {
396         if (newSourceLocations[n].sourceFolder.members().length == 0) { // added new empty source folder
397           n++;
398           continue;
399         }
400       } catch (CoreException ignore) {
401       }
402       if (DEBUG)
403         System.out.println("Added non-empty source folder"); //$NON-NLS-1$
404       return true;
405     }
406     if (o < oldLength) {
407       if (DEBUG)
408         System.out.println("Removed source folder"); //$NON-NLS-1$
409       return true;
410     }
411
412     //  ClasspathLocation[] newBinaryLocations = nameEnvironment.binaryLocations;
413     //  ClasspathLocation[] oldBinaryLocations = lastState.binaryLocations;
414     //  newLength = newBinaryLocations.length;
415     //  oldLength = oldBinaryLocations.length;
416     //  for (n = o = 0; n < newLength && o < oldLength; n++, o++) {
417     //          if (newBinaryLocations[n].equals(oldBinaryLocations[o])) continue;
418     //          if (DEBUG)
419     //                  System.out.println(newBinaryLocations[n] + " != " + oldBinaryLocations[o]); //$NON-NLS-1$
420     //          return true;
421     //  }
422     //  if (n < newLength || o < oldLength) {
423     //          if (DEBUG)
424     //                  System.out.println("Number of binary folders/jar files has changed"); //$NON-NLS-1$
425     //          return true;
426     //  }
427     return false;
428   }
429
430   private boolean hasStructuralDelta() {
431     // handle case when currentProject has only .class file folders and/or jar files... no source/output folders
432     IResourceDelta delta = getDelta(currentProject);
433     if (delta != null && delta.getKind() != IResourceDelta.NO_CHANGE) {
434       //                ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) binaryLocationsPerProject.get(currentProject);
435       //                if (classFoldersAndJars != null) {
436       //                        for (int i = 0, l = classFoldersAndJars.length; i < l; i++) {
437       //                                ClasspathLocation classFolderOrJar = classFoldersAndJars[i]; // either a .class file folder or a zip/jar file
438       //                                if (classFolderOrJar != null) {
439       //                                        IPath p = classFolderOrJar.getProjectRelativePath();
440       //                                        if (p != null) {
441       //                                                IResourceDelta binaryDelta = delta.findMember(p);
442       //                                                if (binaryDelta != null && binaryDelta.getKind() != IResourceDelta.NO_CHANGE)
443       //                                                        return true;
444       //                                        }
445       //                                }
446       //                        }
447       //                }
448     }
449     return false;
450   }
451
452   private void initializeBuilder() throws CoreException {
453     this.javaProject = (JavaProject) PHPCore.create(currentProject);
454     this.workspaceRoot = currentProject.getWorkspace().getRoot();
455
456     // Flush the existing external files cache if this is the beginning of a build cycle
457     String projectName = currentProject.getName();
458     if (builtProjects == null || builtProjects.contains(projectName)) {
459       JavaModel.flushExternalFileCache();
460       builtProjects = new ArrayList();
461     }
462     builtProjects.add(projectName);
463
464     this.binaryLocationsPerProject = new SimpleLookupTable(3);
465     this.nameEnvironment = new NameEnvironment(workspaceRoot, javaProject, binaryLocationsPerProject);
466
467     String filterSequence = javaProject.getOption(PHPCore.CORE_JAVA_BUILD_RESOURCE_COPY_FILTER, true);
468     char[][] filters = filterSequence != null && filterSequence.length() > 0 ? CharOperation.splitAndTrimOn(',', filterSequence.toCharArray()) : null;
469     if (filters == null) {
470       this.extraResourceFileFilters = null;
471       this.extraResourceFolderFilters = null;
472     } else {
473       int fileCount = 0, folderCount = 0;
474       for (int i = 0, l = filters.length; i < l; i++) {
475         char[] f = filters[i];
476         if (f.length == 0)
477           continue;
478         if (f[f.length - 1] == '/')
479           folderCount++;
480         else
481           fileCount++;
482       }
483       this.extraResourceFileFilters = new char[fileCount][];
484       this.extraResourceFolderFilters = new String[folderCount];
485       for (int i = 0, l = filters.length; i < l; i++) {
486         char[] f = filters[i];
487         if (f.length == 0)
488           continue;
489         if (f[f.length - 1] == '/')
490           extraResourceFolderFilters[--folderCount] = new String(CharOperation.subarray(f, 0, f.length - 1));
491         else
492           extraResourceFileFilters[--fileCount] = f;
493       }
494     }
495   }
496
497   private boolean isClasspathBroken(IClasspathEntry[] classpath, IProject p) throws CoreException {
498     if (classpath == JavaProject.INVALID_CLASSPATH) // the .classpath file could not be read
499       return true;
500
501     IMarker[] markers = p.findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO);
502     for (int i = 0, l = markers.length; i < l; i++)
503       if (((Integer) markers[i].getAttribute(IMarker.SEVERITY)).intValue() == IMarker.SEVERITY_ERROR)
504         return true;
505     return false;
506   }
507
508   private boolean isWorthBuilding() throws CoreException {
509     boolean abortBuilds = PHPCore.ABORT.equals(javaProject.getOption(PHPCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, true));
510     if (!abortBuilds)
511       return true;
512
513     // Abort build only if there are classpath errors
514 //    if (isClasspathBroken(javaProject.getRawClasspath(), currentProject)) {
515 //      if (DEBUG)
516 //        System.out.println("Aborted build because project has classpath errors (incomplete or involved in cycle)"); //$NON-NLS-1$
517 //
518 //      JavaModelManager.getJavaModelManager().deltaProcessor.addForRefresh(javaProject);
519 //
520 //      removeProblemsAndTasksFor(currentProject); // remove all compilation problems
521 //
522 //      IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
523 //      marker.setAttribute(IMarker.MESSAGE, Util.bind("build.abortDueToClasspathProblems")); //$NON-NLS-1$
524 //      marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
525 //      return false;
526 //    }
527
528     // make sure all prereq projects have valid build states... only when aborting builds since projects in cycles do not have build states
529     // except for projects involved in a 'warning' cycle (see below)
530     IProject[] requiredProjects = getRequiredProjects(false);
531     next : for (int i = 0, l = requiredProjects.length; i < l; i++) {
532       IProject p = requiredProjects[i];
533       if (getLastState(p) == null) {
534         // The prereq project has no build state: if this prereq project has a 'warning' cycle marker then allow build (see bug id 23357)
535         JavaProject prereq = (JavaProject) PHPCore.create(p);
536         if (prereq.hasCycleMarker() && PHPCore.WARNING.equals(javaProject.getOption(PHPCore.CORE_CIRCULAR_CLASSPATH, true)))
537           continue;
538         if (DEBUG)
539           System.out.println("Aborted build because prereq project " + p.getName() //$NON-NLS-1$
540           +" was not built"); //$NON-NLS-1$
541
542         removeProblemsAndTasksFor(currentProject); // make this the only problem for this project
543         IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
544         marker.setAttribute(IMarker.MESSAGE, isClasspathBroken(prereq.getRawClasspath(), p) ? Util.bind("build.prereqProjectHasClasspathProblems", p.getName()) //$NON-NLS-1$
545         : Util.bind("build.prereqProjectMustBeRebuilt", p.getName())); //$NON-NLS-1$
546         marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
547         return false;
548       }
549     }
550     return true;
551   }
552
553   /*
554    * Instruct the build manager that this project is involved in a cycle and
555    * needs to propagate structural changes to the other projects in the cycle.
556    */
557   void mustPropagateStructuralChanges() {
558     HashSet cycleParticipants = new HashSet(3);
559     javaProject.updateCycleParticipants(null, new ArrayList(), cycleParticipants, workspaceRoot, new HashSet(3));
560     IPath currentPath = javaProject.getPath();
561     Iterator i = cycleParticipants.iterator();
562     while (i.hasNext()) {
563       IPath participantPath = (IPath) i.next();
564       if (participantPath != currentPath) {
565         IProject project = this.workspaceRoot.getProject(participantPath.segment(0));
566         if (hasBeenBuilt(project)) {
567           if (DEBUG)
568             System.out.println("Requesting another build iteration since cycle participant " + project.getName() //$NON-NLS-1$
569             +" has not yet seen some structural changes"); //$NON-NLS-1$
570           needRebuild();
571           return;
572         }
573       }
574     }
575   }
576
577   private void recordNewState(State state) {
578     Object[] keyTable = binaryLocationsPerProject.keyTable;
579     for (int i = 0, l = keyTable.length; i < l; i++) {
580       IProject prereqProject = (IProject) keyTable[i];
581       if (prereqProject != null && prereqProject != currentProject)
582         state.recordStructuralDependency(prereqProject, getLastState(prereqProject));
583     }
584
585     if (DEBUG)
586       System.out.println("Recording new state : " + state); //$NON-NLS-1$
587     // state.dump();
588     JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, state);
589   }
590
591   /**
592    * String representation for debugging purposes
593    */
594   public String toString() {
595     return currentProject == null ? "JavaBuilder for unknown project" //$NON-NLS-1$
596     : "JavaBuilder for " + currentProject.getName(); //$NON-NLS-1$
597   }
598 }