Extend dependency handling
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/core/builder/IBuildState.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/core/builder/IBuildState.java
index 926a0bc..cc00948 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/core/builder/IBuildState.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/core/builder/IBuildState.java
@@ -21,6 +21,10 @@
  * Eventually it will be used to collect dependencies between project sources.
  */
 public interface IBuildState {
+
+	static int STRUCTURAL = 1;
+	static int CONTENT = 2;
+
 	/**
 	 * @param path
 	 */
@@ -35,6 +39,15 @@
 	void recordDependency(IPath path, IPath dependency);
 
 	/**
+	 * 
+	 * @param path
+	 * @param dependency
+	 * @param flags
+	 * @since 4.0
+	 */
+	void recordDependency(IPath path, IPath dependency, int flags);
+
+	/**
 	 * Records the structural change at the specified path
 	 * 
 	 * @param path
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/AbstractBuildState.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/AbstractBuildState.java
index ff6d3ea..a957496 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/AbstractBuildState.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/AbstractBuildState.java
@@ -41,4 +41,8 @@
 	public void resetStructuralChanges() {
 		structuralChanges.clear();
 	}
+
+	public final void recordDependency(IPath path, IPath dependency) {
+		recordDependency(path, dependency, STRUCTURAL);
+	}
 }
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/BuildStateStub.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/BuildStateStub.java
index 28b09c7..728906e 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/BuildStateStub.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/BuildStateStub.java
@@ -21,6 +21,6 @@
 	public void recordImportProblem(IPath path) {
 	}
 
-	public void recordDependency(IPath path, IPath dependency) {
+	public void recordDependency(IPath path, IPath dependency, int flags) {
 	}
 }
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/ScriptBuilder.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/ScriptBuilder.java
index fed3fca..7022ff0 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/ScriptBuilder.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/ScriptBuilder.java
@@ -28,6 +28,7 @@
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -109,44 +110,46 @@
 			log("\nStarting build of " + this.currentProject.getName() //$NON-NLS-1$
 					+ " @ " + new Date(startTime)); //$NON-NLS-1$
 		}
-		this.scriptProject = (ScriptProject) DLTKCore.create(currentProject);
-		if (!ScriptProjectUtil.isBuilderEnabled(scriptProject)) {
-			if (monitor != null) {
-				monitor.done();
-			}
-			return null;
-		}
-		IEnvironment environment = EnvironmentManager
-				.getEnvironment(scriptProject);
-		if (environment == null || !environment.isConnected()) {
-			// Do not build if environment is not available.
-			// TODO: Store build requests and call builds when connection will
-			// be established.
-			if (monitor != null) {
-				monitor.done();
-			}
-			return null;
-		}
-		final String version = currentProject
-				.getPersistentProperty(PROPERTY_BUILDER_VERSION);
-		if (version == null) {
-			removeWrongTaskMarkers();
-			currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION,
-					CURRENT_VERSION);
-			kind = FULL_BUILD;
-		} else if (!CURRENT_VERSION.equals(version)) {
-			if ("200810012003".equals(version)) { //$NON-NLS-1$
-				removeWrongTaskMarkers();
-			}
-			currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION,
-					CURRENT_VERSION);
-			kind = FULL_BUILD;
-		}
-		if (monitor == null) {
-			monitor = new NullProgressMonitor();
-		}
-		IProject[] requiredProjects = getRequiredProjects(true);
+		IProject[] requiredProjects = null;
 		try {
+			this.scriptProject = (ScriptProject) DLTKCore
+					.create(currentProject);
+			if (!ScriptProjectUtil.isBuilderEnabled(scriptProject)) {
+				if (monitor != null) {
+					monitor.done();
+				}
+				return null;
+			}
+			IEnvironment environment = EnvironmentManager
+					.getEnvironment(scriptProject);
+			if (environment == null || !environment.isConnected()) {
+				// Do not build if environment is not available.
+				// TODO: Store build requests and call builds when connection
+				// will
+				// be established.
+				if (monitor != null) {
+					monitor.done();
+				}
+				return null;
+			}
+			final String version = currentProject
+					.getPersistentProperty(PROPERTY_BUILDER_VERSION);
+			if (version == null) {
+				removeWrongTaskMarkers();
+				currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION,
+						CURRENT_VERSION);
+				kind = FULL_BUILD;
+			} else if (!CURRENT_VERSION.equals(version)) {
+				if ("200810012003".equals(version)) { //$NON-NLS-1$
+					removeWrongTaskMarkers();
+				}
+				currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION,
+						CURRENT_VERSION);
+				kind = FULL_BUILD;
+			}
+			if (monitor == null) {
+				monitor = new NullProgressMonitor();
+			}
 			if (kind == FULL_BUILD) {
 				if (DEBUG)
 					log("Performing full build as requested by user"); //$NON-NLS-1$
@@ -169,38 +172,47 @@
 					} else {
 						if (DEBUG)
 							log("Performing incremental build"); //$NON-NLS-1$
+						requiredProjects = getRequiredProjects(true);
 						incrementalBuild(delta, requiredProjects, monitor);
 					}
 				}
 			}
 		} catch (OperationCanceledException e) {
 			// TODO what?
+		} finally {
+			cleanup();
 		}
-		long endTime = 0;
 		if (DEBUG || TRACE) {
-			endTime = System.currentTimeMillis();
-		}
-		if (DEBUG) {
-			log("Finished build of " + currentProject.getName() //$NON-NLS-1$
-					+ " @ " + new Date(endTime) + ", elapsed " + (endTime - startTime) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-		}
-		if (TRACE) {
-			System.out
-					.println("-----SCRIPT-BUILDER-INFORMATION-TRACE----------------------------"); //$NON-NLS-1$
-			System.out.println("Finished build of project:" //$NON-NLS-1$
-					+ currentProject.getName() + "\n" //$NON-NLS-1$
-					+ "Building time:" //$NON-NLS-1$
-					+ Long.toString(endTime - startTime) + "\n" //$NON-NLS-1$
-					+ "Build type:" //$NON-NLS-1$
-					+ (kind == FULL_BUILD ? "Full build" //$NON-NLS-1$
-							: "Incremental build")); //$NON-NLS-1$
-			System.out
-					.println("-----------------------------------------------------------------"); //$NON-NLS-1$
+			final long endTime = System.currentTimeMillis();
+			if (DEBUG) {
+				log("Finished build of " + currentProject.getName() //$NON-NLS-1$
+						+ " @ " + new Date(endTime) + ", elapsed " + (endTime - startTime) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			}
+			if (TRACE) {
+				System.out
+						.println("-----SCRIPT-BUILDER-INFORMATION-TRACE----------------------------"); //$NON-NLS-1$
+				System.out.println("Finished build of project:" //$NON-NLS-1$
+						+ currentProject.getName() + "\n" //$NON-NLS-1$
+						+ "Building time:" //$NON-NLS-1$
+						+ Long.toString(endTime - startTime) + "\n" //$NON-NLS-1$
+						+ "Build type:" //$NON-NLS-1$
+						+ (kind == FULL_BUILD ? "Full build" //$NON-NLS-1$
+								: "Incremental build")); //$NON-NLS-1$
+				System.out
+						.println("-----------------------------------------------------------------"); //$NON-NLS-1$
+			}
 		}
 		monitor.done();
+		if (requiredProjects == null) {
+			requiredProjects = getRequiredProjects(true);
+		}
 		return requiredProjects;
 	}
 
+	private void cleanup() {
+		lastState = null;
+	}
+
 	private static boolean isProjectConfigChange(IResourceDelta projectDelta) {
 		final String[] filenames = {
 				IScriptProjectFilenames.BUILDPATH_FILENAME,
@@ -260,17 +272,21 @@
 		}
 	}
 
-	private class BuildState extends AbstractBuildState {
-		public BuildState(String projectName) {
-			super(projectName);
+	private static class BuildState extends AbstractBuildState {
+		final State state;
+
+		public BuildState(State state) {
+			super(state.scriptProjectName);
+			this.state = state;
 		}
 
 		public void recordImportProblem(IPath path) {
-			ScriptBuilder.this.lastState.recordImportProblem(path);
+			this.state.recordImportProblem(path);
 		}
 
-		public void recordDependency(IPath path, IPath dependency) {
-			ScriptBuilder.this.lastState.recordDependency(path, dependency);
+		public void recordDependency(IPath path, IPath dependency, int flags) {
+			Assert.isTrue(flags != 0);
+			this.state.recordDependency(path, dependency, flags);
 		}
 	}
 
@@ -404,8 +420,8 @@
 	protected static final String NONAME = ""; //$NON-NLS-1$
 
 	protected void fullBuild(final IProgressMonitor monitor) {
-		this.lastState = clearLastState();
-		final IBuildState buildState = new BuildState(currentProject.getName());
+		final State newState = clearLastState();
+		final IBuildState buildState = new BuildState(newState);
 		IScriptBuilder[] builders = null;
 		try {
 			monitor.setTaskName(NLS.bind(
@@ -440,14 +456,15 @@
 				}
 			}
 			saveBuilderVersions(builders);
-			updateExternalFolderLocations(buildChange);
+			updateExternalFolderLocations(newState, buildChange);
 		} catch (CoreException e) {
 			DLTKCore.error(e);
 		} finally {
 			resetBuilders(builders, buildState, monitor);
 			ModelManager.getModelManager().setLastBuiltState(currentProject,
-					this.lastState);
+					newState);
 			monitor.done();
+			this.lastState = null;
 		}
 	}
 
@@ -477,19 +494,12 @@
 	protected void incrementalBuild(IResourceDelta delta,
 			IProject[] requiredProjects, IProgressMonitor monitor)
 			throws CoreException {
-		State newState = new State(this);
+		final State newState = new State(this);
+		newState.copyFrom(this.lastState);
+		final Set<IPath> externalFoldersBefore = new HashSet<IPath>(
+				newState.getExternalFolders());
 
-		final Set<IPath> externalFoldersBefore;
-		if (this.lastState != null) {
-			newState.copyFrom(this.lastState);
-			externalFoldersBefore = new HashSet<IPath>(
-					newState.getExternalFolders());
-		} else {
-			externalFoldersBefore = new HashSet<IPath>();
-		}
-
-		this.lastState = newState;
-		final BuildState buildState = new BuildState(currentProject.getName());
+		final BuildState buildState = new BuildState(newState);
 		IScriptBuilder[] builders = null;
 		try {
 			monitor.setTaskName(NLS.bind(
@@ -504,7 +514,7 @@
 			IBuildChange buildChange = null;
 			if (isBuilderVersionChange(builders)) {
 				buildChange = new FullBuildChange(currentProject, monitor);
-				this.lastState.resetDependencies();
+				newState.resetDependencies();
 			}
 			if (buildChange == null) {
 				buildChange = createBuildChange(delta, requiredProjects,
@@ -517,35 +527,21 @@
 				builder.prepare(buildChange, buildState, monitor);
 				if (buildChange.getBuildType() == IScriptBuilder.FULL_BUILD
 						&& buildChange instanceof IncrementalBuildChange) {
+					if (TRACE) {
+						System.out.println("Full build requested by "
+								+ builder.getClass().getName());
+					}
 					buildChange = new FullBuildChange(currentProject, monitor);
-					this.lastState.resetDependencies();
+					newState.resetDependencies();
 				}
 			}
-			final Set<IPath> processed = new HashSet<IPath>();
-			final Set<IPath> queue = new HashSet<IPath>();
 			if (buildChange instanceof IncrementalBuildChange) {
 				final Set<IPath> changes = ((IncrementalBuildChange) buildChange)
 						.getChangedPaths();
 				if (TRACE) {
 					System.out.println("  Changes: " + changes);
 				}
-				queue.addAll(this.lastState.dependenciesOf(changes, true));
-				for (IProjectChange projectChange : buildChange
-						.getRequiredProjectChanges()) {
-					Collection<IPath> projectChanges = ((IncrementalProjectChange) projectChange)
-							.getChangedPaths();
-					final State projectState = getLastState(
-							projectChange.getProject(), monitor);
-					if (projectState != null) {
-						projectChanges = projectState
-								.allDependenciesOf(projectChanges);
-					}
-					queue.addAll(this.lastState.dependenciesOf(projectChanges,
-							false));
-				}
-				this.lastState.removeDependenciesFor(changes);
-				processed.addAll(changes);
-				queue.removeAll(processed);
+				newState.removeDependenciesFor(changes);
 			}
 			for (IScriptBuilder builder : builders) {
 				if (monitor.isCanceled()) {
@@ -561,52 +557,79 @@
 					}
 				}
 			}
-			while (!buildState.getStructuralChanges().isEmpty()
-					&& !queue.isEmpty()) {
-				if (TRACE) {
-					System.out.println("  Queue: " + queue);
+			newState.recordStructuralChanges(buildState.getStructuralChanges());
+			if (buildChange instanceof IncrementalBuildChange) {
+				final Set<IPath> processed = new HashSet<IPath>();
+				final Set<IPath> changes = ((IncrementalBuildChange) buildChange)
+						.getChangedPaths();
+				processed.addAll(changes);
+				final Set<IPath> queue = new HashSet<IPath>();
+				// TODO review cross-project dependency handling
+				for (IProjectChange projectChange : buildChange
+						.getRequiredProjectChanges()) {
+					final Collection<IPath> projectChanges = ((IncrementalProjectChange) projectChange)
+							.getChangedPaths();
+					final State projectState = getLastState(
+							projectChange.getProject(), monitor);
+					if (projectState != null) {
+						projectChanges.addAll(projectState
+								.getAllStructuralDependencies(projectChanges));
+					}
+					queue.addAll(this.lastState.dependenciesOf(projectChanges,
+							buildState.getStructuralChanges(), false));
 				}
-				final Set<IPath> nextQueue = new HashSet<IPath>();
-				nextQueue.addAll(this.lastState.dependenciesOf(queue, false));
-				this.lastState.removeDependenciesFor(queue);
 				final IWorkspaceRoot root = ResourcesPlugin.getWorkspace()
 						.getRoot();
-				final List<IFile> files = new ArrayList<IFile>();
-				for (IPath path : queue) {
-					files.add(root.getFile(path));
-				}
-				buildState.resetStructuralChanges();
-				final DependencyBuildChange qChange = new DependencyBuildChange(
-						currentProject, delta, files, monitor);
-				for (IScriptBuilder builder : builders) {
-					if (monitor.isCanceled()) {
-						throw new OperationCanceledException();
+				for (;;) {
+					queue.addAll(this.lastState.dependenciesOf(changes,
+							buildState.getStructuralChanges(), true));
+					queue.removeAll(processed);
+					if (queue.isEmpty()) {
+						break;
 					}
-					final long start = TRACE ? System.currentTimeMillis() : 0;
-					builder.build(qChange, buildState, monitor);
 					if (TRACE) {
-						final long elapsed = System.currentTimeMillis() - start;
-						if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) {
-							System.out.println(builder.getClass().getName()
-									+ " " + elapsed + "ms");
+						System.out.println("  Queue: " + queue);
+					}
+					newState.removeDependenciesFor(queue);
+					final List<IFile> files = new ArrayList<IFile>();
+					for (IPath path : queue) {
+						files.add(root.getFile(path));
+					}
+					buildState.resetStructuralChanges();
+					final DependencyBuildChange qChange = new DependencyBuildChange(
+							currentProject, delta, files, monitor);
+					for (IScriptBuilder builder : builders) {
+						if (monitor.isCanceled()) {
+							throw new OperationCanceledException();
+						}
+						final long start = TRACE ? System.currentTimeMillis()
+								: 0;
+						builder.build(qChange, buildState, monitor);
+						if (TRACE) {
+							final long elapsed = System.currentTimeMillis()
+									- start;
+							if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) {
+								System.out.println(builder.getClass().getName()
+										+ " " + elapsed + "ms");
+							}
 						}
 					}
+					changes.clear();
+					changes.addAll(queue);
+					processed.addAll(queue);
+					queue.clear();
 				}
-				processed.addAll(queue);
-				nextQueue.removeAll(processed);
-				queue.clear();
-				// TODO prevent cycles
-				queue.addAll(nextQueue);
 			}
 			saveBuilderVersions(builders);
-			updateExternalFolderLocations(buildChange);
+			updateExternalFolderLocations(newState, buildChange);
 		} catch (CoreException e) {
 			DLTKCore.error(e);
 		} finally {
 			resetBuilders(builders, buildState, monitor);
 			ModelManager.getModelManager().setLastBuiltState(currentProject,
-					this.lastState);
+					newState);
 			monitor.done();
+			this.lastState = null;
 		}
 	}
 
@@ -661,10 +684,10 @@
 						externalFoldersBefore));
 	}
 
-	private void updateExternalFolderLocations(IBuildChange buildChange)
-			throws CoreException {
-		this.lastState.externalFolderLocations.clear();
-		this.lastState.externalFolderLocations.addAll(buildChange
+	private static void updateExternalFolderLocations(State state,
+			IBuildChange buildChange) throws CoreException {
+		state.externalFolderLocations.clear();
+		state.externalFolderLocations.addAll(buildChange
 				.getExternalPaths(IProjectChange.DEFAULT));
 	}
 
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/State.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/State.java
index 7c0936f..05ff66d 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/State.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/builder/State.java
@@ -14,6 +14,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -27,6 +28,7 @@
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.dltk.compiler.util.SimpleLookupTable;
+import org.eclipse.dltk.core.builder.IBuildState;
 
 public class State {
 	// NOTE: this state cannot contain types that are not defined in this
@@ -37,21 +39,42 @@
 	int buildNumber;
 	long lastStructuralBuildTime;
 	SimpleLookupTable structuralBuildTimes;
+	Set<IPath> structuralChanges;
 
 	/**
-	 * 0x16 boolean noCleanExternalFolders is always present
+	 * <ul>
+	 * <li>0x16 boolean noCleanExternalFolders is always present
+	 * <li>0x17 dependencies
+	 * <li>0x18 dependencies + flags
+	 * </ul>
 	 **/
-	public static final byte VERSION = 0x0017;
+	public static final byte VERSION = 0x0018;
 
 	Set<IPath> externalFolderLocations = new HashSet<IPath>();
 
 	boolean noCleanExternalFolders = false;
 
+	static class DependencyInfo {
+		int flags;
+
+		public DependencyInfo() {
+		}
+
+		public DependencyInfo(DependencyInfo source) {
+			this.flags = source.flags;
+		}
+
+		@Override
+		public String toString() {
+			return String.valueOf(flags);
+		}
+	}
+
 	/**
 	 * Full (absolute,including project) path to the set of paths, depending on
 	 * it.
 	 */
-	private final Map<IPath, Set<IPath>> dependencies = new HashMap<IPath, Set<IPath>>();
+	private final Map<IPath, Map<IPath, DependencyInfo>> dependencies = new HashMap<IPath, Map<IPath, DependencyInfo>>();
 
 	private final Set<IPath> importProblems = new HashSet<IPath>();
 
@@ -86,6 +109,7 @@
 		this.buildNumber = lastState.buildNumber + 1;
 		this.lastStructuralBuildTime = lastState.lastStructuralBuildTime;
 		this.structuralBuildTimes = lastState.structuralBuildTimes;
+		this.structuralChanges = null;
 
 		this.externalFolderLocations.clear();
 		this.externalFolderLocations.addAll(lastState.externalFolderLocations);
@@ -100,6 +124,14 @@
 		return this.externalFolderLocations;
 	}
 
