/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.builder;

import org.eclipse.core.runtime.*;
import org.eclipse.core.resources.*;

import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.*;
import org.eclipse.jdt.internal.compiler.*;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.problem.*;
import org.eclipse.jdt.internal.compiler.util.SimpleSet;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;

import java.io.*;
import java.util.*;

/**
 * The abstract superclass of Java builders.
 * Provides the building and compilation mechanism
 * in common with the batch and incremental builders.
 */
public abstract class AbstractImageBuilder implements ICompilerRequestor, ICompilationUnitLocator {

protected JavaBuilder javaBuilder;
protected State newState;

// local copies
protected NameEnvironment nameEnvironment;
protected ClasspathMultiDirectory[] sourceLocations;
protected BuildNotifier notifier;

protected Compiler compiler;
protected WorkQueue workQueue;
protected ArrayList problemSourceFiles;
protected boolean compiledAllAtOnce;

private boolean inCompiler;

protected boolean keepStoringProblemMarkers;
protected SimpleSet filesWithAnnotations = null;

public static int MAX_AT_ONCE = 2000; // best compromise between space used and speed
public final static String[] JAVA_PROBLEM_MARKER_ATTRIBUTE_NAMES = {
	IMarker.MESSAGE,
	IMarker.SEVERITY,
	IJavaModelMarker.ID,
	IMarker.CHAR_START,
	IMarker.CHAR_END,
	IMarker.LINE_NUMBER,
	IJavaModelMarker.ARGUMENTS,
	IJavaModelMarker.CATEGORY_ID,
};
public final static String[] JAVA_TASK_MARKER_ATTRIBUTE_NAMES = {
	IMarker.MESSAGE,
	IMarker.PRIORITY,
	IJavaModelMarker.ID,
	IMarker.CHAR_START,
	IMarker.CHAR_END,
	IMarker.LINE_NUMBER,
	IMarker.USER_EDITABLE,
	IMarker.SOURCE_ID,
};
public final static Integer S_ERROR = new Integer(IMarker.SEVERITY_ERROR);
public final static Integer S_WARNING = new Integer(IMarker.SEVERITY_WARNING);
public final static Integer P_HIGH = new Integer(IMarker.PRIORITY_HIGH);
public final static Integer P_NORMAL = new Integer(IMarker.PRIORITY_NORMAL);
public final static Integer P_LOW = new Integer(IMarker.PRIORITY_LOW);

protected AbstractImageBuilder(JavaBuilder javaBuilder, boolean buildStarting, State newState) {
	// local copies
	this.javaBuilder = javaBuilder;
	this.nameEnvironment = javaBuilder.nameEnvironment;
	this.sourceLocations = this.nameEnvironment.sourceLocations;
	this.notifier = javaBuilder.notifier;
	this.keepStoringProblemMarkers = true; // may get disabled when missing classfiles are encountered

	if (buildStarting) {
		this.newState = newState == null ? new State(javaBuilder) : newState;
		this.compiler = newCompiler();
		this.workQueue = new WorkQueue();
		this.problemSourceFiles = new ArrayList(3);

		if (this.javaBuilder.participants != null) {
			for (int i = 0, l = this.javaBuilder.participants.length; i < l; i++) {
				if (this.javaBuilder.participants[i].isAnnotationProcessor()) {
					// initialize this set so the builder knows to gather CUs that define Annotation types
					// each Annotation processor participant is then asked to process these files AFTER
					// the compile loop. The normal dependency loop will then recompile all affected types
					this.filesWithAnnotations = new SimpleSet(1);
					break;
				}
			}
		}
	}
}

public void acceptResult(CompilationResult result) {
	// In Batch mode, we write out the class files, hold onto the dependency info
	// & additional types and report problems.

	// In Incremental mode, when writing out a class file we need to compare it
	// against the previous file, remembering if structural changes occured.
	// Before reporting the new problems, we need to update the problem count &
	// remove the old problems. Plus delete additional class files that no longer exist.

	SourceFile compilationUnit = (SourceFile) result.getCompilationUnit(); // go directly back to the sourceFile
	if (!this.workQueue.isCompiled(compilationUnit)) {
		this.workQueue.finished(compilationUnit);

		try {
			updateProblemsFor(compilationUnit, result); // record compilation problems before potentially adding duplicate errors
			updateTasksFor(compilationUnit, result); // record tasks
		} catch (CoreException e) {
			throw internalException(e);
		}

		if (result.hasInconsistentToplevelHierarchies)
			// ensure that this file is always retrieved from source for the rest of the build
			if (!this.problemSourceFiles.contains(compilationUnit))
				this.problemSourceFiles.add(compilationUnit);

		IType mainType = null;
		String mainTypeName = null;
		String typeLocator = compilationUnit.typeLocator();
		ClassFile[] classFiles = result.getClassFiles();
		int length = classFiles.length;
		ArrayList duplicateTypeNames = null;
		ArrayList definedTypeNames = new ArrayList(length);
		for (int i = 0; i < length; i++) {
			ClassFile classFile = classFiles[i];

			char[][] compoundName = classFile.getCompoundName();
			char[] typeName = compoundName[compoundName.length - 1];
			boolean isNestedType = classFile.isNestedType;

			// Look for a possible collision, if one exists, report an error but do not write the class file
			if (isNestedType) {
				String qualifiedTypeName = new String(classFile.outerMostEnclosingClassFile().fileName());
				if (this.newState.isDuplicateLocator(qualifiedTypeName, typeLocator))
					continue;
			} else {
				String qualifiedTypeName = new String(classFile.fileName()); // the qualified type name "p1/p2/A"
				if (this.newState.isDuplicateLocator(qualifiedTypeName, typeLocator)) {
					if (duplicateTypeNames == null)
						duplicateTypeNames = new ArrayList();
					duplicateTypeNames.add(compoundName);
					if (mainType == null) {
						try {
							mainTypeName = compilationUnit.initialTypeName; // slash separated qualified name "p1/p1/A"
							mainType = this.javaBuilder.javaProject.findType(mainTypeName.replace('/', '.'));
						} catch (JavaModelException e) {
							// ignore
						}
					}
					IType type;
					if (qualifiedTypeName.equals(mainTypeName)) {
						type = mainType;
					} else {
						String simpleName = qualifiedTypeName.substring(qualifiedTypeName.lastIndexOf('/')+1);
						type = mainType == null ? null : mainType.getCompilationUnit().getType(simpleName);
					}
					createProblemFor(compilationUnit.resource, type, Messages.bind(Messages.build_duplicateClassFile, new String(typeName)), JavaCore.ERROR);
					continue;
				}
				this.newState.recordLocatorForType(qualifiedTypeName, typeLocator);
				if (result.checkSecondaryTypes && !qualifiedTypeName.equals(compilationUnit.initialTypeName))
					acceptSecondaryType(classFile);
			}
			try {
				definedTypeNames.add(writeClassFile(classFile, compilationUnit, !isNestedType));
			} catch (CoreException e) {
				Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
				if (e.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS)
					createProblemFor(compilationUnit.resource, null, Messages.bind(Messages.build_classFileCollision, e.getMessage()), JavaCore.ERROR);
				else
					createProblemFor(compilationUnit.resource, null, Messages.build_inconsistentClassFile, JavaCore.ERROR);
			}
		}
		if (result.hasAnnotations && this.filesWithAnnotations != null) // only initialized if an annotation processor is attached
			this.filesWithAnnotations.add(compilationUnit);

		this.compiler.lookupEnvironment.releaseClassFiles(classFiles);
		finishedWith(typeLocator, result, compilationUnit.getMainTypeName(), definedTypeNames, duplicateTypeNames);
		this.notifier.compiled(compilationUnit);
	}
}

protected void acceptSecondaryType(ClassFile classFile) {
	// noop
}

protected void addAllSourceFiles(final ArrayList sourceFiles) throws CoreException {
	for (int i = 0, l = this.sourceLocations.length; i < l; i++) {
		final ClasspathMultiDirectory sourceLocation = this.sourceLocations[i];
		final char[][] exclusionPatterns = sourceLocation.exclusionPatterns;
		final char[][] inclusionPatterns = sourceLocation.inclusionPatterns;
		final boolean isAlsoProject = sourceLocation.sourceFolder.equals(this.javaBuilder.currentProject);
		final int segmentCount = sourceLocation.sourceFolder.getFullPath().segmentCount();
		final IContainer outputFolder = sourceLocation.binaryFolder;
		final boolean isOutputFolder = sourceLocation.sourceFolder.equals(outputFolder);
		sourceLocation.sourceFolder.accept(
			new IResourceProxyVisitor() {
				public boolean visit(IResourceProxy proxy) throws CoreException {
					switch(proxy.getType()) {
						case IResource.FILE :
							if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(proxy.getName())) {
								IResource resource = proxy.requestResource();
								if (exclusionPatterns != null || inclusionPatterns != null)
									if (Util.isExcluded(resource.getFullPath(), inclusionPatterns, exclusionPatterns, false))
										return false;
								sourceFiles.add(new SourceFile((IFile) resource, sourceLocation));
							}
							return false;
						case IResource.FOLDER :
							IPath folderPath = null;
							if (isAlsoProject)
								if (isExcludedFromProject(folderPath = proxy.requestFullPath()))
									return false;
							if (exclusionPatterns != null) {
								if (folderPath == null)
									folderPath = proxy.requestFullPath();
								if (Util.isExcluded(folderPath, inclusionPatterns, exclusionPatterns, true)) {
									// must walk children if inclusionPatterns != null, can skip them if == null
									// but folder is excluded so do not create it in the output folder
									return inclusionPatterns != null;
								}
							}
							if (!isOutputFolder) {
								if (folderPath == null)
									folderPath = proxy.requestFullPath();
								String packageName = folderPath.lastSegment();
								if (packageName.length() > 0) {
									String sourceLevel = AbstractImageBuilder.this.javaBuilder.javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
									String complianceLevel = AbstractImageBuilder.this.javaBuilder.javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
									if (JavaConventions.validatePackageName(packageName, sourceLevel, complianceLevel).getSeverity() != IStatus.ERROR)
										createFolder(folderPath.removeFirstSegments(segmentCount), outputFolder);
								}
							}
					}
					return true;
				}
			},
			IResource.NONE
		);
		this.notifier.checkCancel();
	}
}

