Work in progress on bug 21029
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java
index 99b83ef..24559fc 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java
@@ -17,10 +17,17 @@
 import org.eclipse.core.resources.ICommand;
 public class BuildCommand extends ModelObject implements ICommand {
 	protected HashMap arguments;
+	/** Cached hash code for performance */
+	protected int hash = -1;
+	
 public BuildCommand() {
 	super(""); //$NON-NLS-1$
 	this.arguments = new HashMap(0);
 }
+public BuildCommand(String builderName, Map args) {
+	super(builderName);
+	this.arguments = args == null ? new HashMap(0) : new HashMap(args);
+}
 public Object clone() {
 	BuildCommand result = null;
 	result = (BuildCommand) super.clone();
@@ -34,10 +41,13 @@
 		return true;
 	if (!(object instanceof BuildCommand))
 		return false;
+	//for performance compare the cached hash value first
+	if (hashCode() != object.hashCode())
+		return false;
 	BuildCommand command = (BuildCommand) object;
 	// equal if same builder name and equal argument tables
-	return getBuilderName().equals(command.getBuilderName()) &&
-		getArguments(false).equals(command.getArguments(false));
+	return (name == null ? command.name == null : name.equals(command.name)) &&
+		(arguments == null ? command.arguments == null : arguments.equals(command.arguments));
 }
 /**
  * @see ICommand#getArguments
@@ -55,8 +65,17 @@
 	return getName();
 }
 public int hashCode() {
-	// hash on name alone
-	return getName().hashCode();
+	//lazily compute and cache hashcode
+	if (hash == -1) {
+		String name = getName();
+		hash = name == null ? 17 : name.hashCode() * 37;
+		if (arguments != null)
+			hash += arguments.hashCode();
+		//make sure it is never the same as the default value
+		if (hash == -1)
+			hash++;
+	}
+	return hash;
 }
 /**
  * @see ICommand#setArguments
@@ -64,6 +83,7 @@
 public void setArguments(Map value) {
 	// copy parameter for safety's sake
 	arguments = value == null ? null : new HashMap(value);
+	hash = -1;
 }
 /**
  * @see ICommand#setBuilderName
@@ -71,5 +91,6 @@
 public void setBuilderName(String value) {
 	//don't allow builder name to be null
 	setName(value == null ? "" : value); //$NON-NLS-1$
+	hash = -1;
 }
 }
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
index 1d1fc20..ed118ff 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
@@ -26,6 +26,16 @@
 	//used for debug/trace timing
 	private long timeStamp = -1;
 	
+	/**
+	 * Persistent information for builders that have not yet been instantiated.
+	 * For closed projects, this contains (IProject -> (List of BuilderPersistentInfo)).
+	 * For open projects: (IProject -> (Map of (ICommand -> BuilderPersistentInfo)))
+	 * This is because the build commands are not yet known for closed projects
+	 * since the project description has not been read from disk.  The
+	 * opening() method performs the conversion from one format to the other.
+	 */
+	private final Map builderPersistentInfo = new HashMap();
+	
 	//the following four fields only apply for the lifetime of 
 	//a single builder invocation.
 	protected ElementTree currentTree;
@@ -159,11 +169,11 @@
 	};
 	Platform.run(code);
 }
