Copyright (c) 2000, 2016 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/
package org.eclipse.jdt.internal.core.builder; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.compiler.*; import org.eclipse.jdt.internal.compiler.*; import org.eclipse.jdt.internal.compiler.classfmt.*; import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.problem.*; import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.core.CompilationGroup; import org.eclipse.jdt.internal.core.util.Messages; import org.eclipse.jdt.internal.core.util.Util; import java.io.*; import java.net.URI; import java.util.*;
The incremental image builder
/** * The incremental image builder */
@SuppressWarnings({"rawtypes", "unchecked"}) public class IncrementalImageBuilder extends AbstractImageBuilder { protected LinkedHashSet<SourceFile> sourceFiles; protected LinkedHashSet<SourceFile> previousSourceFiles; protected Set<String> qualifiedStrings; protected Set<String> simpleStrings; protected Set<String> rootStrings; protected SimpleLookupTable secondaryTypesToRemove; protected boolean hasStructuralChanges; protected boolean makeOutputFolderConsistent; private IncrementalImageBuilder testImageBuilder; public static int MaxCompileLoop = 5; // perform a full build if it takes more than ? incremental compile loops protected IncrementalImageBuilder(JavaBuilder javaBuilder, State buildState, CompilationGroup compilationGroup) { super(javaBuilder, true, buildState, compilationGroup); this.nameEnvironment.isIncrementalBuild = true; this.makeOutputFolderConsistent = JavaCore.ENABLED.equals( javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_RECREATE_MODIFIED_CLASS_FILES_IN_OUTPUT_FOLDER, true)); if (compilationGroup == CompilationGroup.MAIN) { final IncrementalImageBuilder builder = new IncrementalImageBuilder(javaBuilder, this.newState, CompilationGroup.TEST); if (builder.sourceLocations.length > 0) { this.testImageBuilder = builder; this.testImageBuilder.resetCollections(); } } } protected IncrementalImageBuilder(JavaBuilder javaBuilder) { this(javaBuilder, null, CompilationGroup.MAIN); this.newState.copyFrom(javaBuilder.lastState); } protected IncrementalImageBuilder(BatchImageBuilder batchBuilder, CompilationGroup compilationGroup) { this(batchBuilder.javaBuilder, batchBuilder.newState, compilationGroup); resetCollections(); } public boolean build(SimpleLookupTable deltas) { if(this.sourceLocations.length == 0) { if (this.testImageBuilder != null) { return this.testImageBuilder.build(deltas); } else { return true; } } // initialize builder // walk this project's deltas, find changed source files // walk prereq projects' deltas, find changed class files & add affected source files // use the build state # to skip the deltas for certain prereq projects // ignore changed zip/jar files since they caused a full build // compile the source files & acceptResult() // compare the produced class files against the existing ones on disk // recompile all dependent source files of any type with structural changes or new/removed secondary type // keep a loop counter to abort & perform a full build if (JavaBuilder.DEBUG) System.out.println("INCREMENTAL build"); //$NON-NLS-1$ try { resetCollections(); this.notifier.subTask(Messages.build_analyzingDeltas); if (this.javaBuilder.hasBuildpathErrors()) { // if a missing class file was detected in the last build, a build state was saved since its no longer fatal // but we need to rebuild every source file since problems were not recorded // AND to avoid the infinite build scenario if this project is involved in a cycle, see bug 160550 // we need to avoid unnecessary deltas caused by doing a full build in this case if (JavaBuilder.DEBUG) System.out.println("COMPILING all source files since the buildpath has errors "); //$NON-NLS-1$ this.javaBuilder.currentProject.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); addAllSourceFiles(this.sourceFiles); this.notifier.updateProgressDelta(0.25f); } else { IResourceDelta sourceDelta = (IResourceDelta) deltas.get(this.javaBuilder.currentProject); if (sourceDelta != null) { if (!findSourceFiles(sourceDelta)) return this.testImageBuilder != null ? this.testImageBuilder.build(deltas) : false; if(this.testImageBuilder != null) { this.testImageBuilder.findSourceFiles(sourceDelta); } } this.notifier.updateProgressDelta(0.10f); Object[] keyTable = deltas.keyTable; Object[] valueTable = deltas.valueTable; for (int i = 0, l = valueTable.length; i < l; i++) { IResourceDelta delta = (IResourceDelta) valueTable[i]; if (delta != null) { IProject p = (IProject) keyTable[i]; ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) this.javaBuilder.binaryLocationsPerProject.get(p); if (classFoldersAndJars != null) if (!findAffectedSourceFiles(delta, classFoldersAndJars, p)) return false; } } this.notifier.updateProgressDelta(0.10f); this.notifier.subTask(Messages.build_analyzingSources); addAffectedSourceFiles(); this.notifier.updateProgressDelta(0.05f); } if (incrementalBuildLoop() == false) { return false; } if (this.testImageBuilder != null && this.testImageBuilder.incrementalBuildLoop() == false) { return false; } if (this.hasStructuralChanges && this.javaBuilder.javaProject.hasCycleMarker()) this.javaBuilder.mustPropagateStructuralChanges(); } catch (AbortIncrementalBuildException e) { // abort the incremental build and let the batch builder handle the problem if (JavaBuilder.DEBUG) System.out.println("ABORTING incremental build... problem with " + e.qualifiedTypeName + //$NON-NLS-1$ ". Likely renamed inside its existing source file."); //$NON-NLS-1$ return false; } catch (CoreException e) { throw internalException(e); } finally { cleanUp(); if (this.testImageBuilder != null) { this.testImageBuilder.cleanUp(); } } return true; } private boolean incrementalBuildLoop() throws CoreException { int compileLoop = 0; float increment = 0.40f; while (this.sourceFiles.size() > 0) { // added to in acceptResult if (++compileLoop > MaxCompileLoop) { if (JavaBuilder.DEBUG) System.out.println("ABORTING incremental build... exceeded loop count"); //$NON-NLS-1$ return false; } this.notifier.checkCancel(); SourceFile[] allSourceFiles = new SourceFile[this.sourceFiles.size()]; this.sourceFiles.toArray(allSourceFiles); resetCollections(); this.workQueue.addAll(allSourceFiles); this.notifier.setProgressPerCompilationUnit(increment / allSourceFiles.length); increment = increment / 2; compile(allSourceFiles); removeSecondaryTypes(); addAffectedSourceFiles(); } return true; } protected void buildAfterBatchBuild() { // called from a batch builder once all source files have been compiled AND some changes // need to be propagated incrementally (annotations, missing secondary types) if (JavaBuilder.DEBUG) System.out.println("INCREMENTAL build after batch build @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ // this is a copy of the incremental build loop try { addAffectedSourceFiles(); while (this.sourceFiles.size() > 0) { this.notifier.checkCancel(); SourceFile[] allSourceFiles = new SourceFile[this.sourceFiles.size()]; this.sourceFiles.toArray(allSourceFiles); resetCollections(); this.notifier.setProgressPerCompilationUnit(0.08f / allSourceFiles.length); this.workQueue.addAll(allSourceFiles); compile(allSourceFiles); removeSecondaryTypes(); addAffectedSourceFiles(); } } catch (CoreException e) { throw internalException(e); } finally { cleanUp(); } } protected void addAffectedSourceFiles() { if (this.qualifiedStrings.size() == 0 && this.simpleStrings.size() == 0) return; if(this.testImageBuilder != null) { this.testImageBuilder.addAffectedSourceFiles(this.qualifiedStrings, this.simpleStrings, this.rootStrings, null); } addAffectedSourceFiles(this.qualifiedStrings, this.simpleStrings, this.rootStrings, null); } protected void addAffectedSourceFiles(Set<String> qualifiedSet, Set<String> simpleSet, Set<String> rootSet, Set<String> affectedTypes) { // the qualifiedStrings are of the form 'p1/p2' & the simpleStrings are just 'X' char[][][] internedQualifiedNames = ReferenceCollection.internQualifiedNames(qualifiedSet); // if a well known qualified name was found then we can skip over these if (internedQualifiedNames.length < qualifiedSet.size()) internedQualifiedNames = null; char[][] internedSimpleNames = ReferenceCollection.internSimpleNames(simpleSet, true); // if a well known name was found then we can skip over these if (internedSimpleNames.length < simpleSet.size()) internedSimpleNames = null; char[][] internedRootNames = ReferenceCollection.internSimpleNames(rootSet, false); Object[] keyTable = this.newState.references.keyTable; Object[] valueTable = this.newState.references.valueTable; next : for (int i = 0, l = valueTable.length; i < l; i++) { String typeLocator = (String) keyTable[i]; if (typeLocator != null) { if (affectedTypes != null && !affectedTypes.contains(typeLocator)) continue next; ReferenceCollection refs = (ReferenceCollection) valueTable[i]; if (refs.includes(internedQualifiedNames, internedSimpleNames, internedRootNames)) { IFile file = this.javaBuilder.currentProject.getFile(typeLocator); SourceFile sourceFile = findSourceFile(file, true); if (sourceFile == null) continue next; if (this.sourceFiles.contains(sourceFile)) continue next; if (this.compiledAllAtOnce && this.previousSourceFiles != null && this.previousSourceFiles.contains(sourceFile)) continue next; // can skip previously compiled files since already saw hierarchy related problems if (JavaBuilder.DEBUG) System.out.println(" adding affected source file " + typeLocator); //$NON-NLS-1$ this.sourceFiles.add(sourceFile); } } } } protected void addDependentsOf(IPath path, boolean isStructuralChange) { addDependentsOf(path, isStructuralChange, this.qualifiedStrings, this.simpleStrings, this.rootStrings); } protected void addDependentsOf(IPath path, boolean isStructuralChange, Set<String> qualifiedNames, Set<String> simpleNames, Set<String> rootNames) { path = path.setDevice(null); if (isStructuralChange) { String last = path.lastSegment(); if (last.length() == TypeConstants.PACKAGE_INFO_NAME.length) if (CharOperation.equals(last.toCharArray(), TypeConstants.PACKAGE_INFO_NAME)) { path = path.removeLastSegments(1); // the package-info file has changed so blame the package itself /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=323785, in the case of default package, there is no need to blame the package itself as there can be no annotations or documentation comment tags in the package-info file that can influence the rest of the package. Just bail out so we don't touch null objects below. */ if (path.isEmpty()) return; } } if (isStructuralChange && !this.hasStructuralChanges) { this.newState.tagAsStructurallyChanged(); this.hasStructuralChanges = true; } // the qualifiedStrings are of the form 'p1/p2' & the simpleStrings are just 'X' rootNames.add(path.segment(0)); String packageName = path.removeLastSegments(1).toString(); boolean wasNew = qualifiedNames.add(packageName); String typeName = path.lastSegment(); int memberIndex = typeName.indexOf('$'); if (memberIndex > 0) typeName = typeName.substring(0, memberIndex); wasNew = simpleNames.add(typeName) | wasNew; if (wasNew && JavaBuilder.DEBUG) System.out.println(" will look for dependents of " //$NON-NLS-1$ + typeName + " in " + packageName); //$NON-NLS-1$ } protected boolean checkForClassFileChanges(IResourceDelta binaryDelta, ClasspathMultiDirectory md, int segmentCount) throws CoreException { IResource resource = binaryDelta.getResource(); // remember that if inclusion & exclusion patterns change then a full build is done boolean isExcluded = (md.exclusionPatterns != null || md.inclusionPatterns != null) && Util.isExcluded(resource, md.inclusionPatterns, md.exclusionPatterns); switch(resource.getType()) { case IResource.FOLDER : if (isExcluded && md.inclusionPatterns == null) return true; // no need to go further with this delta since its children cannot be included IResourceDelta[] children = binaryDelta.getAffectedChildren(); for (int i = 0, l = children.length; i < l; i++) if (!checkForClassFileChanges(children[i], md, segmentCount)) return false; return true; case IResource.FILE : if (!isExcluded && org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(resource.getName())) { // perform full build if a managed class file has been changed IPath typePath = resource.getFullPath().removeFirstSegments(segmentCount).removeFileExtension(); if (this.newState.isKnownType(typePath.toString())) { if (JavaBuilder.DEBUG) System.out.println("MUST DO FULL BUILD. Found change to class file " + typePath); //$NON-NLS-1$ return false; } return true; } } return true; } @Override protected void cleanUp() { super.cleanUp(); this.sourceFiles = null; this.previousSourceFiles = null; this.qualifiedStrings = null; this.simpleStrings = null; this.rootStrings = null; this.secondaryTypesToRemove = null; this.hasStructuralChanges = false; } @Override protected void compile(SourceFile[] units, SourceFile[] additionalUnits, boolean compilingFirstGroup) { if (compilingFirstGroup && additionalUnits != null) { // add any source file from additionalUnits to units if it defines secondary types // otherwise its possible during testing with MAX_AT_ONCE == 1 that a secondary type // can cause an infinite loop as it alternates between not found and defined, see bug 146324 ArrayList extras = null; for (int i = 0, l = additionalUnits.length; i < l; i++) { SourceFile unit = additionalUnits[i]; if (unit != null && this.newState.getDefinedTypeNamesFor(unit.typeLocator()) != null) { if (JavaBuilder.DEBUG) System.out.println("About to compile file with secondary types "+ unit.typeLocator()); //$NON-NLS-1$ if (extras == null) extras = new ArrayList(3); extras.add(unit); } } if (extras != null) { int oldLength = units.length; int toAdd = extras.size(); System.arraycopy(units, 0, units = new SourceFile[oldLength + toAdd], 0, oldLength); for (int i = 0; i < toAdd; i++) units[oldLength++] = (SourceFile) extras.get(i); } } super.compile(units, additionalUnits, compilingFirstGroup); } @Override protected void deleteGeneratedFiles(IFile[] deletedGeneratedFiles) { // delete generated files and recompile any affected source files try { for (int j = deletedGeneratedFiles.length; --j >= 0;) { IFile deletedFile = deletedGeneratedFiles[j]; if (deletedFile.exists()) continue; // only delete .class files for source files that were actually deleted SourceFile sourceFile = findSourceFile(deletedFile, false); String typeLocator = sourceFile.typeLocator(); int mdSegmentCount = sourceFile.sourceLocation.sourceFolder.getFullPath().segmentCount(); IPath typePath = sourceFile.resource.getFullPath().removeFirstSegments(mdSegmentCount).removeFileExtension(); addDependentsOf(typePath, true); // add dependents of the source file since its now deleted this.previousSourceFiles = null; // existing source files did not see it as deleted since they were compiled before it was char[][] definedTypeNames = this.newState.getDefinedTypeNamesFor(typeLocator); if (definedTypeNames == null) { // defined a single type matching typePath removeClassFile(typePath, sourceFile.sourceLocation.binaryFolder); } else { if (definedTypeNames.length > 0) { // skip it if it failed to successfully define a type IPath packagePath = typePath.removeLastSegments(1); for (int d = 0, l = definedTypeNames.length; d < l; d++) removeClassFile(packagePath.append(new String(definedTypeNames[d])), sourceFile.sourceLocation.binaryFolder); } } this.newState.removeLocator(typeLocator); } } 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$ } } protected boolean findAffectedSourceFiles(IResourceDelta delta, ClasspathLocation[] classFoldersAndJars, IProject prereqProject) { for (int i = 0, l = classFoldersAndJars.length; i < l; i++) { ClasspathLocation bLocation = classFoldersAndJars[i]; // either a .class file folder or a zip/jar file if (bLocation != null) { // skip unchanged output folder IPath p = bLocation.getProjectRelativePath(); if (p != null) { IResourceDelta binaryDelta = delta.findMember(p); if (binaryDelta != null) { if (bLocation instanceof ClasspathJar) { if (JavaBuilder.DEBUG) System.out.println("ABORTING incremental build... found delta to jar/zip file"); //$NON-NLS-1$ return false; // do full build since jar file was changed (added/removed were caught as classpath change) } if (binaryDelta.getKind() == IResourceDelta.ADDED || binaryDelta.getKind() == IResourceDelta.REMOVED) { if (JavaBuilder.DEBUG) System.out.println("ABORTING incremental build... found added/removed binary folder"); //$NON-NLS-1$ return false; // added/removed binary folder should not make it here (classpath change), but handle anyways } int segmentCount = binaryDelta.getFullPath().segmentCount(); IResourceDelta[] children = binaryDelta.getAffectedChildren(); // .class files from class folder StringSet structurallyChangedTypes = null; if (bLocation.isOutputFolder()) structurallyChangedTypes = this.newState.getStructurallyChangedTypes(this.javaBuilder.getLastState(prereqProject)); for (int j = 0, m = children.length; j < m; j++) findAffectedSourceFiles(children[j], segmentCount, structurallyChangedTypes); this.notifier.checkCancel(); } } } } return true; } protected void findAffectedSourceFiles(IResourceDelta binaryDelta, int segmentCount, StringSet structurallyChangedTypes) { // When a package becomes a type or vice versa, expect 2 deltas, // one on the folder & one on the class file IResource resource = binaryDelta.getResource(); switch(resource.getType()) { case IResource.FOLDER : switch (binaryDelta.getKind()) { case IResourceDelta.ADDED : case IResourceDelta.REMOVED : IPath packagePath = resource.getFullPath().removeFirstSegments(segmentCount); String packageName = packagePath.toString(); if (binaryDelta.getKind() == IResourceDelta.ADDED) { // see if any known source file is from the same package... classpath already includes new package if (!this.newState.isKnownPackage(packageName)) { if (JavaBuilder.DEBUG) System.out.println("Found added package " + packageName); //$NON-NLS-1$ addDependentsOf(packagePath, false); return; } if (JavaBuilder.DEBUG) System.out.println("Skipped dependents of added package " + packageName); //$NON-NLS-1$ } else { // see if the package still exists on the classpath if (!this.nameEnvironment.isPackage(packageName, ModuleBinding.ANY)) { if (JavaBuilder.DEBUG) System.out.println("Found removed package " + packageName); //$NON-NLS-1$ addDependentsOf(packagePath, false); return; } if (JavaBuilder.DEBUG) System.out.println("Skipped dependents of removed package " + packageName); //$NON-NLS-1$ } //$FALL-THROUGH$ traverse the sub-packages and .class files case IResourceDelta.CHANGED : IResourceDelta[] children = binaryDelta.getAffectedChildren(); for (int i = 0, l = children.length; i < l; i++) findAffectedSourceFiles(children[i], segmentCount, structurallyChangedTypes); } return; case IResource.FILE : if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(resource.getName())) { IPath typePath = resource.getFullPath().removeFirstSegments(segmentCount).removeFileExtension(); switch (binaryDelta.getKind()) { case IResourceDelta.ADDED : case IResourceDelta.REMOVED : if (JavaBuilder.DEBUG) System.out.println("Found added/removed class file " + typePath); //$NON-NLS-1$ addDependentsOf(typePath, false); return; case IResourceDelta.CHANGED : if ((binaryDelta.getFlags() & IResourceDelta.CONTENT) == 0) return; // skip it since it really isn't changed if (structurallyChangedTypes != null && !structurallyChangedTypes.includes(typePath.toString())) return; // skip since it wasn't a structural change if (JavaBuilder.DEBUG) System.out.println("Found changed class file " + typePath); //$NON-NLS-1$ addDependentsOf(typePath, false); } return; } } } protected boolean findSourceFiles(IResourceDelta delta) throws CoreException { ArrayList visited = this.makeOutputFolderConsistent ? new ArrayList(this.sourceLocations.length) : null; for (int i = 0, l = this.sourceLocations.length; i < l; i++) { ClasspathMultiDirectory md = this.sourceLocations[i]; if (this.makeOutputFolderConsistent && md.hasIndependentOutputFolder && !visited.contains(md.binaryFolder)) { // even a project which acts as its own source folder can have an independent/nested output folder visited.add(md.binaryFolder); IResourceDelta binaryDelta = delta.findMember(md.binaryFolder.getProjectRelativePath()); if (binaryDelta != null) { int segmentCount = binaryDelta.getFullPath().segmentCount(); IResourceDelta[] children = binaryDelta.getAffectedChildren(); for (int j = 0, m = children.length; j < m; j++) if (!checkForClassFileChanges(children[j], md, segmentCount)) return false; } } if (md.sourceFolder.equals(this.javaBuilder.currentProject)) { // skip nested source & output folders when the project is a source folder int segmentCount = delta.getFullPath().segmentCount(); IResourceDelta[] children = delta.getAffectedChildren(); for (int j = 0, m = children.length; j < m; j++) if (!isExcludedFromProject(children[j].getFullPath())) if (!findSourceFiles(children[j], md, segmentCount)) return false; } else { IResourceDelta sourceDelta = delta.findMember(md.sourceFolder.getProjectRelativePath()); if (sourceDelta != null) { if (sourceDelta.getKind() == IResourceDelta.REMOVED) { if (JavaBuilder.DEBUG) System.out.println("ABORTING incremental build... found removed source folder"); //$NON-NLS-1$ return false; // removed source folder should not make it here, but handle anyways (ADDED is supported) } int segmentCount = sourceDelta.getFullPath().segmentCount(); IResourceDelta[] children = sourceDelta.getAffectedChildren(); try { for (int j = 0, m = children.length; j < m; j++) if (!findSourceFiles(children[j], md, segmentCount)) return false; } catch (CoreException e) { // catch the case that a package has been renamed and collides on disk with an as-yet-to-be-deleted package if (e.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) { if (JavaBuilder.DEBUG) System.out.println("ABORTING incremental build... found renamed package"); //$NON-NLS-1$ return false; } throw e; // rethrow } } } this.notifier.checkCancel(); } return true; } protected boolean findSourceFiles(IResourceDelta sourceDelta, ClasspathMultiDirectory md, int segmentCount) throws CoreException { // When a package becomes a type or vice versa, expect 2 deltas, // one on the folder & one on the source file IResource resource = sourceDelta.getResource(); // remember that if inclusion & exclusion patterns change then a full build is done boolean isExcluded = (md.exclusionPatterns != null || md.inclusionPatterns != null) && Util.isExcluded(resource, md.inclusionPatterns, md.exclusionPatterns); switch(resource.getType()) { case IResource.FOLDER : if (isExcluded && md.inclusionPatterns == null) return true; // no need to go further with this delta since its children cannot be included switch (sourceDelta.getKind()) { case IResourceDelta.ADDED : if (!isExcluded) { IPath addedPackagePath = resource.getFullPath().removeFirstSegments(segmentCount); createFolder(addedPackagePath, md.binaryFolder); // ensure package exists in the output folder // see if any known source file is from the same package... classpath already includes new package if (this.sourceLocations.length > 1 && this.newState.isKnownPackage(addedPackagePath.toString())) { if (JavaBuilder.DEBUG) System.out.println("Skipped dependents of added package " + addedPackagePath); //$NON-NLS-1$ } else { if (JavaBuilder.DEBUG) System.out.println("Found added package " + addedPackagePath); //$NON-NLS-1$ addDependentsOf(addedPackagePath, true); } } //$FALL-THROUGH$ collect all the source files case IResourceDelta.CHANGED : IResourceDelta[] children = sourceDelta.getAffectedChildren(); for (int i = 0, l = children.length; i < l; i++) if (!findSourceFiles(children[i], md, segmentCount)) return false; return true; case IResourceDelta.REMOVED : if (isExcluded) { // since this folder is excluded then there is nothing to delete (from this md), but must walk any included subfolders children = sourceDelta.getAffectedChildren(); for (int i = 0, l = children.length; i < l; i++) if (!findSourceFiles(children[i], md, segmentCount)) return false; return true; } IPath removedPackagePath = resource.getFullPath().removeFirstSegments(segmentCount); if (this.sourceLocations.length > 1) { for (int i = 0, l = this.sourceLocations.length; i < l; i++) { if (this.sourceLocations[i].sourceFolder.getFolder(removedPackagePath).exists()) { // only a package fragment was removed, same as removing multiple source files if (md.hasIndependentOutputFolder) createFolder(removedPackagePath, md.binaryFolder); // ensure package exists in the output folder IResourceDelta[] removedChildren = sourceDelta.getAffectedChildren(); for (int j = 0, m = removedChildren.length; j < m; j++) if (!findSourceFiles(removedChildren[j], md, segmentCount)) return false; return true; } } } if ((sourceDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) { // same idea as moving a source file // see bug 163200 IResource movedFolder = this.javaBuilder.workspaceRoot.getFolder(sourceDelta.getMovedToPath()); JavaBuilder.removeProblemsAndTasksFor(movedFolder); } IFolder removedPackageFolder = md.binaryFolder.getFolder(removedPackagePath); if (removedPackageFolder.exists()) removedPackageFolder.delete(IResource.FORCE, null); // add dependents even when the package thinks it does not exist to be on the safe side if (JavaBuilder.DEBUG) System.out.println("Found removed package " + removedPackagePath); //$NON-NLS-1$ addDependentsOf(removedPackagePath, true); this.newState.removePackage(sourceDelta); } return true; case IResource.FILE : if (isExcluded) return true; String resourceName = resource.getName(); if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(resourceName)) { IPath typePath = resource.getFullPath().removeFirstSegments(segmentCount).removeFileExtension(); String typeLocator = resource.getProjectRelativePath().toString(); switch (sourceDelta.getKind()) { case IResourceDelta.ADDED : if (JavaBuilder.DEBUG) System.out.println("Compile this added source file " + typeLocator); //$NON-NLS-1$ this.sourceFiles.add(new SourceFile((IFile) resource, md, true)); String typeName = typePath.toString(); if (!this.newState.isDuplicateLocator(typeName, typeLocator)) { // adding dependents results in 2 duplicate errors if (JavaBuilder.DEBUG) System.out.println("Found added source file " + typeName); //$NON-NLS-1$ addDependentsOf(typePath, true); } return true; case IResourceDelta.REMOVED : char[][] definedTypeNames = this.newState.getDefinedTypeNamesFor(typeLocator); if (definedTypeNames == null) { // defined a single type matching typePath removeClassFile(typePath, md.binaryFolder); if ((sourceDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) { // remove problems and tasks for a compilation unit that is being moved (to another package or renamed) // if the target file is a compilation unit, the new cu will be recompiled // if the target file is a non-java resource, then markers are removed // see bug 2857 IResource movedFile = this.javaBuilder.workspaceRoot.getFile(sourceDelta.getMovedToPath()); JavaBuilder.removeProblemsAndTasksFor(movedFile); } } else { if (JavaBuilder.DEBUG) System.out.println("Found removed source file " + typePath.toString()); //$NON-NLS-1$ addDependentsOf(typePath, true); // add dependents of the source file since it may be involved in a name collision if (definedTypeNames.length > 0) { // skip it if it failed to successfully define a type IPath packagePath = typePath.removeLastSegments(1); for (int i = 0, l = definedTypeNames.length; i < l; i++) removeClassFile(packagePath.append(new String(definedTypeNames[i])), md.binaryFolder); } } this.newState.removeLocator(typeLocator); return true; case IResourceDelta.CHANGED : if ((sourceDelta.getFlags() & IResourceDelta.CONTENT) == 0 && (sourceDelta.getFlags() & IResourceDelta.ENCODING) == 0) return true; // skip it since it really isn't changed if (JavaBuilder.DEBUG) System.out.println("Compile this changed source file " + typeLocator); //$NON-NLS-1$ SourceFile unit = new SourceFile((IFile) resource, md, true); this.sourceFiles.add(unit); } return true; } else if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(resourceName)) { // perform full build if a managed class file has been changed if (this.makeOutputFolderConsistent) { IPath typePath = resource.getFullPath().removeFirstSegments(segmentCount).removeFileExtension(); if (this.newState.isKnownType(typePath.toString())) { if (JavaBuilder.DEBUG) System.out.println("MUST DO FULL BUILD. Found change to class file " + typePath); //$NON-NLS-1$ return false; } } return true; } else if (md.hasIndependentOutputFolder) { if (this.javaBuilder.filterExtraResource(resource)) return true; // copy all other resource deltas to the output folder IPath resourcePath = resource.getFullPath().removeFirstSegments(segmentCount); IResource outputFile = md.binaryFolder.getFile(resourcePath); switch (sourceDelta.getKind()) { case IResourceDelta.ADDED : if (outputFile.exists()) { if (JavaBuilder.DEBUG) System.out.println("Deleting existing file " + resourcePath); //$NON-NLS-1$ outputFile.delete(IResource.FORCE, null); } if (JavaBuilder.DEBUG) System.out.println("Copying added file " + resourcePath); //$NON-NLS-1$ createFolder(resourcePath.removeLastSegments(1), md.binaryFolder); // ensure package exists in the output folder copyResource(resource, outputFile); return true; case IResourceDelta.REMOVED : if (outputFile.exists()) { if (JavaBuilder.DEBUG) System.out.println("Deleting removed file " + resourcePath); //$NON-NLS-1$ outputFile.delete(IResource.FORCE, null); } return true; case IResourceDelta.CHANGED : if ((sourceDelta.getFlags() & IResourceDelta.CONTENT) == 0 && (sourceDelta.getFlags() & IResourceDelta.ENCODING) == 0) return true; // skip it since it really isn't changed if (outputFile.exists()) { if (JavaBuilder.DEBUG) System.out.println("Deleting existing file " + resourcePath); //$NON-NLS-1$ outputFile.delete(IResource.FORCE, null); } if (JavaBuilder.DEBUG) System.out.println("Copying changed file " + resourcePath); //$NON-NLS-1$ createFolder(resourcePath.removeLastSegments(1), md.binaryFolder); // ensure package exists in the output folder copyResource(resource, outputFile); } return true; } } return true; } @Override protected void finishedWith(String sourceLocator, CompilationResult result, char[] mainTypeName, ArrayList definedTypeNames, ArrayList duplicateTypeNames) { char[][] previousTypeNames = this.newState.getDefinedTypeNamesFor(sourceLocator); if (previousTypeNames == null) previousTypeNames = new char[][] {mainTypeName}; IPath packagePath = null; next : for (int i = 0, l = previousTypeNames.length; i < l; i++) { char[] previous = previousTypeNames[i]; for (int j = 0, m = definedTypeNames.size(); j < m; j++) if (CharOperation.equals(previous, (char[]) definedTypeNames.get(j))) continue next; SourceFile sourceFile = (SourceFile) result.getCompilationUnit(); if (packagePath == null) { int count = sourceFile.sourceLocation.sourceFolder.getFullPath().segmentCount(); packagePath = sourceFile.resource.getFullPath().removeFirstSegments(count).removeLastSegments(1); } if (this.secondaryTypesToRemove == null) this.secondaryTypesToRemove = new SimpleLookupTable(); ArrayList types = (ArrayList) this.secondaryTypesToRemove.get(sourceFile.sourceLocation.binaryFolder); if (types == null) types = new ArrayList(definedTypeNames.size()); types.add(packagePath.append(new String(previous))); this.secondaryTypesToRemove.put(sourceFile.sourceLocation.binaryFolder, types); } super.finishedWith(sourceLocator, result, mainTypeName, definedTypeNames, duplicateTypeNames); } @Override protected void processAnnotationResults(CompilationParticipantResult[] results) { 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 && !this.sourceFiles.contains(sourceFile)) this.sourceFiles.add(sourceFile); } } recordParticipantResult(result); } } protected void removeClassFile(IPath typePath, IContainer outputFolder) throws CoreException { if (typePath.lastSegment().indexOf('$') == -1) { // is not a nested type this.newState.removeQualifiedTypeName(typePath.toString()); // add dependents even when the type thinks it does not exist to be on the safe side if (JavaBuilder.DEBUG) System.out.println("Found removed type " + typePath); //$NON-NLS-1$ addDependentsOf(typePath, true); // when member types are removed, their enclosing type is structurally changed } IFile classFile = outputFolder.getFile(typePath.addFileExtension(SuffixConstants.EXTENSION_class)); if (classFile.exists()) { if (JavaBuilder.DEBUG) System.out.println("Deleting class file of removed type " + typePath); //$NON-NLS-1$ classFile.delete(IResource.FORCE, null); } } protected void removeSecondaryTypes() throws CoreException { if (this.secondaryTypesToRemove != null) { // delayed deleting secondary types until the end of the compile loop Object[] keyTable = this.secondaryTypesToRemove.keyTable; Object[] valueTable = this.secondaryTypesToRemove.valueTable; for (int i = 0, l = keyTable.length; i < l; i++) { IContainer outputFolder = (IContainer) keyTable[i]; if (outputFolder != null) { ArrayList paths = (ArrayList) valueTable[i]; for (int j = 0, m = paths.size(); j < m; j++) removeClassFile((IPath) paths.get(j), outputFolder); } } this.secondaryTypesToRemove = null; if (this.previousSourceFiles != null) this.previousSourceFiles = null; // cannot optimize recompile case when a secondary type is deleted, see 181269 } } protected void resetCollections() { if (this.sourceFiles == null) { this.sourceFiles = new LinkedHashSet<>(33); this.previousSourceFiles = null; this.qualifiedStrings = new HashSet<>(3); this.simpleStrings = new HashSet<>(3); this.rootStrings = new HashSet<>(3); this.hasStructuralChanges = false; } else { this.previousSourceFiles = this.sourceFiles.isEmpty() ? null : (LinkedHashSet) this.sourceFiles.clone(); this.sourceFiles.clear(); this.qualifiedStrings.clear(); this.simpleStrings.clear(); this.rootStrings.clear(); this.workQueue.clear(); } } @Override protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException { if (CharOperation.equals(sourceFile.getMainTypeName(), TypeConstants.PACKAGE_INFO_NAME)) { IResource pkgResource = sourceFile.resource.getParent(); IMarker[] findMarkers = pkgResource.findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); if (findMarkers.length > 0) { // markers must be from the time when no package-info.java existed. // trigger a full build, so marker is cleared also from packages in other source folders throw new AbortCompilation(true, new AbortIncrementalBuildException(new String(TypeConstants.PACKAGE_INFO_NAME))); } } IMarker[] markers = JavaBuilder.getProblemsFor(sourceFile.resource); CategorizedProblem[] problems = result.getProblems(); if (problems == null && markers.length == 0) return; this.notifier.updateProblemCounts(markers, problems); JavaBuilder.removeProblemsFor(sourceFile.resource); storeProblemsFor(sourceFile, problems); } @Override protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) throws CoreException { IMarker[] markers = JavaBuilder.getTasksFor(sourceFile.resource); CategorizedProblem[] tasks = result.getTasks(); if (tasks == null && markers.length == 0) return; JavaBuilder.removeTasksFor(sourceFile.resource); storeTasksFor(sourceFile, tasks); }
See Also:
  • writeClassFileContents.writeClassFileContents(ClassFile, IFile, String, boolean, SourceFile)
/** * @see org.eclipse.jdt.internal.core.builder.AbstractImageBuilder#writeClassFileContents(org.eclipse.jdt.internal.compiler.ClassFile, org.eclipse.core.resources.IFile, java.lang.String, boolean, org.eclipse.jdt.internal.core.builder.SourceFile) */
@Override protected void writeClassFileContents(ClassFile classfile, IFile file, String qualifiedFileName, boolean isTopLevelType, SourceFile compilationUnit) throws CoreException { // Before writing out the class file, compare it to the previous file // If structural changes occurred then add dependent source files byte[] bytes = classfile.getBytes(); if (file.exists()) { if (writeClassFileCheck(file, qualifiedFileName, bytes) || compilationUnit.updateClassFile) { // see 46093 if (JavaBuilder.DEBUG) System.out.println("Writing changed class file " + file.getName());//$NON-NLS-1$ if (!file.isDerived()) file.setDerived(true, null); file.setContents(new ByteArrayInputStream(bytes), true, false, null); } else if (JavaBuilder.DEBUG) { System.out.println("Skipped over unchanged class file " + file.getName());//$NON-NLS-1$ } } else { if (isTopLevelType) addDependentsOf(new Path(qualifiedFileName), true); // new type if (JavaBuilder.DEBUG) System.out.println("Writing new class file " + file.getName());//$NON-NLS-1$ try { file.create(new ByteArrayInputStream(bytes), IResource.FORCE | IResource.DERIVED, null); } catch (CoreException e) { if (e.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) { IStatus status = e.getStatus(); if (status instanceof IResourceStatus) { IPath oldFilePath = ((IResourceStatus) status).getPath(); char[] oldTypeName = oldFilePath.removeFileExtension().lastSegment().toCharArray(); char[][] previousTypeNames = this.newState.getDefinedTypeNamesFor(compilationUnit.typeLocator()); boolean fromSameFile = false; if (previousTypeNames == null) { fromSameFile = CharOperation.equals(compilationUnit.getMainTypeName(), oldTypeName); } else { for (int i = 0, l = previousTypeNames.length; i < l; i++) { if (CharOperation.equals(previousTypeNames[i], oldTypeName)) { fromSameFile = true; break; } } } if (fromSameFile) { // file is defined by the same compilationUnit, but won't be deleted until later so do it now IFile collision = file.getParent().getFile(new Path(oldFilePath.lastSegment())); collision.delete(true, false, null); boolean success = false; try { file.create(new ByteArrayInputStream(bytes), IResource.FORCE | IResource.DERIVED, null); success = true; } catch (CoreException ignored) { // ignore the second exception } if (success) return; } } // catch the case that a type has been renamed and collides on disk with an as-yet-to-be-deleted type throw new AbortCompilation(true, new AbortIncrementalBuildException(qualifiedFileName)); } throw e; // rethrow } } } protected boolean writeClassFileCheck(IFile file, String fileName, byte[] newBytes) throws CoreException { try { byte[] oldBytes = Util.getResourceContentsAsByteArray(file); notEqual : if (newBytes.length == oldBytes.length) { for (int i = newBytes.length; --i >= 0;) if (newBytes[i] != oldBytes[i]) break notEqual; return false; // bytes are identical so skip them } URI location = file.getLocationURI(); if (location == null) return false; // unable to determine location of this class file String filePath = location.getSchemeSpecificPart(); ClassFileReader reader = new ClassFileReader(oldBytes, filePath.toCharArray()); // ignore local types since they're only visible inside a single method if (!(reader.isLocal() || reader.isAnonymous()) && reader.hasStructuralChanges(newBytes)) { if (JavaBuilder.DEBUG) System.out.println("Type has structural changes " + fileName); //$NON-NLS-1$ addDependentsOf(new Path(fileName), true); this.newState.wasStructurallyChanged(fileName); } } catch (JavaModelException jme) { Throwable e = jme.getCause(); if (e instanceof CoreException) { // assuming a ResourceException during IFile.getContents(), treat it like a corrupt file addDependentsOf(new Path(fileName), true); this.newState.wasStructurallyChanged(fileName); } else { throw jme; } } catch (ClassFormatException e) { addDependentsOf(new Path(fileName), true); this.newState.wasStructurallyChanged(fileName); } return true; } @Override public String toString() { return "incremental image builder for:\n\tnew state: " + this.newState; //$NON-NLS-1$ } /* Debug helper static void dump(IResourceDelta delta) { StringBuffer buffer = new StringBuffer(); IPath path = delta.getFullPath(); for (int i = path.segmentCount(); --i > 0;) buffer.append(" "); switch (delta.getKind()) { case IResourceDelta.ADDED: buffer.append('+'); break; case IResourceDelta.REMOVED: buffer.append('-'); break; case IResourceDelta.CHANGED: buffer.append('*'); break; case IResourceDelta.NO_CHANGE: buffer.append('='); break; default: buffer.append('?'); break; } buffer.append(path); System.out.println(buffer.toString()); IResourceDelta[] children = delta.getAffectedChildren(); for (int i = 0, l = children.length; i < l; i++) dump(children[i]); } */ }