protected void cleanUp() {
	this.nameEnvironment.cleanup();

	this.javaBuilder = null;
	this.nameEnvironment = null;
	this.sourceLocations = null;
	this.notifier = null;
	this.compiler = null;
	this.workQueue = null;
	this.problemSourceFiles = null;
}

/* Compile the given elements, adding more elements to the work queue
* if they are affected by the changes.
*/
protected void compile(SourceFile[] units) {
	if (this.filesWithAnnotations != null && this.filesWithAnnotations.elementSize > 0)
		// will add files that have annotations in acceptResult() & then processAnnotations() before exitting this method
		this.filesWithAnnotations.clear();

	// notify CompilationParticipants which source files are about to be compiled
	CompilationParticipantResult[] participantResults = this.javaBuilder.participants == null ? null : notifyParticipants(units);
	if (participantResults != null && participantResults.length > units.length) {
		units = new SourceFile[participantResults.length];
		for (int i = participantResults.length; --i >= 0;)
			units[i] = participantResults[i].sourceFile;
	}

	int unitsLength = units.length;
	this.compiledAllAtOnce = unitsLength <= MAX_AT_ONCE;
	if (this.compiledAllAtOnce) {
		// do them all now
		if (JavaBuilder.DEBUG)
			for (int i = 0; i < unitsLength; i++)
				System.out.println("About to compile " + units[i].typeLocator()); //$NON-NLS-1$
		compile(units, null, true);
	} else {
		SourceFile[] remainingUnits = new SourceFile[unitsLength]; // copy of units, removing units when about to compile
		System.arraycopy(units, 0, remainingUnits, 0, unitsLength);
		int doNow = unitsLength < MAX_AT_ONCE ? unitsLength : MAX_AT_ONCE;
		SourceFile[] toCompile = new SourceFile[doNow];
		int remainingIndex = 0;
		boolean compilingFirstGroup = true;
		while (remainingIndex < unitsLength) {
			int count = 0;
			while (remainingIndex < unitsLength && count < doNow) {
				// Although it needed compiling when this method was called, it may have
				// already been compiled when it was referenced by another unit.
				SourceFile unit = remainingUnits[remainingIndex];
				if (unit != null && (compilingFirstGroup || this.workQueue.isWaiting(unit))) {
					if (JavaBuilder.DEBUG)
						System.out.println("About to compile #" + remainingIndex + " : "+ unit.typeLocator()); //$NON-NLS-1$ //$NON-NLS-2$
					toCompile[count++] = unit;
				}
				remainingUnits[remainingIndex++] = null;
			}
			if (count < doNow)
				System.arraycopy(toCompile, 0, toCompile = new SourceFile[count], 0, count);
			if (!compilingFirstGroup)
				for (int a = remainingIndex; a < unitsLength; a++)
					if (remainingUnits[a] != null && this.workQueue.isCompiled(remainingUnits[a]))
						remainingUnits[a] = null; // use the class file for this source file since its been compiled
			compile(toCompile, remainingUnits, compilingFirstGroup);
			compilingFirstGroup = false;
		}
	}

	if (participantResults != null) {
		for (int i = participantResults.length; --i >= 0;)
			if (participantResults[i] != null)
				recordParticipantResult(participantResults[i]);

		processAnnotations(participantResults);
	}
}

