Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/core/internal/events/BuildManager.java


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
7    * 
8    * Contributors:
9    *     IBM Corporation - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.core.internal.events;
12  
13  import java.util.*;
14  import org.eclipse.core.internal.dtree.DeltaDataTree;
15  import org.eclipse.core.internal.resources.*;
16  import org.eclipse.core.internal.utils.Assert;
17  import org.eclipse.core.internal.utils.Policy;
18  import org.eclipse.core.internal.watson.ElementTree;
19  import org.eclipse.core.resources.*;
20  import org.eclipse.core.runtime.*;
21  import org.eclipse.core.runtime.jobs.ILock;
22  import org.osgi.framework.Bundle;
23  
24  public class BuildManager implements ICoreConstants, IManager, ILifecycleListener {
25  
26    private static final int TOTAL_BUILD_WORK = Policy.totalWork * 1000;
27  
28    /**
29     * Cache used to optimize the common case of an autobuild against
30     * a workspace where only a single project has changed (and hence
31     * only a single delta is interesting).
32     */
33    class DeltaCache {
34      private Object delta;
35      private ElementTree newTree;
36      private ElementTree oldTree;
37      private IPath projectPath;
38  
39      public void cache(IPath project, ElementTree oldTree, ElementTree newTree, Object delta) {
40        this.projectPath = project;
41        this.oldTree = oldTree;
42        this.newTree = newTree;
43        this.delta = delta;
44      }
45  
46      public void flush() {
47        this.projectPath = null;
48        this.oldTree = null;
49        this.newTree = null;
50        this.delta = null;
51      }
52  
53      /**
54       * Returns the cached resource delta for the given project and trees, or
55       * null if there is no matching delta in the cache.
56       */
57      public Object getDelta(IPath project, ElementTree oldTree, ElementTree newTree) {
58        if (delta == null)
59          return null;
60        boolean pathsEqual = projectPath == null ? project == null : projectPath.equals(project);
61        if (pathsEqual && this.oldTree == oldTree && this.newTree == newTree)
62          return delta;
63        return null;
64      }
65    }
66  
67    /**
68     * These builders are added to build tables in place of builders that couldn't be instantiated
69     */
70    class MissingBuilder extends IncrementalProjectBuilder {
71      private boolean hasBeenBuilt = false;
72      private String name;
73  
74      MissingBuilder(String name) {
75        this.name = name;
76      }
77  
78      /**
79       * Log an exception on the first build, and silently do nothing on subsequent builds.
80       */
81      protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
82        if (!hasBeenBuilt) {
83          hasBeenBuilt = true;
84          String msg = Policy.bind("events.skippingBuilder", new String[] {name, getProject().getName()}); //$NON-NLS-1$
85          IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, 1, msg, null);
86          ResourcesPlugin.getPlugin().getLog().log(status);
87        }
88        return null;
89      }
90    }
91  
92    //the job for performing background autobuild
93    protected final AutoBuildJob autoBuildJob;
94    protected boolean building = false;
95    protected final ArrayList builtProjects = new ArrayList();
96    protected InternalBuilder currentBuilder;
97    protected DeltaDataTree currentDelta;
98  
99    //the following four fields only apply for the lifetime of 
100   //a single builder invocation.
101   protected ElementTree currentTree;
102   /**
103    * Caches the IResourceDelta for a pair of trees
104    */
105   final protected DeltaCache deltaCache = new DeltaCache();
106   /**
107    * Caches the DeltaDataTree used to determine if a build is necessary
108    */
109   final protected DeltaCache deltaTreeCache = new DeltaCache();
110 
111   protected ElementTree lastBuiltTree;
112 
113   //used for the build cycle looping mechanism
114   protected boolean rebuildRequested = false;
115 
116   private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
117   
118   //used for debug/trace timing
119   private long timeStamp = -1;
120   protected Workspace workspace;
121   private ILock lock;
122 
123   public BuildManager(Workspace workspace, ILock workspaceLock) {
124     this.workspace = workspace;
125     this.autoBuildJob = new AutoBuildJob(workspace);
126     this.lock = workspaceLock;
127   }
128 
129   protected void basicBuild(int trigger, IncrementalProjectBuilder builder, Map args, MultiStatus status, IProgressMonitor monitor) {
130     try {
131       currentBuilder = builder;
132       //clear any old requests to forget built state
133       currentBuilder.clearForgetLastBuiltState();
134       // Figure out want kind of build is needed
135       boolean clean = trigger == IncrementalProjectBuilder.CLEAN_BUILD;
136       lastBuiltTree = currentBuilder.getLastBuiltTree();
137       // If no tree is available we have to do a full build
138       if (!clean && lastBuiltTree == null)
139         trigger = IncrementalProjectBuilder.FULL_BUILD;
140       boolean fullBuild = trigger == IncrementalProjectBuilder.FULL_BUILD;
141       // Grab a pointer to the current state before computing the delta
142       currentTree = fullBuild ? null : workspace.getElementTree();
143       int depth = -1;
144       try {
145         //short-circuit if none of the projects this builder cares about have changed.
146         if (!clean && !fullBuild && !needsBuild(currentBuilder))
147           return;
148         String name = currentBuilder.getLabel();
149         String message;
150         if (name != null)
151           message = Policy.bind("events.invoking.2", name, builder.getProject().getFullPath().toString()); //$NON-NLS-1$
152         else
153           message = Policy.bind("events.invoking.1", builder.getProject().getFullPath().toString()); //$NON-NLS-1$
154         monitor.subTask(message);
155         hookStartBuild(builder, trigger);
156         //release workspace lock while calling builders
157         depth = getWorkManager().beginUnprotected();
158         //do the build
159         Platform.run(getSafeRunnable(trigger, args, status, monitor));
160       } finally {
161         if (depth >= 0)
162           getWorkManager().endUnprotected(depth);
163         // Be sure to clean up after ourselves.
164         if (clean || currentBuilder.wasForgetStateRequested()) {
165           currentBuilder.setLastBuiltTree(null);
166         } else {
167           // remember the current state as the last built state.
168           ElementTree lastTree = workspace.getElementTree();
169           lastTree.immutable();
170           currentBuilder.setLastBuiltTree(lastTree);
171         }
172         hookEndBuild(builder);
173       }
174     } finally {
175       currentBuilder = null;
176       currentTree = null;
177       lastBuiltTree = null;
178       currentDelta = null;
179     }
180   }
181   /**
182    * We know the work manager is always available in the middle of
183    * a build.
184    */
185   private WorkManager getWorkManager() {
186     try {
187       return workspace.getWorkManager();
188     } catch (CoreException e) {
189       //cannot happen
190     }
191     //avoid compile error
192     return null;
193   }
194 
195   protected void basicBuild(IProject project, int trigger, ICommand[] commands, MultiStatus status, IProgressMonitor monitor) {
196     monitor = Policy.monitorFor(monitor);
197     try {
198       String message = Policy.bind("events.building.1", project.getFullPath().toString()); //$NON-NLS-1$
199       monitor.beginTask(message, Math.max(1, commands.length));
200       for (int i = 0; i < commands.length; i++) {
201         checkCanceled(trigger, monitor);
202         IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
203         BuildCommand command = (BuildCommand) commands[i];
204         basicBuild(project, trigger, command.getBuilderName(), command.getArguments(false), status, sub);
205       }
206     } finally {
207       monitor.done();
208     }
209   }
210 
211   protected void basicBuild(final IProject project, final int trigger, final MultiStatus status, final IProgressMonitor monitor) {
212     if (!project.isAccessible())
213       return;
214     final ICommand[] commands = ((Project) project).internalGetDescription().getBuildSpec(false);
215     if (commands.length == 0)
216       return;
217     ISafeRunnable code = new ISafeRunnable() {
218       public void handleException(Throwable e) {
219         if (e instanceof OperationCanceledException)
220           throw (OperationCanceledException) e;
221         // don't log the exception....it is already being logged in Workspace#run
222         // should never get here because the lower-level build code wrappers
223         // builder exceptions in core exceptions if required.
224         String message = e.getMessage();
225         if (message == null)
226           message = Policy.bind("events.unknown", e.getClass().getName(), currentBuilder.getClass().getName()); //$NON-NLS-1$
227         status.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e));
228       }
229 
230       public void run() throws Exception {
231         basicBuild(project, trigger, commands, status, monitor);
232       }
233     };
234     Platform.run(code);
235   }
236 
237   protected void basicBuild(IProject project, int trigger, String builderName, Map args, MultiStatus status, IProgressMonitor monitor) {
238     IncrementalProjectBuilder builder = null;
239     try {
240       builder = getBuilder(builderName, project, status);
241       if (!validateNature(builder, builderName)) {
242         //skip this builder and null its last built tree because it is invalid
243         //if the nature gets added or re-enabled a full build will be triggered
244         ((InternalBuilder) builder).setLastBuiltTree(null);
245         return;
246       }
247     } catch (CoreException e) {
248       status.add(e.getStatus());
249       return;
250     }
251     basicBuild(trigger, builder, args, status, monitor);
252   }
253 
254   /**
255    * Loop the workspace build until no more builders request a rebuild.
256    */
257   protected void basicBuildLoop(IProject[] ordered, IProject[] unordered, int trigger, MultiStatus status, IProgressMonitor monitor) {
258     int projectWork = ordered.length + unordered.length;
259     if (projectWork > 0)
260       projectWork = TOTAL_BUILD_WORK / projectWork;
261     int maxIterations = workspace.getDescription().getMaxBuildIterations();
262     if (maxIterations <= 0)
263       maxIterations = 1;
264     rebuildRequested = true;
265     for (int iter = 0; rebuildRequested && iter < maxIterations; iter++) {
266       rebuildRequested = false;
267       builtProjects.clear();
268       for (int i = 0; i < ordered.length; i++) {
269         if (ordered[i].isAccessible()) {
270           basicBuild(ordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
271           builtProjects.add(ordered[i]);
272         }
273       }
274       for (int i = 0; i < unordered.length; i++) {
275         if (unordered[i].isAccessible()) {
276           basicBuild(unordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
277           builtProjects.add(unordered[i]);
278         }
279       }
280       //subsequent builds should always be incremental
281       trigger = IncrementalProjectBuilder.INCREMENTAL_BUILD;
282     }
283   }
284 
285   public void build(int trigger, IProgressMonitor monitor) throws CoreException {
286     monitor = Policy.monitorFor(monitor);
287     try {
288       monitor.beginTask(ICoreConstants.MSG_EVENTS_BUILDING_0, TOTAL_BUILD_WORK);
289       if (!canRun(trigger))
290         return;
291       try {
292         building = true;
293         IProject[] ordered = workspace.getBuildOrder();
294         HashSet leftover = new HashSet(Arrays.asList(workspace.getRoot().getProjects()));
295         leftover.removeAll(Arrays.asList(ordered));
296         IProject[] unordered = (IProject[]) leftover.toArray(new IProject[leftover.size()]);
297         MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.BUILD_FAILED, ICoreConstants.MSG_EVENTS_ERRORS, null);
298 
299         basicBuildLoop(ordered, unordered, trigger, status, monitor);
300 
301         // if the status is not ok, throw an exception 
302         if (!status.isOK())
303           throw new ResourceException(status);
304       } finally {
305         cleanup(trigger);
306       }
307     } finally {
308       monitor.done();
309       if (trigger == IncrementalProjectBuilder.INCREMENTAL_BUILD || trigger == IncrementalProjectBuilder.FULL_BUILD)
310         autoBuildJob.avoidBuild();
311     }
312   }
313 
314   private void cleanup(int trigger) {
315     building = false;
316     builtProjects.clear();
317     deltaCache.flush();
318     deltaTreeCache.flush();
319     //ensure autobuild runs after a clean
320     if (trigger == IncrementalProjectBuilder.CLEAN_BUILD)
321       autoBuildJob.forceBuild();
322   }
323 
324   public void build(IProject project, int trigger, IProgressMonitor monitor) throws CoreException {
325     if (!canRun(trigger))
326       return;
327     try {
328       building = true;
329       MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, ICoreConstants.MSG_EVENTS_ERRORS, null);
330       basicBuild(project, trigger, status, monitor);
331       if (!status.isOK())
332         throw new ResourceException(status);
333     } finally {
334       cleanup(trigger);
335     }
336   }
337 
338   public void build(IProject project, int trigger, String builderName, Map args, IProgressMonitor monitor) throws CoreException {
339     monitor = Policy.monitorFor(monitor);
340     try {
341       String message = Policy.bind("events.building.1", project.getFullPath().toString()); //$NON-NLS-1$
342       monitor.beginTask(message, 1);
343       if (!canRun(trigger))
344         return;
345       try {
346         building = true;
347         MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, ICoreConstants.MSG_EVENTS_ERRORS, null);
348         basicBuild(project, trigger, builderName, args, status, Policy.subMonitorFor(monitor, 1));
349         if (!status.isOK())
350           throw new ResourceException(status);
351       } finally {
352         cleanup(trigger);
353       }
354     } finally {
355       monitor.done();
356     }
357   }
358 
359   protected boolean canRun(int trigger) {
360     return !building;
361   }
362 
363   /**
364    * Another thread is attempting to modify the workspace. Cancel the
365    * autobuild and wait until it completes.
366    */
367   public void interrupt() {
368     autoBuildJob.interrupt();
369   }
370 
371   /**
372    * Cancel the build if the user has canceled or if an auto-build has been interrupted.
373    */
374   private void checkCanceled(int trigger, IProgressMonitor monitor) {
375     //if the system is shutting down, don't build
376     if (systemBundle.getState() == Bundle.STOPPING)
377       throw new OperationCanceledException();
378     Policy.checkCanceled(monitor);
379     //check for auto-cancel only if we are auto-building
380     if (trigger != IncrementalProjectBuilder.AUTO_BUILD)
381       return;
382     //check for request to interrupt the auto-build
383     if (autoBuildJob.isInterrupted())
384       throw new OperationCanceledException();
385   }
386 
387   protected IProject[] computeUnorderedProjects(IProject[] ordered) {
388     IProject[] unordered;
389     HashSet leftover = new HashSet(Arrays.asList(workspace.getRoot().getProjects()));
390     leftover.removeAll(Arrays.asList(ordered));
391     unordered = (IProject[]) leftover.toArray(new IProject[leftover.size()]);
392     return unordered;
393   }
394 
395   /**
396    * Creates and returns a Map mapping String(builder name) -> BuilderPersistentInfo. 
397    * The table includes entries for all builders that are
398    * in the builder spec, and that have a last built state, even if they 
399    * have not been instantiated this session.
400    */
401   public Map createBuildersPersistentInfo(IProject project) throws CoreException {
402     /* get the old builder map */
403     Map oldInfos = getBuildersPersistentInfo(project);
404 
405     ICommand[] buildCommands = ((Project) project).internalGetDescription().getBuildSpec(false);
406     if (buildCommands.length == 0)
407       return null;
408 
409     /* build the new map */
410     Map newInfos = new HashMap(buildCommands.length * 2);
411     Hashtable instantiatedBuilders = getBuilders(project);
412     for (int i = 0; i < buildCommands.length; i++) {
413       String builderName = buildCommands[i].getBuilderName();
414       BuilderPersistentInfo info = null;
415       IncrementalProjectBuilder builder = (IncrementalProjectBuilder) instantiatedBuilders.get(builderName);
416       if (builder == null) {
417         // if the builder was not instantiated, use the old info if any.
418         if (oldInfos != null)
419           info = (BuilderPersistentInfo) oldInfos.get(builderName);
420       } else if (!(builder instanceof MissingBuilder)) {
421         ElementTree oldTree = ((InternalBuilder) builder).getLastBuiltTree();
422         //don't persist build state for builders that have no last built state
423         if (oldTree != null) {
424           // if the builder was instantiated, construct a memento with the important info
425           info = new BuilderPersistentInfo();
426           info.setProjectName(project.getName());
427           info.setBuilderName(builderName);
428           info.setLastBuildTree(oldTree);
429           info.setInterestingProjects(((InternalBuilder) builder).getInterestingProjects());
430         }
431       }
432       if (info != null)
433         newInfos.put(builderName, info);
434     }
435     return newInfos;
436   }
437 
438   protected String debugBuilder() {
439     return currentBuilder == null ? "<no builder>" : currentBuilder.getClass().getName(); //$NON-NLS-1$
440   }
441 
442   protected String debugProject() {
443     if (currentBuilder == null)
444       return "<no project>"; //$NON-NLS-1$
445     return currentBuilder.getProject().getFullPath().toString();
446   }
447 
448   /**
449    * The outermost workspace operation has finished.  Do an autobuild if necessary.
450    */
451   public void endTopLevel(boolean needsBuild) {
452     autoBuildJob.build(needsBuild);
453   }
454 
455   protected IncrementalProjectBuilder getBuilder(String builderName, IProject project, MultiStatus status) throws CoreException {
456     Hashtable builders = getBuilders(project);
457     IncrementalProjectBuilder result = (IncrementalProjectBuilder) builders.get(builderName);
458     if (result != null)
459       return result;
460     result = initializeBuilder(builderName, project, status);
461     builders.put(builderName, result);
462     ((InternalBuilder) result).setProject(project);
463     ((InternalBuilder) result).startupOnInitialize();
464     return result;
465   }
466 
467   /**
468    * Returns a hashtable of all instantiated builders for the given project.
469    * This hashtable maps String(builder name) -> Builder.
470    */
471   protected Hashtable getBuilders(IProject project) {
472     ProjectInfo info = (ProjectInfo) workspace.getResourceInfo(project.getFullPath(), false, false);
473     if (info == null)
474       Assert.isNotNull(info, Policy.bind("events.noProject", project.getName())); //$NON-NLS-1$
475     return info.getBuilders();
476   }
477 
478   /**
479    * Returns a Map mapping String(builder name) -> BuilderPersistentInfo.
480    * The map includes entries for all builders that are in the builder spec,
481    * and that have a last built state, even if they have not been instantiated
482    * this session.
483    */
484   public Map getBuildersPersistentInfo(IProject project) throws CoreException {
485     return (Map) project.getSessionProperty(K_BUILD_MAP);
486   }
487 
488   protected IResourceDelta getDelta(IProject project) {
489     try {
490       lock.acquire();
491       if (currentTree == null) {
492         if (Policy.DEBUG_BUILD_FAILURE)
493           Policy.debug("Build: no tree for delta " + debugBuilder() + " [" + debugProject() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
494         return null;
495       }
496       //check if this builder has indicated it cares about this project
497       if (!isInterestingProject(project)) {
498         if (Policy.DEBUG_BUILD_FAILURE)
499           Policy.debug("Build: project not interesting for this builder " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
500         return null;
501       }
502       //check if this project has changed
503       if (currentDelta != null && currentDelta.findNodeAt(project.getFullPath()) == null) {
504         //if the project never existed (not in delta and not in current tree), return null
505         if (!project.exists())
506           return null;
507         //just return an empty delta rooted at this project
508         return ResourceDeltaFactory.newEmptyDelta(project);
509       }
510       //now check against the cache
511       IResourceDelta result = (IResourceDelta) deltaCache.getDelta(project.getFullPath(), lastBuiltTree, currentTree);
512       if (result != null)
513         return result;
514   
515       long startTime = 0L;
516       if (Policy.DEBUG_BUILD_DELTA) {
517         startTime = System.currentTimeMillis();
518         Policy.debug("Computing delta for project: " + project.getName()); //$NON-NLS-1$
519       }
520       result = ResourceDeltaFactory.computeDelta(workspace, lastBuiltTree, currentTree, project.getFullPath(), -1);
521       deltaCache.cache(project.getFullPath(), lastBuiltTree, currentTree, result);
522       if (Policy.DEBUG_BUILD_FAILURE && result == null)
523         Policy.debug("Build: no delta " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
524       if (Policy.DEBUG_BUILD_DELTA)
525         Policy.debug("Finished computing delta, time: " + (System.currentTimeMillis() - startTime) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
526       return result;
527     } finally {
528       lock.release();
529     }
530   }
531 
532   /**
533    * Returns the safe runnable instance for invoking a builder
534    */
535   protected ISafeRunnable getSafeRunnable(final int trigger, final Map args, final MultiStatus status, final IProgressMonitor monitor) {
536     return new ISafeRunnable() {
537       public void handleException(Throwable e) {
538         if (e instanceof OperationCanceledException) {
539           // discard tree state just in case builders did not clean
540           // up after monitor was cancelled.
541           currentBuilder.forgetLastBuiltState();
542           throw (OperationCanceledException) e;
543         }
544         //ResourceStats.buildException(e);
545         // don't log the exception....it is already being logged in Platform#run
546 
547         //add a generic message to the MultiStatus
548         String builderName = currentBuilder.getLabel();
549         if (builderName == null || builderName.length() == 0)
550           builderName = currentBuilder.getClass().getName();
551         String pluginId = currentBuilder.getPluginDescriptor().getUniqueIdentifier();
552         String message = Policy.bind("events.builderError", builderName, currentBuilder.getProject().getName()); //$NON-NLS-1$
553         status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, null));
554 
555         //add the exception status to the MultiStatus
556         if (e instanceof CoreException)
557           status.add(((CoreException) e).getStatus());
558         else {
559           message = e.getMessage();
560           if (message == null)
561             message = Policy.bind("events.unknown", e.getClass().getName(), builderName); //$NON-NLS-1$
562           status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, e));
563         }
564       }
565 
566       public void run() throws Exception {
567         IProject[] prereqs = null;
568         //invoke the appropriate build method depending on the trigger
569         if (trigger != IncrementalProjectBuilder.CLEAN_BUILD)
570           prereqs = currentBuilder.build(trigger, args, monitor);
571         else
572           currentBuilder.clean(monitor);
573         if (prereqs == null)
574           prereqs = new IProject[0];
575         currentBuilder.setInterestingProjects((IProject[]) prereqs.clone());
576       }
577     };
578   }
579 
580   public void handleEvent(LifecycleEvent event) {
581     IProject project = null;
582     switch (event.kind) {
583       case LifecycleEvent.PRE_PROJECT_DELETE :
584       case LifecycleEvent.PRE_PROJECT_MOVE :
585         project = (IProject) event.resource;
586         //make sure the builder persistent info is deleted for the project move case
587         if (project.isAccessible())
588           setBuildersPersistentInfo(project, null);
589     }
590   }
591 
592   /**
593    * Returns true if the given project has been built during this build cycle, and
594    * false otherwise.
595    */
596   public boolean hasBeenBuilt(IProject project) {
597     return builtProjects.contains(project);
598   }
599 
600   /**
601    * Hook for adding trace options and debug information at the end of a build.
602    */
603   private void hookEndBuild(IncrementalProjectBuilder builder) {
604     if (Policy.MONITOR_BUILDERS)
605       EventStats.endBuild();
606     if (!Policy.DEBUG_BUILD_INVOKING || timeStamp == -1)
607       return; //builder wasn't called or we are not debugging
608     Policy.debug("Builder finished: " + toString(builder) + " time: " + (System.currentTimeMillis() - timeStamp) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
609     timeStamp = -1;
610   }
611 
612   /**
613    * Hook for adding trace options and debug information at the start of a build.
614    */
615   private void hookStartBuild(IncrementalProjectBuilder builder, int trigger) {
616     if (Policy.MONITOR_BUILDERS)
617       EventStats.startBuild(builder);
618     if (Policy.DEBUG_BUILD_INVOKING) {
619       timeStamp = System.currentTimeMillis();
620       String type;
621       switch (trigger) {
622         case IncrementalProjectBuilder.FULL_BUILD :
623           type = "FULL_BUILD"; //$NON-NLS-1$
624           break;
625         case IncrementalProjectBuilder.INCREMENTAL_BUILD :
626         default :
627           type = "INCREMENTAL_BUILD"; //$NON-NLS-1$
628       }
629       Policy.debug("Invoking (" + type + ") on builder: " + toString(builder)); //$NON-NLS-1$ //$NON-NLS-2$
630     }
631   }
632 
633   /**
634    * Instantiates the builder with the given name.  If the builder, its plugin, or its nature
635    * is missing, create a placeholder builder to takes its place.  This is needed to generate 
636    * appropriate exceptions when somebody tries to invoke the builder, and to
637    * prevent trying to instantiate it every time a build is run.
638    * This method NEVER returns null.
639    */
640   protected IncrementalProjectBuilder initializeBuilder(String builderName, IProject project, MultiStatus status) throws CoreException {
641     IncrementalProjectBuilder builder = null;
642     try {
643       builder = instantiateBuilder(builderName);
644     } catch (CoreException e) {
645       status.add(new ResourceStatus(IResourceStatus.BUILD_FAILED, project.getFullPath(), Policy.bind("events.instantiate.1", builderName), e)); //$NON-NLS-1$
646       status.add(e.getStatus());
647     }
648     if (builder == null) {
649       //unable to create the builder, so create a placeholder to fill in for it
650       builder = new MissingBuilder(builderName);
651     }
652     // get the map of builders to get the last built tree
653     Map infos = getBuildersPersistentInfo(project);
654     if (infos != null) {
655       BuilderPersistentInfo info = (BuilderPersistentInfo) infos.remove(builderName);
656       if (info != null) {
657         ElementTree tree = info.getLastBuiltTree();
658         if (tree != null)
659           ((InternalBuilder) builder).setLastBuiltTree(tree);
660         ((InternalBuilder) builder).setInterestingProjects(info.getInterestingProjects());
661       }
662       // delete the build map if it's now empty 
663       if (infos.size() == 0)
664         setBuildersPersistentInfo(project, null);
665     }
666     return builder;
667   }
668 
669   /**
670    * Instantiates and returns the builder with the given name.  If the builder, its plugin, or its nature
671    * is missing, returns null.
672    */
673   protected IncrementalProjectBuilder instantiateBuilder(String builderName) throws CoreException {
674     IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, builderName);
675     if (extension == null)
676       return null;
677     IConfigurationElement[] configs = extension.getConfigurationElements();
678     if (configs.length == 0)
679       return null;
680     String hasNature = configs[0].getAttribute("hasNature"); //$NON-NLS-1$
681     String natureId = null;
682     if (hasNature != null && hasNature.equalsIgnoreCase(Boolean.TRUE.toString())) {
683       //find the nature that owns this builder
684       String builderId = extension.getUniqueIdentifier();
685       natureId = workspace.getNatureManager().findNatureForBuilder(builderId);
686       if (natureId == null)
687         return null;
688     }
689     //The nature exists, or this builder doesn't specify a nature
690     InternalBuilder builder = (InternalBuilder) configs[0].createExecutableExtension("run"); //$NON-NLS-1$
691     builder.setPluginDescriptor(extension.getDeclaringPluginDescriptor());
692     builder.setLabel(extension.getLabel());
693     builder.setNatureId(natureId);
694     return (IncrementalProjectBuilder) builder;
695   }
696 
697   /**
698    * Returns true if the current builder is interested in changes
699    * to the given project, and false otherwise.
700    */
701   protected boolean isInterestingProject(IProject project) {
702     if (project.equals(currentBuilder.getProject()))
703       return true;
704     IProject[] interestingProjects = currentBuilder.getInterestingProjects();
705     for (int i = 0; i < interestingProjects.length; i++) {
706       if (interestingProjects[i].equals(project)) {
707         return true;
708       }
709     }
710     return false;
711   }
712 
713   /**
714    * Returns true if the given builder needs to be invoked, and false
715    * otherwise.
716    * 
717    * The algorithm is to compute the intersection of the set of projects that
718    * have changed since the last build, and the set of projects this builder
719    * cares about.  This is an optimization, under the assumption that computing
720    * the forward delta once (not the resource delta) is more efficient than
721    * computing project deltas and invoking builders for projects that haven't
722    * changed.
723    */
724   protected boolean needsBuild(InternalBuilder builder) {
725     //compute the delta since the last built state
726     ElementTree oldTree = builder.getLastBuiltTree();
727     ElementTree newTree = workspace.getElementTree();
728     long start = System.currentTimeMillis();
729     currentDelta = (DeltaDataTree) deltaTreeCache.getDelta(null, oldTree, newTree);
730     if (currentDelta == null) {
731       if (Policy.DEBUG_NEEDS_BUILD) {
732         String message = "Checking if need to build. Starting delta computation between: " + oldTree.toString() + " and " + newTree.toString(); //$NON-NLS-1$ //$NON-NLS-2$
733         Policy.debug(message);
734       }
735       currentDelta = newTree.getDataTree().forwardDeltaWith(oldTree.getDataTree(), ResourceComparator.getComparator(false));
736       if (Policy.DEBUG_NEEDS_BUILD)
737         Policy.debug("End delta computation. (" + (System.currentTimeMillis() - start) + "ms)."); //$NON-NLS-1$ //$NON-NLS-2$
738       deltaTreeCache.cache(null, oldTree, newTree, currentDelta);
739     }
740 
741     //search for the builder's project
742     if (currentDelta.findNodeAt(builder.getProject().getFullPath()) != null) {
743       if (Policy.DEBUG_NEEDS_BUILD)
744         Policy.debug(toString(builder) + " needs building because of changes in: " + builder.getProject().getName()); //$NON-NLS-1$
745       return true;
746     }
747 
748     //search for builder's interesting projects
749     IProject[] projects = builder.getInterestingProjects();
750     for (int i = 0; i < projects.length; i++) {
751       if (currentDelta.findNodeAt(projects[i].getFullPath()) != null) {
752         if (Policy.DEBUG_NEEDS_BUILD)
753           Policy.debug(toString(builder) + " needs building because of changes in: " + projects[i].getName()); //$NON-NLS-1$
754         return true;
755       }
756     }
757     return false;
758   }
759 
760   /**
761    * Removes all builders with the given ID from the build spec.
762    * Does nothing if there were no such builders in the spec
763    */
764   protected void removeBuilders(IProject project, String builderId) throws CoreException {
765     IProjectDescription desc = project.getDescription();
766     ICommand[] oldSpec = desc.getBuildSpec();
767     int oldLength = oldSpec.length;
768     if (oldLength == 0)
769       return;
770     int remaining = 0;
771     //null out all commands that match the builder to remove
772     for (int i = 0; i < oldSpec.length; i++) {
773       if (oldSpec[i].getBuilderName().equals(builderId))
774         oldSpec[i] = null;
775       else
776         remaining++;
777     }
778     //check if any were actually removed
779     if (remaining == oldSpec.length)
780       return;
781     ICommand[] newSpec = new ICommand[remaining];
782     for (int i = 0, newIndex = 0; i < oldLength; i++) {
783       if (oldSpec[i] != null)
784         newSpec[newIndex++] = oldSpec[i];
785     }
786     desc.setBuildSpec(newSpec);
787     project.setDescription(desc, IResource.NONE, null);
788   }
789 
790   /**
791    * Hook for builders to request a rebuild.
792    */
793   public void requestRebuild() {
794     rebuildRequested = true;
795   }
796 
797   /**
798    * Sets the builder map for the given project.  The builder map is
799    * a Map mapping String(builder name) -> BuilderPersistentInfo.
800    * The map includes entries for all builders that are
801    * in the builder spec, and that have a last built state, even if they 
802    * have not been instantiated this session.
803    */
804   public void setBuildersPersistentInfo(IProject project, Map map) {
805     try {
806       project.setSessionProperty(K_BUILD_MAP, map);
807     } catch (CoreException e) {
808       //project is missing -- build state will be lost
809       //can't throw an exception because this happens on startup
810       IStatus error = new ResourceStatus(IStatus.ERROR, 1, project.getFullPath(), "Project missing in setBuildersPersistentInfo", null); //$NON-NLS-1$
811       ResourcesPlugin.getPlugin().getLog().log(error);
812     }
813   }
814 
815   public void shutdown(IProgressMonitor monitor) {
816     autoBuildJob.cancel();
817   }
818 
819   public void startup(IProgressMonitor monitor) {
820     workspace.addLifecycleListener(this);
821   }
822 
823   /**
824    * Returns a string representation of the given builder.  
825    * For debugging purposes only.
826    */
827   protected String toString(InternalBuilder builder) {
828     String name = builder.getClass().getName();
829     name = name.substring(name.lastIndexOf('.') + 1);
830     return name + "(" + builder.getProject().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
831   }
832 
833   /**
834    * Returns true if the nature membership rules are satisifed for the given
835    * builder extension on the given project, and false otherwise.  A builder that 
836    * does not specify that it belongs to a nature is always valid.  A builder 
837    * extension that belongs to a nature can be invalid for the following reasons:
838    * <ul>
839    * <li>The nature that owns the builder does not exist on the given project</li>
840    * <li>The nature that owns the builder is disabled on the given project</li>
841    * </ul>
842    * Furthermore, if the nature that owns the builder does not exist on the project,
843    * that builder will be removed from the build spec.
844    * 
845    * Note: This method only validates nature constraints that can vary at runtime.
846    * Additional checks are done in the instantiateBuilder method for constraints
847    * that cannot vary once the plugin registry is initialized.
848    */
849   protected boolean validateNature(InternalBuilder builder, String builderId) throws CoreException {
850     String nature = builder.getNatureId();
851     if (nature == null)
852       return true;
853     IProject project = builder.getProject();
854     if (!project.hasNature(nature)) {
855       //remove this builder from the build spec
856       removeBuilders(project, builderId);
857       return false;
858     }
859     return project.isNatureEnabled(nature);
860   }
861 }