| /******************************************************************************* |
| * 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.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.PackageFragment; |
| 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. |
| */ |
| @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.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.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); |
| } |
| } |
| } |