protected void compile(SourceFile[] units, SourceFile[] additionalUnits, boolean compilingFirstGroup) {
	if (units.length == 0) return;
	this.notifier.aboutToCompile(units[0]); // just to change the message

	// extend additionalFilenames with all hierarchical problem types found during this entire build
	if (!this.problemSourceFiles.isEmpty()) {
		int toAdd = this.problemSourceFiles.size();
		int length = additionalUnits == null ? 0 : additionalUnits.length;
		if (length == 0)
			additionalUnits = new SourceFile[toAdd];
		else
			System.arraycopy(additionalUnits, 0, additionalUnits = new SourceFile[length + toAdd], 0, length);
		for (int i = 0; i < toAdd; i++)
			additionalUnits[length + i] = (SourceFile) this.problemSourceFiles.get(i);
	}
	String[] initialTypeNames = new String[units.length];
	for (int i = 0, l = units.length; i < l; i++)
		initialTypeNames[i] = units[i].initialTypeName;
	this.nameEnvironment.setNames(initialTypeNames, additionalUnits);
	this.notifier.checkCancel();
	try {
		this.inCompiler = true;
		this.compiler.compile(units);
	} catch (AbortCompilation ignored) {
		// ignore the AbortCompilcation coming from BuildNotifier.checkCancelWithinCompiler()
		// the Compiler failed after the user has chose to cancel... likely due to an OutOfMemory error
	} finally {
		this.inCompiler = false;
	}
	// Check for cancel immediately after a compile, because the compiler may
	// have been cancelled but without propagating the correct exception
	this.notifier.checkCancel();
}

