/*******************************************************************************
 * Copyright (c) 2000, 2003 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 Corporation - initial API and implementation
 *******************************************************************************/
package net.sourceforge.phpdt.internal.core.builder;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import net.sourceforge.phpdt.core.IClasspathEntry;
import net.sourceforge.phpdt.core.IJavaModelMarker;
import net.sourceforge.phpdt.core.JavaCore;
import net.sourceforge.phpdt.core.JavaModelException;
import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.internal.core.JavaModel;
import net.sourceforge.phpdt.internal.core.JavaModelManager;
import net.sourceforge.phpdt.internal.core.JavaProject;
import net.sourceforge.phpdt.internal.core.util.SimpleLookupTable;
import net.sourceforge.phpdt.internal.core.util.Util;
import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil;
import net.sourceforge.phpeclipse.PHPeclipsePlugin;
import net.sourceforge.phpeclipse.builder.IdentifierIndexManager;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;

public class PHPBuilder extends IncrementalProjectBuilder {
	IProject currentProject;

	JavaProject javaProject;

	IWorkspaceRoot workspaceRoot;

	NameEnvironment nameEnvironment;

	SimpleLookupTable binaryLocationsPerProject; // maps a project to its
													// binary

	// resources (output folders,
	// class folders, zip/jar files)
	State lastState;

	BuildNotifier notifier;

	char[][] extraResourceFileFilters;

	String[] extraResourceFolderFilters;

	public static final String CLASS_EXTENSION = "class"; //$NON-NLS-1$

	public static boolean DEBUG = false;

	/**
	 * A list of project names that have been built. This list is used to reset
	 * the JavaModel.existingExternalFiles cache when a build cycle begins so
	 * that deleted external jars are discovered.
	 */
	static ArrayList builtProjects = null;

	public static IMarker[] getProblemsFor(IResource resource) {
		try {
			if (resource != null && resource.exists())
				return resource.findMarkers(
						IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false,
						IResource.DEPTH_INFINITE);
		} catch (CoreException e) {
		} // assume there are no problems
		return new IMarker[0];
	}

	public static IMarker[] getTasksFor(IResource resource) {
		try {
			if (resource != null && resource.exists())
				return resource.findMarkers(IJavaModelMarker.TASK_MARKER,
						false, IResource.DEPTH_INFINITE);
		} catch (CoreException e) {
		} // assume there are no tasks
		return new IMarker[0];
	}

	public static void finishedBuilding(IResourceChangeEvent event) {
		BuildNotifier.resetProblemCounters();
	}

	/**
	 * Hook allowing to initialize some static state before a complete build
	 * iteration. This hook is invoked during PRE_AUTO_BUILD notification
	 */
	public static void buildStarting() {
		// do nothing
		// TODO (philippe) is it still needed?
	}

	/**
	 * Hook allowing to reset some static state after a complete build
	 * iteration. This hook is invoked during POST_AUTO_BUILD notification
	 */
	public static void buildFinished() {
		BuildNotifier.resetProblemCounters();
	}

	public static void removeProblemsFor(IResource resource) {
		try {
			if (resource != null && resource.exists())
				resource.deleteMarkers(
						IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false,
						IResource.DEPTH_INFINITE);
		} catch (CoreException e) {
		} // assume there were no problems
	}