+	void recordStructuralChanges(Set<IPath> changes) {
+		if (changes != null && !changes.isEmpty()) {
+			this.structuralChanges = new HashSet<IPath>(changes);
+		} else {
+			this.structuralChanges = null;
+		}
+	}
+
 	static State read(IProject project, DataInputStream in) throws IOException {
 		if (ScriptBuilder.DEBUG)
 			System.out.println("About to read state " + project.getName()); //$NON-NLS-1$
@@ -132,9 +164,10 @@
 		final int dependencyCount = in.readInt();
 		newState.dependencies.clear();
 		for (int i = 0; i < dependencyCount; ++i) {
-			final Set<IPath> paths = new HashSet<IPath>();
-			newState.dependencies.put(Path.fromOSString(in.readUTF()), paths);
-			readPaths(in, paths);
+			final Map<IPath, DependencyInfo> paths = new HashMap<IPath, DependencyInfo>();
+			newState.dependencies.put(Path.fromPortableString(in.readUTF()),
+					paths);
+			readDependencyPaths(in, paths);
 		}
 		newState.importProblems.clear();
 		readPaths(in, newState.importProblems);
@@ -186,9 +219,10 @@
 		}
 		out.writeBoolean(this.noCleanExternalFolders);
 		out.writeInt(dependencies.size());