-protected void basicBuild(IProject project, int trigger, String builderName, Map args, MultiStatus status, IProgressMonitor monitor) {
+protected void basicBuild(IProject project, int trigger, ICommand command, MultiStatus status, IProgressMonitor monitor) {
 	IncrementalProjectBuilder builder = null;
 	try {
-		builder = getBuilder(builderName, project);
-		if (!validateNature(builder, builderName)) {
+		builder = getBuilder(command, project);
+		if (!validateNature(builder, command.getBuilderName())) {
 			//skip this builder and null its last built tree because it is invalid
 			//if the nature gets added or re-enabled a full build will be triggered
 			((InternalBuilder)builder).setLastBuiltTree(null);
@@ -173,7 +183,7 @@
 		status.add(e.getStatus());
 		return;
 	}
-	basicBuild(trigger, builder, args, status, monitor);
+	basicBuild(trigger, builder, ((BuildCommand)command).getArguments(false), status, monitor);
 }
 protected void basicBuild(IProject project, int trigger, ICommand[] commands, MultiStatus status, IProgressMonitor monitor) {
 	monitor = Policy.monitorFor(monitor);
@@ -183,7 +193,7 @@
 		for (int i = 0; i < commands.length; i++) {
 			IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
 			BuildCommand command = (BuildCommand) commands[i];
-			basicBuild(project, trigger, command.getBuilderName(), command.getArguments(false), status, sub);
+			basicBuild(project, trigger, command, status, sub);
 			Policy.checkCanceled(monitor);
 		}
 	} finally {
@@ -247,7 +257,7 @@
 		try {
 			building = true;
 			MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Policy.bind("events.errors"), null); //$NON-NLS-1$
-			basicBuild(project, kind, builderName, args, status, Policy.subMonitorFor(monitor, 1));
+			basicBuild(project, kind, new BuildCommand(builderName, args), status, Policy.subMonitorFor(monitor, 1));
 			if (!status.isOK())
 				throw new ResourceException(status);
 		} finally {
@@ -266,12 +276,13 @@
 public void closing(IProject project) {
 }
 /**
- * Creates and returns a Map mapping String(builder name) -> BuilderPersistentInfo. 
- * The table includes entries for all builders that are
- * in the builder spec, and that have a last built state, even if they 
- * have not been instantiated this session.
+ * Creates and returns a List of BuilderPersistentInfo, or null if there are
+ * no builders for this project.
+ * The list includes entries for all builders that are in the builder spec, and 
+ * that have a last built state, even if they have not been instantiated this 
+ * session.
  */
-public Map createBuildersPersistentInfo(IProject project) throws CoreException {
+public List createBuildersPersistentInfo(IProject project) throws CoreException {
 	/* get the old builder map */
 	Map oldInfos = getBuildersPersistentInfo(project);
 
@@ -280,30 +291,31 @@
 		return null;
 		
 	/* build the new map */
-	Map newInfos = new HashMap(buildCommands.length * 2);
-	Hashtable instantiatedBuilders = getBuilders(project);
+	List newInfos = new ArrayList(buildCommands.length);
+	Map instantiatedBuilders = getBuilders(project);
 	for (int i = 0; i < buildCommands.length; i++) {
-		String builderName = buildCommands[i].getBuilderName();
+		ICommand command = buildCommands[i];
 		BuilderPersistentInfo info = null;
-		IncrementalProjectBuilder builder = (IncrementalProjectBuilder) instantiatedBuilders.get(builderName);
+		InternalBuilder builder = (InternalBuilder) instantiatedBuilders.get(command);
 		if (builder == null) {
 			// if the builder was not instantiated, use the old info if any.
 			if (oldInfos != null) 
-				info = (BuilderPersistentInfo) oldInfos.get(builderName);
+				info = (BuilderPersistentInfo) oldInfos.get(command);
 		} else if (!(builder instanceof MissingBuilder)) {
-			ElementTree oldTree = ((InternalBuilder) builder).getLastBuiltTree();
-			//don't persist build state for builders that have no last built state
+			ElementTree oldTree = builder.getLastBuiltTree();
+			//don't persist build state for builders that have no last built tree
 			if (oldTree != null) {
-				// if the builder was instantiated, construct a memento with the important info
+				// construct a memento with the important info
 				info = new BuilderPersistentInfo();
 				info.setProjectName(project.getName());
-				info.setBuilderName(builderName);
+				info.setBuilderName(command.getBuilderName());
 				info.setLastBuildTree(oldTree);
-				info.setInterestingProjects(((InternalBuilder)builder).getInterestingProjects());
+				info.setInterestingProjects(builder.getInterestingProjects());
+				info.setBuildSpecPosition(i);
 			}
 		}
 		if (info != null)
-			newInfos.put(builderName, info);
+			newInfos.add(info);
 	}
 	return newInfos;
 }
@@ -316,37 +328,42 @@
 	return currentBuilder.getProject().getFullPath().toString();
 }
 public void deleting(IProject project) {
-	//make sure the builder persistent info is deleted for the project move case
+	//make sure the builder persistent info is deleted (move and delete cases)
 	setBuildersPersistentInfo(project, null);
 }
-protected IncrementalProjectBuilder getBuilder(String builderName, IProject project) throws CoreException {
-	Hashtable builders = getBuilders(project);
-	IncrementalProjectBuilder result = (IncrementalProjectBuilder) builders.get(builderName);
+protected IncrementalProjectBuilder getBuilder(ICommand command, IProject project) throws CoreException {
+	Map builders = getBuilders(project);
+	IncrementalProjectBuilder result = (IncrementalProjectBuilder) builders.get(command);
 	if (result != null)
 		return result;
-	result = initializeBuilder(builderName, project);
-	builders.put(builderName, result);
+	result = initializeBuilder(command, project);
+	builders.put(command, result);
 	((InternalBuilder) result).setProject(project);
 	result.startupOnInitialize();
 	return result;
 }
 /**
- * Returns a hashtable of all instantiated builders for the given project.
- * This hashtable maps String(builder name) -> Builder.
+ * Returns a map of all instantiated builders for the given project.
+ * This hashtable maps ICommand -> Builder.
  */
-protected Hashtable getBuilders(IProject project) {
+protected Map getBuilders(IProject project) {
 	ProjectInfo info = (ProjectInfo) workspace.getResourceInfo(project.getFullPath(), false, false);
 	Assert.isNotNull(info, Policy.bind("events.noProject", project.getName())); //$NON-NLS-1$
 	return info.getBuilders();
 }
 /**
- * Returns a Map mapping String(builder name) -> BuilderPersistentInfo.
+ * Returns a Map mapping ICommand -> BuilderPersistentInfo, or
+ * null if there is no persistent info for this project.
  * The map includes entries for all builders that are in the builder spec,
- * and that have a last built state, even if they have not been instantiated
+ * and that have a last built state, but have not been instantiated
  * this session.
  */
 public Map getBuildersPersistentInfo(IProject project) throws CoreException {
-	return (Map) project.getSessionProperty(K_BUILD_MAP);
+	Object result = builderPersistentInfo.get(project);
+	//for closed projects this will be a List
+	if (result instanceof Map)
+		return (Map)result;
+	return null;
 }
 protected IResourceDelta getDelta(IProject project) {
 	if (currentTree == null) {
@@ -436,8 +453,9 @@
  * prevent trying to instantiate it every time a build is run.
  * This method NEVER returns null.
  */
-protected IncrementalProjectBuilder initializeBuilder(String builderName, IProject project) throws CoreException {
+protected IncrementalProjectBuilder initializeBuilder(ICommand command, IProject project) throws CoreException {
 	try {
+		String builderName = command.getBuilderName();
 		IncrementalProjectBuilder builder = instantiateBuilder(builderName);
 		if (builder == null) {
 			//unable to create the builder, so create a placeholder to fill in for it
@@ -446,7 +464,7 @@
 		// get the map of builders to get the last built tree
 		Map infos = getBuildersPersistentInfo(project);
 		if (infos != null) {
-			BuilderPersistentInfo info = (BuilderPersistentInfo) infos.remove(builderName);
+			BuilderPersistentInfo info = (BuilderPersistentInfo) infos.remove(command);
 			if (info != null) {
 				ElementTree tree = info.getLastBuiltTree();
 				if (tree != null) 
@@ -455,7 +473,7 @@
 			}
 			// delete the build map if it's now empty 
 			if (infos.size() == 0)
-				setBuildersPersistentInfo(project, null);
+				builderPersistentInfo.remove(project);
 		}
 		return builder;
 	} catch (CoreException e) {
@@ -540,6 +558,36 @@
 	return false;	
 }
 public void opening(IProject project) {
+	//a project is being opened. link builder persistent info to appropriate
+	//command in the build spec
+	List infoList = (List)builderPersistentInfo.remove(project);
+	if (infoList == null || infoList.size() == 0)
+		return;//no builders for this project
+		
+	ProjectDescription description = ((Project)project).internalGetDescription();
+	if (description != null) {
+		ICommand[] commands = description.getBuildSpec(false);
+		//create map of ICommand->BuilderPersistentInfo
+		HashMap infoMap = new HashMap(commands.length * 2 + 1);
+		for (Iterator it = infoList.iterator(); it.hasNext();) {
+			BuilderPersistentInfo info = (BuilderPersistentInfo) it.next();
+			int position = info.getBuildSpecPosition();
+			if (position >= 0 && position < commands.length)
+				infoMap.put(commands[position], info);
+			else {
+				//backwards compatibility -- build spec position wasn't previously stored
+				//find a build command with matching builder name
+				String builderName = info.getBuilderName();
+				for (int i = 0; i < commands.length; i++) {
+					if (commands[i].getBuilderName().equals(builderName)) {
+						infoMap.put(commands[i], info);
+						break;
+					}						
+				}
+			}
+		}
+		builderPersistentInfo.put(project, infoMap);
+	}
 }
 /**
  * Removes all builders with the given ID from the build spec.
@@ -572,21 +620,14 @@
 }
 
 /**
- * Sets the builder map for the given project.  The builder map is
- * a Map mapping String(builder name) -> BuilderPersistentInfo.
- * The map includes entries for all builders that are
- * in the builder spec, and that have a last built state, even if they 
- * have not been instantiated this session.
+ * Supplies the list of BuilderPersistentInfo objects that was serialized
+ * on shutdown.  This list includes entries for all builders that are
+ * in the builder spec, have a last built state, but have not been instantiated
+ * yest this session.  Infos should only ever be supplied on startup, as this 
+ * information depends on the current ordering of the project's build spec.
  */
-public void setBuildersPersistentInfo(IProject project, Map map) {
-	try {
-		project.setSessionProperty(K_BUILD_MAP, map);
-	} catch (CoreException e) {
-		//project is missing -- build state will be lost
-		//can't throw an exception because this happens on startup
-		IStatus error = new ResourceStatus(IStatus.ERROR, 1, project.getFullPath(), "Project missing in setBuildersPersistentInfo", null);
-		ResourcesPlugin.getPlugin().getLog().log(error);
-	}
+public void setBuildersPersistentInfo(IProject project, List infoList) {
+	builderPersistentInfo.put(project, infoList);
 }
 public void shutdown(IProgressMonitor monitor) {
 }
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java
index a80590a..b2a9ec0 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java
@@ -19,18 +19,13 @@
 	protected String builderName;
 	protected ElementTree lastBuildTree;
 	protected IProject[] interestingProjects = ICoreConstants.EMPTY_PROJECT_ARRAY;
-
-public void setProjectName(String name) {
-	projectName = name;
+	/** Offset of this builder in the build spec */
+	protected int buildSpecPosition = -1;
+	
+public BuilderPersistentInfo() {
 }
-public void setBuilderName(String name) {
-	builderName = name;
-}
-public void setLastBuildTree(ElementTree tree) {
-	lastBuildTree = tree;
-}
-public void setInterestingProjects(IProject[] projects) {
-	interestingProjects = projects;
+public int getBuildSpecPosition() {
+	return buildSpecPosition;
 }
 public String getProjectName() {
 	return projectName;
@@ -44,4 +39,19 @@
 public IProject[] getInterestingProjects() {
 	return interestingProjects;
 }
+public void setProjectName(String name) {
+	projectName = name;
+}
+public void setBuilderName(String name) {
+	builderName = name;
+}
+public void setBuildSpecPosition(int buildSpecPosition) {
+	this.buildSpecPosition = buildSpecPosition;
+}
+public void setLastBuildTree(ElementTree tree) {
+	lastBuildTree = tree;
+}
+public void setInterestingProjects(IProject[] projects) {
+	interestingProjects = projects;
+}
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java
index c64d337..73f4d79 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java
@@ -16,8 +16,6 @@
 public interface ICoreConstants {
 	
 	// Standard resource SessionProperties
-	/** map of builders to their last built state. */
-	public static final QualifiedName K_BUILD_MAP = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "BuildMap"); //$NON-NLS-1$
 
 	// resource info constants
 	static final long I_UNKNOWN_SYNC_INFO = -2;
@@ -63,6 +61,7 @@
 
 	public static final int WORKSPACE_TREE_VERSION_1 = 67305985;
 	public static final int WORKSPACE_TREE_VERSION_2 = 67305986;
+	public static final int WORKSPACE_TREE_VERSION_3 = 67305987;
 	
 	// helper constants for empty structures
 	public static final IProject[] EMPTY_PROJECT_ARRAY = new IProject[0];
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
index e1bc292..29419ce 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
@@ -460,7 +460,7 @@
 	
 	//clear instantiated builders and natures because they reference the project handle
 	ProjectInfo info = (ProjectInfo) ((Resource)destination).getResourceInfo(false, true);
-	info.setBuilders(null);
+	info.clearBuilders();
 	info.clearNatures();
 	
 	//clear session properties and markers for the new project, because they shouldn't be copied.
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java
index 53ee9c6..db8a092 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java
@@ -10,14 +10,15 @@
  ******************************************************************************/
 package org.eclipse.core.internal.resources;
 
-import org.eclipse.core.resources.IProjectNature;
-import org.eclipse.core.internal.properties.PropertyStore;
 import java.util.HashMap;
-import java.util.Hashtable;
+import java.util.Map;
+
+import org.eclipse.core.internal.properties.PropertyStore;
+import org.eclipse.core.resources.IProjectNature;
 
 public class ProjectInfo extends ResourceInfo {
 	/** The list of builders for this project */
-	protected Hashtable builders = null;
+	protected HashMap builders = null;
 
 	/** The property store for this resource */
 	protected PropertyStore propertyStore = null;
@@ -27,12 +28,19 @@
 
 	/** The list of natures for this project */
 	protected HashMap natures = null;
+public void clearBuilders() {
+	builders = null;
+}
 public synchronized void clearNatures() {
 	natures = null;
 }
-public Hashtable getBuilders() {
+/**
+ * Returns a map, ICommand->IncrementalProjectBuilder, of all builders
+ * that have been instantiated for this project during this session.
+ */
+public Map getBuilders() {
 	if (builders == null)
-		builders = new Hashtable(5);
+		builders = new HashMap(5);
 	return builders;
 }
 /**
@@ -54,9 +62,6 @@
 public PropertyStore getPropertyStore() {
 	return propertyStore;
 }
-public void setBuilders(Hashtable value) {
-	builders = value;
-}
 /**
  * Sets the description associated with this info.  The value may be null.
  */
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
index 8fe4c53..8e45da3 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
@@ -215,7 +215,7 @@
 		// Clear the natures and builders on the destination project.
 		ProjectInfo info = (ProjectInfo) destination.getResourceInfo(false, true);
 		info.clearNatures();
-		info.setBuilders(null);
+		info.clearBuilders();
 
 		// Generate marker deltas.
 		try {
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
index 928874d..76ae085 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
@@ -26,8 +26,7 @@
 	protected Properties masterTable;
 	protected ElementTree lastSnap;
 	protected int operationCount = 0;
-	protected boolean snapshotRequested;
-	
+	protected boolean snapshotRequested;	
 
 	protected DelayedSnapshotRunnable snapshotRunnable;	
 	
@@ -173,9 +172,9 @@
 	for (int i = 0; i < projects.length; i++) {
 		IProject project = projects[i];
 		if (project.isOpen()) {
-			Map builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project);
+			List builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project);
 			if (builderInfos != null) {
-				for (Iterator it = builderInfos.values().iterator(); it.hasNext();) {
+				for (Iterator it = builderInfos.iterator(); it.hasNext();) {
 					BuilderPersistentInfo info = (BuilderPersistentInfo) it.next();
 					trees.add(info.getLastBuiltTree());
 				}
@@ -765,7 +764,8 @@
 		workspace.getMetaArea().write(description);
 	IProject[] roots = workspace.getRoot().getProjects();
 	for (int i = 0; i < roots.length; i++)
-		saveMetaInfo((Project) roots[i], null);
+		if (roots[i].isAccessible())
+			saveMetaInfo((Project) roots[i], null);
 }
 /**
  * Writes the current state of the entire workspace tree to disk.
@@ -779,7 +779,7 @@
 		IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation);
 		DataOutputStream output = new DataOutputStream(new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString()));
 		try {
-			output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2);
+			output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_3);
 			writeTree(computeStatesToSave(contexts, workspace.getElementTree()), output, monitor);
 		} finally {
 			output.close();
@@ -869,7 +869,7 @@
 			SafeChunkyOutputStream safeStream = new SafeChunkyOutputStream(localFile);
 			DataOutputStream out = new DataOutputStream(safeStream);
 			try {
-				out.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2);
+				out.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_3);
 				writeWorkspaceFields(out, monitor);
 				writer.writeDelta(tree, lastSnap, Path.ROOT, writer.D_INFINITE, out, ResourceComparator.getComparator());
 				safeStream.succeed();
@@ -979,16 +979,16 @@
 
 			// add builders' trees
 			IProject[] projects = workspace.getRoot().getProjects();
-			List builders = new ArrayList(projects.length * 2);
+			List builderInfos = new ArrayList(projects.length * 2);
 			for (int i = 0; i < projects.length; i++) {
 				IProject project = projects[i];
 				if (project.isOpen()) {
-					Map infos = workspace.getBuildManager().createBuildersPersistentInfo(project);
+					List infos = workspace.getBuildManager().createBuildersPersistentInfo(project);
 					if (infos != null)
-						builders.addAll(infos.values());
+						builderInfos.addAll(infos);
 				}
 			}
-			writeBuilderPersistentInfo(output, builders, trees, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100));
+			writeBuilderPersistentInfo(output, builderInfos, trees, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100));
 
 			// add the current tree in the list as the last element
 			trees.add(current);
@@ -1013,7 +1013,7 @@
 		SafeFileOutputStream safe = new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString());
 		try {
 			DataOutputStream output = new DataOutputStream(safe);
-			output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2);
+			output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_3);
 			writeTree(project, output, null);
 		} finally {
 			safe.close();
@@ -1036,13 +1036,13 @@
 		boolean wasImmutable = false;
 		try {
 			/**
-			 * Obtain a table of String(builder name) -> BuilderPersistentInfo.
+			 * Obtain a List of BuilderPersistentInfo for this project
 			 * This includes builders that have never been instantiated
 			 * but already had a last built state.
 			 */
-			Map builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project);
-			List builders = builderInfos == null ? new ArrayList(5) : new ArrayList(builderInfos.values());
-			List trees = new ArrayList(builders.size() + 1);
+			List builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project);
+			int numBuilders = builderInfos == null ? 0 : builderInfos.size();
+			List trees = new ArrayList(numBuilders + 1);
 			monitor.worked(1);
 
 			/* Make sure the most recent tree is in the array */
@@ -1051,7 +1051,7 @@
 			current.immutable();
 
 			/* add the tree for each builder to the array */
-			writeBuilderPersistentInfo(output, builders, trees, Policy.subMonitorFor(monitor, 1));
+			writeBuilderPersistentInfo(output, builderInfos, trees, Policy.subMonitorFor(monitor, 1));
 			trees.add(current);
 
 			/* save the forest! */
@@ -1425,13 +1425,14 @@
 	for (int i = 0; i < projects.length; i++)
 		visitAndSnap(projects[i]);
 }
-protected void writeBuilderPersistentInfo(DataOutputStream output, List builders, List trees, IProgressMonitor monitor) throws IOException {
+protected void writeBuilderPersistentInfo(DataOutputStream output, List builderInfos, List trees, IProgressMonitor monitor) throws IOException {
 	monitor = Policy.monitorFor(monitor);
 	try {
 		// write the number of builders we are saving
-		output.writeInt(builders.size());
-		for (int i = 0; i < builders.size(); i++) {
-			BuilderPersistentInfo info = (BuilderPersistentInfo) builders.get(i);
+		int numBuilders = builderInfos == null ? 0 : builderInfos.size();
+		output.writeInt(numBuilders);
+		for (int i = 0; i < numBuilders; i++) {
+			BuilderPersistentInfo info = (BuilderPersistentInfo) builderInfos.get(i);
 			output.writeUTF(info.getProjectName());
 			output.writeUTF(info.getBuilderName());
 			// write interesting projects
@@ -1439,6 +1440,8 @@
 			output.writeInt(interestingProjects.length);
 			for (int j = 0; j < interestingProjects.length; j++)
 				output.writeUTF(interestingProjects[j].getName());
+			//write build spec position
+			output.writeInt(info.getBuildSpecPosition());
 			ElementTree last = info.getLastBuiltTree();
 			if (last ==null) {
 				//try to be resilient if a builder has no last built tree
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java
index d8fedf4..e1b2428 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java
@@ -81,7 +81,7 @@
 		monitor.done();
 	}
 }
-protected void readBuildersPersistentInfo(DataInputStream input, List builders, IProgressMonitor monitor) throws IOException, CoreException {
+protected void readBuildersPersistentInfo(DataInputStream input, List builders, IProgressMonitor monitor) throws IOException {
 	monitor = Policy.monitorFor(monitor);
 	try {
 		int builderCount = input.readInt();
@@ -139,7 +139,7 @@
 protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) throws CoreException {
 	monitor = Policy.monitorFor(monitor);
 	try {
-		HashMap infos = null;
+		ArrayList infos = null;
 		String projectName = null;
 		for (int i = 0; i < buildersToBeLinked.size(); i++) {
 			BuilderPersistentInfo info = (BuilderPersistentInfo) buildersToBeLinked.get(i);
@@ -149,10 +149,10 @@
 					workspace.getBuildManager().setBuildersPersistentInfo(project, infos);
 				}
 				projectName = info.getProjectName();
-				infos = new HashMap(5);
+				infos = new ArrayList(5);
 			}
 			info.setLastBuildTree(trees[index++]);
-			infos.put(info.getBuilderName(), info);
+			infos.add(info);
 		}
 		if (infos != null) {
 			IProject project = workspace.getRoot().getProject(projectName);
@@ -195,6 +195,8 @@
 			return new WorkspaceTreeReader(workspace);
 		case ICoreConstants.WORKSPACE_TREE_VERSION_2:
 			return new WorkspaceTreeReader_2(workspace);
+		case ICoreConstants.WORKSPACE_TREE_VERSION_3:
+			return new WorkspaceTreeReader_3(workspace);
 		default:
 			// The following class should be
 			// removed soon. See comments in WorkspaceTreeReader_0.
@@ -226,13 +228,13 @@
 
 		/* map builder names to trees */
 		if (numBuilders > 0) {
-			Map infos = new HashMap(trees.length * 2 + 1);
+			List infos = new ArrayList(numBuilders);
 			for (int i = 0; i < numBuilders; i++) {
 				BuilderPersistentInfo info = new BuilderPersistentInfo();
 				info.setBuilderName(builderNames[i]);
 				info.setProjectName(project.getName());
 				info.setLastBuildTree(trees[i]);
-				infos.put(builderNames[i], info);
+				infos.add(info);
 			}
 			workspace.getBuildManager().setBuildersPersistentInfo(project, infos);
 		}
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_3.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_3.java
new file mode 100644
index 0000000..d3ecf23
--- /dev/null
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_3.java
@@ -0,0 +1,55 @@
+/**********************************************************************
+ * Copyright (c) 2002 IBM Corporation and others.
+ * All rights reserved.   This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors: 
+ * IBM - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.core.internal.events.BuilderPersistentInfo;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Reads .tree files in format version 3.
+ */
+public class WorkspaceTreeReader_3 extends WorkspaceTreeReader_2 {
+public WorkspaceTreeReader_3(Workspace workspace) {
+	super(workspace);
+}
+protected int getVersion() {
+	return ICoreConstants.WORKSPACE_TREE_VERSION_3;
+}
+protected void readBuildersPersistentInfo(DataInputStream input, List builders, IProgressMonitor monitor) throws IOException {
+	monitor = Policy.monitorFor(monitor);
+	try {
+		int builderCount = input.readInt();
+		for (int i = 0; i < builderCount; i++) {
+			BuilderPersistentInfo info = new BuilderPersistentInfo();
+			info.setProjectName(input.readUTF());
+			info.setBuilderName(input.readUTF());
+			// read interesting projects
+			int n = input.readInt();
+			IProject[] projects = new IProject[n];
+			for (int j = 0; j < n; j++)
+				projects[j] = workspace.getRoot().getProject(input.readUTF());
+			info.setInterestingProjects(projects);
+			//read build spec position
+			info.setBuildSpecPosition(input.readInt());
+			builders.add(info);
+		}
+	} finally {
+		monitor.done();
+	}
+}
+}
\ No newline at end of file