	public static void removeTasksFor(IResource resource) {
		try {
			if (resource != null && resource.exists())
				resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false,
						IResource.DEPTH_INFINITE);
		} catch (CoreException e) {
		} // assume there were no problems
	}

	public static void removeProblemsAndTasksFor(IResource resource) {
		try {
			if (resource != null && resource.exists()) {
				resource.deleteMarkers(
						IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false,
						IResource.DEPTH_INFINITE);
				resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false,
						IResource.DEPTH_INFINITE);
			}
		} catch (CoreException e) {
		} // assume there were no problems
	}

	public static State readState(IProject project, DataInputStream in)
			throws IOException {
		return State.read(project, in);
	}

	public static void writeState(Object state, DataOutputStream out)
			throws IOException {
		((State) state).write(out);
	}

	public PHPBuilder() {
	}

	protected IProject[] build(int kind, Map ignored, IProgressMonitor monitor)
			throws CoreException {
		this.currentProject = getProject();
		if (currentProject == null || !currentProject.isAccessible())
			return new IProject[0];
		if (DEBUG)
			System.out
					.println("\nStarting build of " + currentProject.getName() //$NON-NLS-1$
							+ " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$
		this.notifier = new BuildNotifier(monitor, currentProject);
		notifier.begin();
		boolean ok = false;
		try {
			notifier.checkCancel();
			initializeBuilder();
			if (isWorthBuilding()) {
				if (kind == FULL_BUILD) {
					processFullPHPIndex(currentProject, monitor);
					buildAll();
				} else {
					if ((this.lastState = getLastState(currentProject)) == null) {
						if (DEBUG)
							System.out
									.println("Performing full build since last saved state was not found"); //$NON-NLS-1$
						processFullPHPIndex(currentProject, monitor);
						buildAll();
						// } else if (hasClasspathChanged()) {
						// // if the output location changes, do not delete the
						// binary files
						// // from old location
						// // the user may be trying something
						// buildAll();
					} else if (nameEnvironment.sourceLocations.length > 0) {
						// if there is no source to compile & no classpath
						// changes then we
						// are done
						SimpleLookupTable deltas = findDeltas();
						if (deltas == null)
							buildAll();
						else if (deltas.elementSize > 0)
							buildDeltas(deltas);
						else if (DEBUG)
							System.out
									.println("Nothing to build since deltas were empty"); //$NON-NLS-1$
					} else {
						if (hasStructuralDelta()) { // double check that a jar
													// file didn't
							// get replaced in a binary project
							processFullPHPIndex(currentProject, monitor);
							buildAll();
						} else {
							if (DEBUG)
								System.out
										.println("Nothing to build since there are no source folders and no deltas"); //$NON-NLS-1$
							lastState.tagAsNoopBuild();
						}
					}
				}
				ok = true;
			}
		} catch (CoreException e) {
			Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
			IMarker marker = currentProject
					.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			marker.setAttribute(IMarker.MESSAGE, Util.bind(
					"build.inconsistentProject", e.getLocalizedMessage())); //$NON-NLS-1$
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
		} catch (ImageBuilderInternalException e) {
			Util.log(e.getThrowable(),
					"JavaBuilder handling ImageBuilderInternalException"); //$NON-NLS-1$
			IMarker marker = currentProject
					.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			marker
					.setAttribute(
							IMarker.MESSAGE,
							Util
									.bind(
											"build.inconsistentProject", e.coreException.getLocalizedMessage())); //$NON-NLS-1$
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
		} catch (MissingClassFileException e) {
			// do not log this exception since its thrown to handle aborted
			// compiles
			// because of missing class files
			if (DEBUG)
				System.out.println(Util.bind("build.incompleteClassPath",
						e.missingClassFile)); //$NON-NLS-1$
			IMarker marker = currentProject
					.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			marker.setAttribute(IMarker.MESSAGE, Util.bind(
					"build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
		} catch (MissingSourceFileException e) {
			// do not log this exception since its thrown to handle aborted
			// compiles
			// because of missing source files
			if (DEBUG)
				System.out.println(Util.bind("build.missingSourceFile",
						e.missingSourceFile)); //$NON-NLS-1$
			removeProblemsAndTasksFor(currentProject); // make this the only
														// problem
			// for this project
			IMarker marker = currentProject
					.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			marker.setAttribute(IMarker.MESSAGE, Util.bind(
					"build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (!ok)
				// If the build failed, clear the previously built state,
				// forcing a
				// full build next time.
				clearLastState();
			notifier.done();
			cleanup();
		}
		IProject[] requiredProjects = getRequiredProjects(true);
		if (DEBUG)
			System.out.println("Finished build of " + currentProject.getName() //$NON-NLS-1$
					+ " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$
		return requiredProjects;
	}

	/**
	 * Performs a <code>FULL_BUILD</code> by visiting all nodes in the
	 * resource tree under the specified project.
	 * 
	 * @param iProject
	 */
	private void processFullPHPIndex(final IProject iProject,
			final IProgressMonitor monitor) {
		final IdentifierIndexManager indexManager = PHPeclipsePlugin
				.getDefault().getIndexManager(iProject);
		// Create resource visitor logic
		IResourceVisitor myVisitor = new IResourceVisitor() {
			public boolean visit(IResource resource) throws CoreException {
				if (resource.getType() == IResource.FILE) {
					if (monitor.isCanceled()) {
						throw new OperationCanceledException();
					}
					if ((resource.getFileExtension() != null)
							&& PHPFileUtil.isPHPFile((IFile) resource)) {
						monitor.worked(1);
						monitor.subTask("Parsing: " + resource.getFullPath());
						// update indexfile for the project:
						// PHPProject nature = (PHPProject) iProject
						// .getNature(PHPeclipsePlugin.PHP_NATURE_ID);
						indexManager.addFile((IFile) resource);
					}
				}
				return true;
			}
		};
		// Process the project using the visitor just created
		try {
			// if (iProject.hasNature(PHPeclipsePlugin.PHP_NATURE_ID)) {
			// thePHPProject = new PHPProject();
			// thePHPProject.setProject(iProject);
			// }
			indexManager.initialize();
			iProject.accept(myVisitor);
			indexManager.writeFile();
		} catch (CoreException e) {
			e.printStackTrace();
		}
	}

	private void buildAll() {
		notifier.checkCancel();
		notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$
		if (DEBUG && lastState != null)
			System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$
		clearLastState();
		BatchImageBuilder imageBuilder = new BatchImageBuilder(this);
		imageBuilder.build();
		recordNewState(imageBuilder.newState);
	}

	private void buildDeltas(SimpleLookupTable deltas) {
		notifier.checkCancel();
		notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$
		if (DEBUG && lastState != null)
			System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$
		clearLastState(); // clear the previously built state so if the build
		// fails, a full build will occur next time
		IncrementalImageBuilder imageBuilder = new IncrementalImageBuilder(this);
		if (imageBuilder.build(deltas))
			recordNewState(imageBuilder.newState);
		else {
			processFullPHPIndex(currentProject, notifier.monitor);
			buildAll();
		}
	}

	private void cleanup() {
		this.nameEnvironment = null;
		this.binaryLocationsPerProject = null;
		this.lastState = null;
		this.notifier = null;
		this.extraResourceFileFilters = null;
		this.extraResourceFolderFilters = null;
	}

	private void clearLastState() {
		JavaModelManager.getJavaModelManager().setLastBuiltState(
				currentProject, null);
	}

	boolean filterExtraResource(IResource resource) {
		if (extraResourceFileFilters != null) {
			char[] name = resource.getName().toCharArray();
			for (int i = 0, l = extraResourceFileFilters.length; i < l; i++)
				if (CharOperation
						.match(extraResourceFileFilters[i], name, true))
					return true;
		}
		if (extraResourceFolderFilters != null) {
			IPath path = resource.getProjectRelativePath();
			String pathName = path.toString();
			int count = path.segmentCount();
			if (resource.getType() == IResource.FILE)
				count--;
			for (int i = 0, l = extraResourceFolderFilters.length; i < l; i++)
				if (pathName.indexOf(extraResourceFolderFilters[i]) != -1)
					for (int j = 0; j < count; j++)
						if (extraResourceFolderFilters[i].equals(path
								.segment(j)))
							return true;
		}
		return false;
	}

	private SimpleLookupTable findDeltas() {
		notifier.subTask(Util.bind(
				"build.readingDelta", currentProject.getName())); //$NON-NLS-1$
		IResourceDelta delta = getDelta(currentProject);
		SimpleLookupTable deltas = new SimpleLookupTable(3);
		if (delta != null) {
			if (delta.getKind() != IResourceDelta.NO_CHANGE) {
				if (DEBUG)
					System.out.println("Found source delta for: "
							+ currentProject.getName()); //$NON-NLS-1$
				deltas.put(currentProject, delta);
			}
		} else {
			if (DEBUG)
				System.out
						.println("Missing delta for: " + currentProject.getName()); //$NON-NLS-1$
			notifier.subTask(""); //$NON-NLS-1$
			return null;
		}
		Object[] keyTable = binaryLocationsPerProject.keyTable;
		Object[] valueTable = binaryLocationsPerProject.valueTable;
		nextProject: for (int i = 0, l = keyTable.length; i < l; i++) {
			IProject p = (IProject) keyTable[i];
			if (p != null && p != currentProject) {
				State s = getLastState(p);
				if (!lastState.wasStructurallyChanged(p, s)) { // see if we can
																// skip
					// its delta
					if (s.wasNoopBuild())
						continue nextProject; // project has no source folders
												// and can be
					// skipped
					// ClasspathLocation[] classFoldersAndJars =
					// (ClasspathLocation[])
					// valueTable[i];
					boolean canSkip = true;
					// for (int j = 0, m = classFoldersAndJars.length; j < m;
					// j++) {
					// if (classFoldersAndJars[j].isOutputFolder())
					// classFoldersAndJars[j] = null; // can ignore output
					// folder since
					// project was not structurally changed
					// else
					// canSkip = false;
					// }
					if (canSkip)
						continue nextProject; // project has no structural
												// changes in its
					// output folders
				}
				notifier.subTask(Util.bind("build.readingDelta", p.getName())); //$NON-NLS-1$
				delta = getDelta(p);
				if (delta != null) {
					if (delta.getKind() != IResourceDelta.NO_CHANGE) {
						if (DEBUG)
							System.out
									.println("Found binary delta for: " + p.getName()); //$NON-NLS-1$
						deltas.put(p, delta);
					}
				} else {
					if (DEBUG)
						System.out.println("Missing delta for: " + p.getName()); //$NON-NLS-1$
					notifier.subTask(""); //$NON-NLS-1$
					return null;
				}
			}
		}
		notifier.subTask(""); //$NON-NLS-1$
		return deltas;
	}

	private State getLastState(IProject project) {
		return (State) JavaModelManager.getJavaModelManager()
				.getLastBuiltState(project, notifier.monitor);
	}

	/*
	 * Return the list of projects for which it requires a resource delta. This
	 * builder's project is implicitly included and need not be specified.
	 * Builders must re-specify the list of interesting projects every time they
	 * are run as this is not carried forward beyond the next build. Missing
	 * projects should be specified but will be ignored until they are added to
	 * the workspace.
	 */
	private IProject[] getRequiredProjects(boolean includeBinaryPrerequisites) {
		if (javaProject == null || workspaceRoot == null)
			return new IProject[0];
		ArrayList projects = new ArrayList();
		try {
			IClasspathEntry[] entries = javaProject.getExpandedClasspath(true);
			for (int i = 0, l = entries.length; i < l; i++) {
				IClasspathEntry entry = entries[i];
				IPath path = entry.getPath();
				IProject p = null;
				switch (entry.getEntryKind()) {
				case IClasspathEntry.CPE_PROJECT:
					p = workspaceRoot.getProject(path.lastSegment()); // missing
					// projects are
					// considered too
					break;
				case IClasspathEntry.CPE_LIBRARY:
					if (includeBinaryPrerequisites && path.segmentCount() > 1) {
						// some binary resources on the class path can come from
						// projects
						// that are not included in the project references
						IResource resource = workspaceRoot.findMember(path
								.segment(0));
						if (resource instanceof IProject)
							p = (IProject) resource;
					}
				}
				if (p != null && !projects.contains(p))
					projects.add(p);
			}
		} catch (JavaModelException e) {
			return new IProject[0];
		}
		IProject[] result = new IProject[projects.size()];
		projects.toArray(result);
		return result;
	}

	// private boolean hasClasspathChanged() {
	// ClasspathMultiDirectory[] newSourceLocations =
	// nameEnvironment.sourceLocations;
	// ClasspathMultiDirectory[] oldSourceLocations = lastState.sourceLocations;
	// int newLength = newSourceLocations.length;
	// int oldLength = oldSourceLocations.length;
	// int n, o;
	// for (n = o = 0; n < newLength && o < oldLength; n++, o++) {
	// if (newSourceLocations[n].equals(oldSourceLocations[o]))
	// continue; // checks source & output folders
	// try {
	// if (newSourceLocations[n].sourceFolder.members().length == 0) { // added
	// // new
	// // empty
	// // source
	// // folder
	// o--;
	// continue;
	// }
	// } catch (CoreException ignore) {
	// }
	// if (DEBUG)
	// System.out.println(newSourceLocations[n] + " != "
	// + oldSourceLocations[o]); //$NON-NLS-1$
	// return true;
	// }
	// while (n < newLength) {
	// try {
	// if (newSourceLocations[n].sourceFolder.members().length == 0) { // added
	// // new
	// // empty
	// // source
	// // folder
	// n++;
	// continue;
	// }
	// } catch (CoreException ignore) {
	// }
	// if (DEBUG)
	// System.out.println("Added non-empty source folder"); //$NON-NLS-1$
	// return true;
	// }
	// if (o < oldLength) {
	// if (DEBUG)
	// System.out.println("Removed source folder"); //$NON-NLS-1$
	// return true;
	// }
	// // ClasspathLocation[] newBinaryLocations =
	// // nameEnvironment.binaryLocations;
	// // ClasspathLocation[] oldBinaryLocations = lastState.binaryLocations;
	// // newLength = newBinaryLocations.length;
	// // oldLength = oldBinaryLocations.length;
	// // for (n = o = 0; n < newLength && o < oldLength; n++, o++) {
	// // if (newBinaryLocations[n].equals(oldBinaryLocations[o])) continue;
	// // if (DEBUG)
	// // System.out.println(newBinaryLocations[n] + " != " +
	// // oldBinaryLocations[o]); //$NON-NLS-1$
	// // return true;
	// // }
	// // if (n < newLength || o < oldLength) {
	// // if (DEBUG)
	// // System.out.println("Number of binary folders/jar files has changed");
	// // //$NON-NLS-1$
	// // return true;
	// // }
	// return false;
	// }
	private boolean hasStructuralDelta() {
		// handle case when currentProject has only .class file folders and/or
		// jar
		// files... no source/output folders
		IResourceDelta delta = getDelta(currentProject);
		if (delta != null && delta.getKind() != IResourceDelta.NO_CHANGE) {
			// ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[])
			// binaryLocationsPerProject.get(currentProject);
			// if (classFoldersAndJars != null) {
			// for (int i = 0, l = classFoldersAndJars.length; i < l; i++) {
			// ClasspathLocation classFolderOrJar = classFoldersAndJars[i]; //
			// either
			// a .class file folder or a zip/jar file
			// if (classFolderOrJar != null) {
			// IPath p = classFolderOrJar.getProjectRelativePath();
			// if (p != null) {
			// IResourceDelta binaryDelta = delta.findMember(p);
			// if (binaryDelta != null && binaryDelta.getKind() !=
			// IResourceDelta.NO_CHANGE)
			// return true;
			// }
			// }
			// }
			// }
		}
		return false;
	}

	private void initializeBuilder() throws CoreException {
		this.javaProject = (JavaProject) JavaCore.create(currentProject);
		this.workspaceRoot = currentProject.getWorkspace().getRoot();
		// Flush the existing external files cache if this is the beginning of a
		// build cycle
		String projectName = currentProject.getName();
		if (builtProjects == null || builtProjects.contains(projectName)) {
			JavaModel.flushExternalFileCache();
			builtProjects = new ArrayList();
		}
		builtProjects.add(projectName);
		this.binaryLocationsPerProject = new SimpleLookupTable(3);
		this.nameEnvironment = new NameEnvironment(workspaceRoot, javaProject,
				binaryLocationsPerProject);
		String filterSequence = javaProject.getOption(
				JavaCore.CORE_JAVA_BUILD_RESOURCE_COPY_FILTER, true);
		char[][] filters = filterSequence != null
				&& filterSequence.length() > 0 ? CharOperation.splitAndTrimOn(
				',', filterSequence.toCharArray()) : null;
		if (filters == null) {
			this.extraResourceFileFilters = null;
			this.extraResourceFolderFilters = null;
		} else {
			int fileCount = 0, folderCount = 0;
			for (int i = 0, l = filters.length; i < l; i++) {
				char[] f = filters[i];
				if (f.length == 0)
					continue;
				if (f[f.length - 1] == '/')
					folderCount++;
				else
					fileCount++;
			}
			this.extraResourceFileFilters = new char[fileCount][];
			this.extraResourceFolderFilters = new String[folderCount];
			for (int i = 0, l = filters.length; i < l; i++) {
				char[] f = filters[i];
				if (f.length == 0)
					continue;
				if (f[f.length - 1] == '/')
					extraResourceFolderFilters[--folderCount] = new String(
							CharOperation.subarray(f, 0, f.length - 1));
				else
					extraResourceFileFilters[--fileCount] = f;
			}
		}
	}

	private boolean isClasspathBroken(IClasspathEntry[] classpath, IProject p)
			throws CoreException {
		if (classpath == JavaProject.INVALID_CLASSPATH) // the .classpath file
			// could not be read
			return true;
		IMarker[] markers = p.findMarkers(
				IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false,
				IResource.DEPTH_ZERO);
		for (int i = 0, l = markers.length; i < l; i++)
			if (((Integer) markers[i].getAttribute(IMarker.SEVERITY))
					.intValue() == IMarker.SEVERITY_ERROR)
				return true;
		return false;
	}

	private boolean isWorthBuilding() throws CoreException {
		boolean abortBuilds = JavaCore.ABORT.equals(javaProject.getOption(
				JavaCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, true));
		if (!abortBuilds)
			return true;
		// Abort build only if there are classpath errors
		// if (isClasspathBroken(javaProject.getRawClasspath(), currentProject))
		// {
		// if (DEBUG)
		// System.out.println("Aborted build because project has classpath
		// errors
		// (incomplete or involved in cycle)"); //$NON-NLS-1$
		//
		// JavaModelManager.getJavaModelManager().deltaProcessor.addForRefresh(javaProject);
		//
		// removeProblemsAndTasksFor(currentProject); // remove all compilation
		// problems
		//
		// IMarker marker =
		// currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
		// marker.setAttribute(IMarker.MESSAGE,
		// ProjectPrefUtil.bind("build.abortDueToClasspathProblems"));
		// //$NON-NLS-1$
		// marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
		// return false;
		// }
		// make sure all prereq projects have valid build states... only when
		// aborting builds since projects in cycles do not have build states
		// except for projects involved in a 'warning' cycle (see below)
		IProject[] requiredProjects = getRequiredProjects(false);
		next: for (int i = 0, l = requiredProjects.length; i < l; i++) {
			IProject p = requiredProjects[i];
			if (getLastState(p) == null) {
				// The prereq project has no build state: if this prereq project
				// has a
				// 'warning' cycle marker then allow build (see bug id 23357)
				JavaProject prereq = (JavaProject) JavaCore.create(p);
				if (prereq.hasCycleMarker()
						&& JavaCore.WARNING.equals(javaProject.getOption(
								JavaCore.CORE_CIRCULAR_CLASSPATH, true)))
					continue;
				if (DEBUG)
					System.out.println("Aborted build because prereq project "
							+ p.getName() //$NON-NLS-1$
							+ " was not built"); //$NON-NLS-1$
				removeProblemsAndTasksFor(currentProject); // make this the
															// only
				// problem for this project
				IMarker marker = currentProject
						.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
				marker
						.setAttribute(
								IMarker.MESSAGE,
								isClasspathBroken(prereq.getRawClasspath(), p) ? Util
										.bind(
												"build.prereqProjectHasClasspathProblems", p.getName()) //$NON-NLS-1$
										: Util
												.bind(
														"build.prereqProjectMustBeRebuilt", p.getName())); //$NON-NLS-1$
				marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
				return false;
			}
		}
		return true;
	}

	/*
	 * Instruct the build manager that this project is involved in a cycle and
	 * needs to propagate structural changes to the other projects in the cycle.
	 */
	void mustPropagateStructuralChanges() {
		HashSet cycleParticipants = new HashSet(3);
		javaProject.updateCycleParticipants(null, new ArrayList(),
				cycleParticipants, workspaceRoot, new HashSet(3));
		IPath currentPath = javaProject.getPath();
		Iterator i = cycleParticipants.iterator();
		while (i.hasNext()) {
			IPath participantPath = (IPath) i.next();
			if (participantPath != currentPath) {
				IProject project = this.workspaceRoot
						.getProject(participantPath.segment(0));
				if (hasBeenBuilt(project)) {
					if (DEBUG)
						System.out
								.println("Requesting another build iteration since cycle participant "
										+ project.getName() //$NON-NLS-1$
										+ " has not yet seen some structural changes"); //$NON-NLS-1$
					needRebuild();
					return;
				}
			}
		}
	}

	private void recordNewState(State state) {
		Object[] keyTable = binaryLocationsPerProject.keyTable;
		for (int i = 0, l = keyTable.length; i < l; i++) {
			IProject prereqProject = (IProject) keyTable[i];
			if (prereqProject != null && prereqProject != currentProject)
				state.recordStructuralDependency(prereqProject,
						getLastState(prereqProject));
		}
		if (DEBUG)
			System.out.println("Recording new state : " + state); //$NON-NLS-1$
		// state.dump();
		JavaModelManager.getJavaModelManager().setLastBuiltState(
				currentProject, state);
	}

	/**
	 * String representation for debugging purposes
	 */
	public String toString() {
		return currentProject == null ? "JavaBuilder for unknown project" //$NON-NLS-1$
				: "JavaBuilder for " + currentProject.getName(); //$NON-NLS-1$
	}
}