-		for (Map.Entry<IPath, Set<IPath>> entry : dependencies.entrySet()) {
+		for (Map.Entry<IPath, Map<IPath, DependencyInfo>> entry : dependencies
+				.entrySet()) {
 			out.writeUTF(entry.getKey().toPortableString());
-			writePaths(out, entry.getValue());
+			writeDependencyPaths(out, entry.getValue());
 		}
 		writePaths(out, importProblems);
 	}
@@ -209,6 +243,26 @@
 		}
 	}
 
+	private static void readDependencyPaths(DataInputStream in,
+			Map<IPath, DependencyInfo> paths) throws IOException {
+		final int pathCount = in.readInt();
+		for (int j = 0; j < pathCount; ++j) {
+			final IPath path = Path.fromPortableString(in.readUTF());
+			final DependencyInfo depInfo = new DependencyInfo();
+			depInfo.flags = in.readInt();
+			paths.put(path, depInfo);
+		}
+	}
+
+	private void writeDependencyPaths(DataOutputStream out,
+			Map<IPath, DependencyInfo> paths) throws IOException {
+		out.writeInt(paths.size());
+		for (Map.Entry<IPath, DependencyInfo> entry : paths.entrySet()) {
+			out.writeUTF(entry.getKey().toPortableString());
+			out.writeInt(entry.getValue().flags);
+		}
+	}
+
 	/**
 	 * Returns a string representation of the receiver.
 	 */
