/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.builder;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
/**
* The incremental image builder
*/
public class IncrementalImageBuilder extends AbstractImageBuilder {
protected ArrayList sourceFiles;
protected ArrayList previousSourceFiles;
protected StringSet qualifiedStrings;
protected StringSet simpleStrings;
protected StringSet rootStrings;
protected SimpleLookupTable secondaryTypesToRemove;
protected boolean hasStructuralChanges;
protected int compileLoop;
protected boolean makeOutputFolderConsistent;
public static int MaxCompileLoop= 5; // perform a full build if it takes more than ? incremental compile loops
protected IncrementalImageBuilder(JavaBuilder javaBuilder, State buildState) {
super(javaBuilder, true, buildState);
this.nameEnvironment.isIncrementalBuild= true;
this.makeOutputFolderConsistent= JavaCore.ENABLED.equals(
javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_RECREATE_MODIFIED_CLASS_FILES_IN_OUTPUT_FOLDER, true));
}
protected IncrementalImageBuilder(JavaBuilder javaBuilder) {
this(javaBuilder, null);
this.newState.copyFrom(javaBuilder.lastState);
}
protected IncrementalImageBuilder(BatchImageBuilder batchBuilder) {
this(batchBuilder.javaBuilder, batchBuilder.newState);
resetCollections();
}
public boolean build(SimpleLookupTable deltas) {
// 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 mssing 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 false;
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);
}
this.compileLoop= 0;
float increment= 0.40f;
while (this.sourceFiles.size() > 0) { // added to in acceptResult
if (++this.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();
}
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();
}
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.elementSize == 0 && this.simpleStrings.elementSize == 0)
return;
addAffectedSourceFiles(this.qualifiedStrings, this.simpleStrings, this.rootStrings, null);
}
protected void addAffectedSourceFiles(StringSet qualifiedSet, StringSet simpleSet, StringSet rootSet, StringSet 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.elementSize)
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.elementSize)
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.includes(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, StringSet qualifiedNames, StringSet simpleNames, StringSet 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
}
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;
}
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;
this.compileLoop= 0;
}
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);
}
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)) {
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
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$
this.sourceFiles.add(new SourceFile((IFile)resource, md, true));
}
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;
}
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);
}
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 ArrayList(33);
this.previousSourceFiles= null;
this.qualifiedStrings= new StringSet(3);
this.simpleStrings= new StringSet(3);
this.rootStrings= new StringSet(3);
this.hasStructuralChanges= false;
this.compileLoop= 0;
} else {
this.previousSourceFiles= this.sourceFiles.isEmpty() ? null : (ArrayList)this.sourceFiles.clone();
this.sourceFiles.clear();
this.qualifiedStrings.clear();
this.simpleStrings.clear();
this.rootStrings.clear();
this.workQueue.clear();
}
}
protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
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);
}
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 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)
*/
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 (ClassFormatException e) {
addDependentsOf(new Path(fileName), true);
this.newState.wasStructurallyChanged(fileName);
}
return true;
}
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]);
}
*/
}