org.aspectj.org.eclipse.jdt.internal.core.builder.AbstractImageBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/*******************************************************************************
* Copyright (c) 2000, 2013 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.aspectj.org.eclipse.jdt.internal.core.builder;
import org.eclipse.core.runtime.*;
import org.eclipse.core.resources.*;
import org.aspectj.org.eclipse.jdt.core.*;
import org.aspectj.org.eclipse.jdt.core.compiler.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.Compiler;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.problem.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.util.SimpleSet;
import org.aspectj.org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.aspectj.org.eclipse.jdt.internal.core.JavaModelManager;
import org.aspectj.org.eclipse.jdt.internal.core.PackageFragment;
import org.aspectj.org.eclipse.jdt.internal.core.util.Messages;
import org.aspectj.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.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
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;
//2000 is best compromise between space used and speed
public static int MAX_AT_ONCE = Integer.getInteger(JavaModelManager.MAX_COMPILED_UNITS_AT_ONCE, 2000).intValue();
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.aspectj.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 = MAX_AT_ONCE == 0 || 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.aspectj.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
HashSet managedMarkerTypes = JavaModelManager.getJavaModelManager().compilationParticipants.managedMarkerTypes();
problems: for (int i = 0, l = problems.length; i < l; i++) {
CategorizedProblem problem = problems[i];
int id = problem.getID();
// we may use a different resource for certain problems such as IProblem.MissingNonNullByDefaultAnnotationOnPackage
// but at the start of the next problem we should reset it to the source file's resource
IResource resource = sourceFile.resource;
// 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))) {
if (id == IProblem.MissingNonNullByDefaultAnnotationOnPackage && !(CharOperation.equals(sourceFile.getMainTypeName(), TypeConstants.PACKAGE_INFO_NAME))) {
// for this kind of problem, marker needs to be created on the package instead of on the source file
// see bug 372012
char[] fileName = sourceFile.getFileName();
int pkgEnd = CharOperation.lastIndexOf('/', fileName);
if (pkgEnd == -1)
pkgEnd = CharOperation.lastIndexOf(File.separatorChar, fileName);
PackageFragment pkg = null;
if (pkgEnd != -1)
pkg = (PackageFragment) Util.getPackageFragment(sourceFile.getFileName(), pkgEnd, -1 /*no jar separator for java files*/);
if (pkg != null) {
try {
IMarker[] existingMarkers = pkg.resource().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_ZERO);
int len = existingMarkers.length;
for (int j=0; j < len; j++) {
if (((Integer)existingMarkers[j].getAttribute(IJavaModelMarker.ID)).intValue() == IProblem.MissingNonNullByDefaultAnnotationOnPackage) {
continue problems; // marker already present
}
}
} catch (CoreException e) {
// marker retrieval failed, cannot do much
if (JavaModelManager.VERBOSE) {
e.printStackTrace();
}
}
IResource tempRes = pkg.resource();
if (tempRes != null) {
resource = tempRes;
}
}
}
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
allValues[index++] = new Integer(problem.getSourceEnd() + 1); // 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);
}
}
}