protected void copyResource(IResource source, IResource destination) throws CoreException {
	IPath destPath = destination.getFullPath();
	try {
		source.copy(destPath, IResource.FORCE | IResource.DERIVED, null);
	} catch (CoreException e) {
		// handle the case when the source resource is deleted
		source.refreshLocal(0, null);
		if (!source.exists()) return; // source resource was deleted so skip it
		throw e;
	}
	Util.setReadOnly(destination, false); // just in case the original was read only
}

protected void createProblemFor(IResource resource, IMember javaElement, String message, String problemSeverity) {
	try {
		IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
		int severity = problemSeverity.equals(JavaCore.WARNING) ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_ERROR;

		ISourceRange range = null;
		if (javaElement != null) {
			try {
				range = javaElement.getNameRange();
			} catch (JavaModelException e) {
				if (e.getJavaModelStatus().getCode() != IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST) {
					throw e;
				}
				if (!CharOperation.equals(javaElement.getElementName().toCharArray(), TypeConstants.PACKAGE_INFO_NAME)) {
					throw e;
				}
				// else silently swallow the exception as the synthetic interface type package-info has no
				// source range really. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=258145
			}
		}
		int start = range == null ? 0 : range.getOffset();
		int end = range == null ? 1 : start + range.getLength();
		marker.setAttributes(
			new String[] {IMarker.MESSAGE, IMarker.SEVERITY, IMarker.CHAR_START, IMarker.CHAR_END, IMarker.SOURCE_ID},
			new Object[] {message, new Integer(severity), new Integer(start), new Integer(end), JavaBuilder.SOURCE_ID});
	} catch (CoreException e) {
		throw internalException(e);
	}
}

protected void deleteGeneratedFiles(IFile[] deletedGeneratedFiles) {
	// no op by default
}

protected SourceFile findSourceFile(IFile file, boolean mustExist) {
	if (mustExist && !file.exists()) return null;

	// assumes the file exists in at least one of the source folders & is not excluded
	ClasspathMultiDirectory md = this.sourceLocations[0];
	if (this.sourceLocations.length > 1) {
		IPath sourceFileFullPath = file.getFullPath();
		for (int j = 0, m = this.sourceLocations.length; j < m; j++) {
			if (this.sourceLocations[j].sourceFolder.getFullPath().isPrefixOf(sourceFileFullPath)) {
				md = this.sourceLocations[j];
				if (md.exclusionPatterns == null && md.inclusionPatterns == null)
					break;
				if (!Util.isExcluded(file, md.inclusionPatterns, md.exclusionPatterns))
					break;
			}
		}
	}
	return new SourceFile(file, md);
}

protected void finishedWith(String sourceLocator, CompilationResult result, char[] mainTypeName, ArrayList definedTypeNames, ArrayList duplicateTypeNames) {
	if (duplicateTypeNames == null) {
		this.newState.record(sourceLocator, result.qualifiedReferences, result.simpleNameReferences, result.rootReferences, mainTypeName, definedTypeNames);
		return;
	}

	char[][] simpleRefs = result.simpleNameReferences;
	// for each duplicate type p1.p2.A, add the type name A (package was already added)
	next : for (int i = 0, l = duplicateTypeNames.size(); i < l; i++) {
		char[][] compoundName = (char[][]) duplicateTypeNames.get(i);
		char[] typeName = compoundName[compoundName.length - 1];
		int sLength = simpleRefs.length;
		for (int j = 0; j < sLength; j++)
			if (CharOperation.equals(simpleRefs[j], typeName))
				continue next;
		System.arraycopy(simpleRefs, 0, simpleRefs = new char[sLength + 1][], 0, sLength);
		simpleRefs[sLength] = typeName;
	}
	this.newState.record(sourceLocator, result.qualifiedReferences, simpleRefs, result.rootReferences, mainTypeName, definedTypeNames);
}