@@ -232,15 +286,20 @@
 		importProblems.add(path);
 	}
 
-	protected void recordDependency(IPath path, IPath dependency) {
+	protected void recordDependency(IPath path, IPath dependency, int flags) {
 		Assert.isLegal(scriptProjectName.equals(path.segment(0)));
 		Assert.isLegal(!path.equals(dependency));
-		Set<IPath> paths = dependencies.get(dependency);
+		Map<IPath, DependencyInfo> paths = dependencies.get(dependency);
 		if (paths == null) {
-			paths = new HashSet<IPath>();
+			paths = new HashMap<IPath, DependencyInfo>();
 			dependencies.put(dependency, paths);
 		}
-		paths.add(path);
+		DependencyInfo depInfo = paths.get(path);
+		if (depInfo == null) {
+			depInfo = new DependencyInfo();
+			paths.put(path, depInfo);
+		}
+		depInfo.flags |= flags;
 	}
 
 	protected void resetDependencies() {
@@ -249,46 +308,69 @@
 	}
 
 	protected void removeDependenciesFor(Set<IPath> paths) {
-		for (Iterator<Map.Entry<IPath, Set<IPath>>> i = dependencies.entrySet()
-				.iterator(); i.hasNext();) {
-			final Map.Entry<IPath, Set<IPath>> entry = i.next();
-			if (entry.getValue().removeAll(paths) && entry.getValue().isEmpty()) {
+		for (Iterator<Map.Entry<IPath, Map<IPath, DependencyInfo>>> i = dependencies
+				.entrySet().iterator(); i.hasNext();) {
+			final Map.Entry<IPath, Map<IPath, DependencyInfo>> entry = i.next();
+			if (entry.getValue().keySet().removeAll(paths)
+					&& entry.getValue().isEmpty()) {
 				i.remove();
 			}
 		}
 		importProblems.removeAll(paths);
 	}
 
-	protected Collection<IPath> dependenciesOf(Collection<IPath> paths,
-			boolean includeImportProblems) {
-		final Collection<IPath> result = new ArrayList<IPath>();
+	protected Set<IPath> dependenciesOf(Collection<IPath> paths,
+			Set<IPath> structuralChanges, boolean includeImportProblems) {
+		final Set<IPath> result = new HashSet<IPath>();
 		if (includeImportProblems) {
-			result.addAll(importProblems);
+			for (IPath path : importProblems) {
+				result.add(path);
+			}
 		}
 		for (IPath path : paths) {
-			final Set<IPath> deps = dependencies.get(path);
+			final boolean structuralChange = structuralChanges.contains(path);
+			final Map<IPath, DependencyInfo> deps = dependencies.get(path);
 			if (deps != null) {
-				result.addAll(deps);
+				for (Map.Entry<IPath, DependencyInfo> entry : deps.entrySet()) {
+					if (structuralChange
+							|| ((entry.getValue().flags & IBuildState.CONTENT) != 0)) {
+						result.add(entry.getKey());
+					}
+				}
 			}
 		}
 		return result;
 	}
 
-	protected Collection<IPath> allDependenciesOf(Collection<IPath> paths) {
-		final Collection<IPath> result = new HashSet<IPath>();
-		final List<IPath> queue = new ArrayList<IPath>();
-		while (!paths.isEmpty()) {
-			result.addAll(paths);
-			for (IPath path : paths) {
-				final Set<IPath> deps = dependencies.get(path);
+	protected Collection<IPath> getAllStructuralDependencies(
+			Collection<IPath> paths) {
+		if (structuralChanges == null) {
+			return Collections.emptyList();
+		}
+		final Set<IPath> result = new HashSet<IPath>();
+		result.addAll(paths);
+		result.retainAll(structuralChanges);
+		if (result.isEmpty()) {
+			return Collections.emptyList();
+		}
+		final List<IPath> queue = new ArrayList<IPath>(result);
+		while (!queue.isEmpty()) {
+			final List<IPath> nextQueue = new ArrayList<IPath>();
+			for (IPath path : queue) {
+				final Map<IPath, DependencyInfo> deps = dependencies.get(path);
 				if (deps != null) {
-					queue.addAll(deps);
+					for (Map.Entry<IPath, DependencyInfo> entry : deps
+							.entrySet()) {
+						if (!result.contains(entry.getKey())
+								&& ((entry.getValue().flags & IBuildState.STRUCTURAL) != 0)) {
+							nextQueue.add(entry.getKey());
+						}
+					}
 				}
 			}
-			queue.removeAll(result);
-			paths.clear();
-			paths.addAll(queue);
+			result.addAll(nextQueue);
 			queue.clear();
+			queue.addAll(nextQueue);
 		}
 		return result;
 	}