protected IContainer createFolder(IPath packagePath, IContainer outputFolder) throws CoreException {
	if (packagePath.isEmpty()) return outputFolder;
	IFolder folder = outputFolder.getFolder(packagePath);
	if (!folder.exists()) {
		createFolder(packagePath.removeLastSegments(1), outputFolder);
		folder.create(IResource.FORCE | IResource.DERIVED, true, null);
	}
	return folder;
}



/* (non-Javadoc)
 * @see org.eclipse.jdt.internal.core.builder.ICompilationUnitLocator#fromIFile(org.eclipse.core.resources.IFile)
 */
public ICompilationUnit fromIFile(IFile file) {
	return findSourceFile(file, true);
}

protected void initializeAnnotationProcessorManager(Compiler newCompiler) {
	AbstractAnnotationProcessorManager annotationManager = JavaModelManager.getJavaModelManager().createAnnotationProcessorManager();
	if (annotationManager != null) {
		annotationManager.configureFromPlatform(newCompiler, this, this.javaBuilder.javaProject);
		annotationManager.setErr(new PrintWriter(System.err));
		annotationManager.setOut(new PrintWriter(System.out));
	}
	newCompiler.annotationProcessorManager = annotationManager;
}

protected RuntimeException internalException(CoreException t) {
	ImageBuilderInternalException imageBuilderException = new ImageBuilderInternalException(t);
	if (this.inCompiler)
		return new AbortCompilation(true, imageBuilderException);
	return imageBuilderException;
}

protected boolean isExcludedFromProject(IPath childPath) throws JavaModelException {
	// answer whether the folder should be ignored when walking the project as a source folder
	if (childPath.segmentCount() > 2) return false; // is a subfolder of a package

	for (int j = 0, k = this.sourceLocations.length; j < k; j++) {
		if (childPath.equals(this.sourceLocations[j].binaryFolder.getFullPath())) return true;
		if (childPath.equals(this.sourceLocations[j].sourceFolder.getFullPath())) return true;
	}
	// skip default output folder which may not be used by any source folder
	return childPath.equals(this.javaBuilder.javaProject.getOutputLocation());
}

protected Compiler newCompiler() {
	// disable entire javadoc support if not interested in diagnostics
	Map projectOptions = this.javaBuilder.javaProject.getOptions(true);
	String option = (String) projectOptions.get(JavaCore.COMPILER_PB_INVALID_JAVADOC);
	if (option == null || option.equals(JavaCore.IGNORE)) { // TODO (frederic) see why option is null sometimes while running model tests!?
		option = (String) projectOptions.get(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS);
		if (option == null || option.equals(JavaCore.IGNORE)) {
			option = (String) projectOptions.get(JavaCore.COMPILER_PB_MISSING_JAVADOC_COMMENTS);
			if (option == null || option.equals(JavaCore.IGNORE)) {
				option = (String) projectOptions.get(JavaCore.COMPILER_PB_UNUSED_IMPORT);
				if (option == null || option.equals(JavaCore.IGNORE)) { // Unused import need also to look inside javadoc comment
					projectOptions.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.DISABLED);
				}
			}
		}
	}

	// called once when the builder is initialized... can override if needed
	CompilerOptions compilerOptions = new CompilerOptions(projectOptions);
	compilerOptions.performMethodsFullRecovery = true;
	compilerOptions.performStatementsRecovery = true;
	Compiler newCompiler = new Compiler(
		this.nameEnvironment,
		DefaultErrorHandlingPolicies.proceedWithAllProblems(),
		compilerOptions,
		this,
		ProblemFactory.getProblemFactory(Locale.getDefault()));
	CompilerOptions options = newCompiler.options;
	// temporary code to allow the compiler to revert to a single thread
	String setting = System.getProperty("jdt.compiler.useSingleThread"); //$NON-NLS-1$
	newCompiler.useSingleThread = setting != null && setting.equals("true"); //$NON-NLS-1$

	// enable the compiler reference info support
	options.produceReferenceInfo = true;

	if (options.complianceLevel >= ClassFileConstants.JDK1_6
			&& options.processAnnotations) {
		// support for Java 6 annotation processors
		initializeAnnotationProcessorManager(newCompiler);
	}

	return newCompiler;
}

protected CompilationParticipantResult[] notifyParticipants(SourceFile[] unitsAboutToCompile) {
	CompilationParticipantResult[] results = new CompilationParticipantResult[unitsAboutToCompile.length];
	for (int i = unitsAboutToCompile.length; --i >= 0;)
		results[i] = new CompilationParticipantResult(unitsAboutToCompile[i]);

	// TODO (kent) do we expect to have more than one participant?
	// and if so should we pass the generated files from the each processor to the others to process?
	// and what happens if some participants do not expect to be called with only a few files, after seeing 'all' the files?
	for (int i = 0, l = this.javaBuilder.participants.length; i < l; i++)
		this.javaBuilder.participants[i].buildStarting(results, this instanceof BatchImageBuilder);

	SimpleSet uniqueFiles = null;
	CompilationParticipantResult[] toAdd = null;
	int added = 0;
	for (int i = results.length; --i >= 0;) {
		CompilationParticipantResult result = results[i];
		if (result == null) continue;

		IFile[] deletedGeneratedFiles = result.deletedFiles;
		if (deletedGeneratedFiles != null)
			deleteGeneratedFiles(deletedGeneratedFiles);

		IFile[] addedGeneratedFiles = result.addedFiles;
		if (addedGeneratedFiles != null) {
			for (int j = addedGeneratedFiles.length; --j >= 0;) {
				SourceFile sourceFile = findSourceFile(addedGeneratedFiles[j], true);
				if (sourceFile == null) continue;
				if (uniqueFiles == null) {
					uniqueFiles = new SimpleSet(unitsAboutToCompile.length + 3);
					for (int f = unitsAboutToCompile.length; --f >= 0;)
						uniqueFiles.add(unitsAboutToCompile[f]);
				}
				if (uniqueFiles.addIfNotIncluded(sourceFile) == sourceFile) {
					CompilationParticipantResult newResult = new CompilationParticipantResult(sourceFile);
					// is there enough room to add all the addedGeneratedFiles.length ?
					if (toAdd == null) {
						toAdd = new CompilationParticipantResult[addedGeneratedFiles.length];
					} else {
						int length = toAdd.length;
						if (added == length)
							System.arraycopy(toAdd, 0, toAdd = new CompilationParticipantResult[length + addedGeneratedFiles.length], 0, length);
					}
					toAdd[added++] = newResult;
				}
			}
		}
	}

	if (added >0 ) {
		int length = results.length;
		System.arraycopy(results, 0, results = new CompilationParticipantResult[length + added], 0 , length);
		System.arraycopy(toAdd, 0, results, length, added);
	}
	return results;
}

protected abstract void processAnnotationResults(CompilationParticipantResult[] results);

protected void processAnnotations(CompilationParticipantResult[] results) {
	boolean hasAnnotationProcessor = false;
	for (int i = 0, l = this.javaBuilder.participants.length; !hasAnnotationProcessor && i < l; i++)
		hasAnnotationProcessor = this.javaBuilder.participants[i].isAnnotationProcessor();
	if (!hasAnnotationProcessor) return;

	boolean foundAnnotations = this.filesWithAnnotations != null && this.filesWithAnnotations.elementSize > 0;
	for (int i = results.length; --i >= 0;)
		results[i].reset(foundAnnotations && this.filesWithAnnotations.includes(results[i].sourceFile));

	// even if no files have annotations, must still tell every annotation processor in case the file used to have them
	for (int i = 0, l = this.javaBuilder.participants.length; i < l; i++)
		if (this.javaBuilder.participants[i].isAnnotationProcessor())
			this.javaBuilder.participants[i].processAnnotations(results);
	processAnnotationResults(results);
}

protected void recordParticipantResult(CompilationParticipantResult result) {
	// any added/changed/deleted generated files have already been taken care
	// just record the problems and dependencies - do not expect there to be many
	// must be called after we're finished with the compilation unit results but before incremental loop adds affected files
	CategorizedProblem[] problems = result.problems;
	if (problems != null && problems.length > 0) {
		// existing problems have already been removed so just add these as new problems
		this.notifier.updateProblemCounts(problems);
		try {
			storeProblemsFor(result.sourceFile, problems);
		} catch (CoreException e) {
			// must continue with compile loop so just log the CoreException
			Util.log(e, "JavaBuilder logging CompilationParticipant's CoreException to help debugging"); //$NON-NLS-1$
		}
	}

	String[] dependencies = result.dependencies;
	if (dependencies != null) {
		ReferenceCollection refs = (ReferenceCollection) this.newState.references.get(result.sourceFile.typeLocator());
		if (refs != null)
			refs.addDependencies(dependencies);
	}
}

/**
 * Creates a marker from each problem and adds it to the resource.
 * The marker is as follows:
 *   - its type is T_PROBLEM
 *   - its plugin ID is the JavaBuilder's plugin ID
 *	 - its message is the problem's message
 *	 - its priority reflects the severity of the problem
 *	 - its range is the problem's range
 *	 - it has an extra attribute "ID" which holds the problem's id
 *   - it's {@link IMarker#SOURCE_ID} attribute is positioned to {@link JavaBuilder#SOURCE_ID} if
 *     the problem was generated by JDT; else the {@link IMarker#SOURCE_ID} attribute is
 *     carried from the problem to the marker in extra attributes, if present.
 */
protected void storeProblemsFor(SourceFile sourceFile, CategorizedProblem[] problems) throws CoreException {
	if (sourceFile == null || problems == null || problems.length == 0) return;
	 // once a classpath error is found, ignore all other problems for this project so the user can see the main error
	// but still try to compile as many source files as possible to help the case when the base libraries are in source
	if (!this.keepStoringProblemMarkers) return; // only want the one error recorded on this source file

	IResource resource = sourceFile.resource;
	HashSet managedMarkerTypes = JavaModelManager.getJavaModelManager().compilationParticipants.managedMarkerTypes();
	for (int i = 0, l = problems.length; i < l; i++) {
		CategorizedProblem problem = problems[i];
		int id = problem.getID();

		// handle missing classfile situation
		if (id == IProblem.IsClassPathCorrect) {
			String missingClassfileName = problem.getArguments()[0];
			if (JavaBuilder.DEBUG)
				System.out.println(Messages.bind(Messages.build_incompleteClassPath, missingClassfileName));
			boolean isInvalidClasspathError = JavaCore.ERROR.equals(this.javaBuilder.javaProject.getOption(JavaCore.CORE_INCOMPLETE_CLASSPATH, true));
			// insert extra classpath problem, and make it the only problem for this project (optional)
			if (isInvalidClasspathError && JavaCore.ABORT.equals(this.javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, true))) {
				JavaBuilder.removeProblemsAndTasksFor(this.javaBuilder.currentProject); // make this the only problem for this project
				this.keepStoringProblemMarkers = false;
			}
			IMarker marker = this.javaBuilder.currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			marker.setAttributes(
				new String[] {IMarker.MESSAGE, IMarker.SEVERITY, IJavaModelMarker.CATEGORY_ID, IMarker.SOURCE_ID},
				new Object[] {
					Messages.bind(Messages.build_incompleteClassPath, missingClassfileName),
					new Integer(isInvalidClasspathError ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING),
					new Integer(CategorizedProblem.CAT_BUILDPATH),
					JavaBuilder.SOURCE_ID
				}
			);
			// even if we're not keeping more markers, still fall through rest of the problem reporting, so that offending
			// IsClassPathCorrect problem gets recorded since it may help locate the offending reference
		}

		String markerType = problem.getMarkerType();
		boolean managedProblem = false;
		if (IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER.equals(markerType)
				|| (managedProblem = managedMarkerTypes.contains(markerType))) {
			IMarker marker = resource.createMarker(markerType);

			String[] attributeNames = JAVA_PROBLEM_MARKER_ATTRIBUTE_NAMES;
			int standardLength = attributeNames.length;
			String[] allNames = attributeNames;
			int managedLength = managedProblem ? 0 : 1;
			String[] extraAttributeNames = problem.getExtraMarkerAttributeNames();
			int extraLength = extraAttributeNames == null ? 0 : extraAttributeNames.length;
			if (managedLength > 0 || extraLength > 0) {
				allNames = new String[standardLength + managedLength + extraLength];
				System.arraycopy(attributeNames, 0, allNames, 0, standardLength);
				if (managedLength > 0)
					allNames[standardLength] = IMarker.SOURCE_ID;
				System.arraycopy(extraAttributeNames, 0, allNames, standardLength + managedLength, extraLength);
			}

			Object[] allValues = new Object[allNames.length];
			// standard attributes
			int index = 0;
			allValues[index++] = problem.getMessage(); // message
			allValues[index++] = problem.isError() ? S_ERROR : S_WARNING; // severity
			allValues[index++] = new Integer(id); // ID
			allValues[index++] = new Integer(problem.getSourceStart()); // start
			int end = problem.getSourceEnd();
			allValues[index++] = new Integer(end > 0 ? end + 1 : end); // end
			allValues[index++] = new Integer(problem.getSourceLineNumber()); // line
			allValues[index++] = Util.getProblemArgumentsForMarker(problem.getArguments()); // arguments
			allValues[index++] = new Integer(problem.getCategoryID()); // category ID
			// SOURCE_ID attribute for JDT problems
			if (managedLength > 0)
				allValues[index++] = JavaBuilder.SOURCE_ID;
			// optional extra attributes
			if (extraLength > 0)
				System.arraycopy(problem.getExtraMarkerAttributeValues(), 0, allValues, index, extraLength);

			marker.setAttributes(allNames, allValues);

			if (!this.keepStoringProblemMarkers) return; // only want the one error recorded on this source file
		}
	}
}

protected void storeTasksFor(SourceFile sourceFile, CategorizedProblem[] tasks) throws CoreException {
	if (sourceFile == null || tasks == null || tasks.length == 0) return;

	IResource resource = sourceFile.resource;
	for (int i = 0, l = tasks.length; i < l; i++) {
		CategorizedProblem task = tasks[i];
		if (task.getID() == IProblem.Task) {
			IMarker marker = resource.createMarker(IJavaModelMarker.TASK_MARKER);
			Integer priority = P_NORMAL;
			String compilerPriority = task.getArguments()[2];
			if (JavaCore.COMPILER_TASK_PRIORITY_HIGH.equals(compilerPriority))
				priority = P_HIGH;
			else if (JavaCore.COMPILER_TASK_PRIORITY_LOW.equals(compilerPriority))
				priority = P_LOW;

			String[] attributeNames = JAVA_TASK_MARKER_ATTRIBUTE_NAMES;
			int standardLength = attributeNames.length;
			String[] allNames = attributeNames;
			String[] extraAttributeNames = task.getExtraMarkerAttributeNames();
			int extraLength = extraAttributeNames == null ? 0 : extraAttributeNames.length;
			if (extraLength > 0) {
				allNames = new String[standardLength + extraLength];
				System.arraycopy(attributeNames, 0, allNames, 0, standardLength);
				System.arraycopy(extraAttributeNames, 0, allNames, standardLength, extraLength);
			}

			Object[] allValues = new Object[allNames.length];
			// standard attributes
			int index = 0;
			allValues[index++] = task.getMessage();
			allValues[index++] = priority;
			allValues[index++] = new Integer(task.getID());
			allValues[index++] = new Integer(task.getSourceStart());
			allValues[index++] = new Integer(task.getSourceEnd() + 1);
			allValues[index++] = new Integer(task.getSourceLineNumber());
			allValues[index++] = Boolean.FALSE;
			allValues[index++] = JavaBuilder.SOURCE_ID;
			// optional extra attributes
			if (extraLength > 0)
				System.arraycopy(task.getExtraMarkerAttributeValues(), 0, allValues, index, extraLength);

			marker.setAttributes(allNames, allValues);
		}
	}
}

protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
	CategorizedProblem[] problems = result.getProblems();
	if (problems == null || problems.length == 0) return;

	this.notifier.updateProblemCounts(problems);
	storeProblemsFor(sourceFile, problems);
}

protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
	CategorizedProblem[] tasks = result.getTasks();
	if (tasks == null || tasks.length == 0) return;

	storeTasksFor(sourceFile, tasks);
}

protected char[] writeClassFile(ClassFile classFile, SourceFile compilationUnit, boolean isTopLevelType) throws CoreException {
	String fileName = new String(classFile.fileName()); // the qualified type name "p1/p2/A"
	IPath filePath = new Path(fileName);
	IContainer outputFolder = compilationUnit.sourceLocation.binaryFolder;
	IContainer container = outputFolder;
	if (filePath.segmentCount() > 1) {
		container = createFolder(filePath.removeLastSegments(1), outputFolder);
		filePath = new Path(filePath.lastSegment());
	}

	IFile file = container.getFile(filePath.addFileExtension(SuffixConstants.EXTENSION_class));
	writeClassFileContents(classFile, file, fileName, isTopLevelType, compilationUnit);
	// answer the name of the class file as in Y or Y$M
	return filePath.lastSegment().toCharArray();
}

protected void writeClassFileContents(ClassFile classFile, IFile file, String qualifiedFileName, boolean isTopLevelType, SourceFile compilationUnit) throws CoreException {
//	InputStream input = new SequenceInputStream(
//			new ByteArrayInputStream(classFile.header, 0, classFile.headerOffset),
//			new ByteArrayInputStream(classFile.contents, 0, classFile.contentsOffset));
	InputStream input = new ByteArrayInputStream(classFile.getBytes());
	if (file.exists()) {
		// Deal with shared output folders... last one wins... no collision cases detected
		if (JavaBuilder.DEBUG)
			System.out.println("Writing changed class file " + file.getName());//$NON-NLS-1$
		if (!file.isDerived())
			file.setDerived(true, null);
		file.setContents(input, true, false, null);
	} else {
		// Default implementation just writes out the bytes for the new class file...
		if (JavaBuilder.DEBUG)
			System.out.println("Writing new class file " + file.getName());//$NON-NLS-1$
		file.create(input, IResource.FORCE | IResource.DERIVED, null);
	}
}
}
