| /* ******************************************************************* |
| * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). |
| * 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: |
| * PARC initial implementation |
| * Andy Clement overhauled |
| * ******************************************************************/ |
| |
| package org.aspectj.ajdt.internal.core.builder; |
| |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.util.AbstractMap; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.aspectj.ajdt.internal.compiler.CompilationResultDestinationManager; |
| import org.aspectj.ajdt.internal.compiler.InterimCompilationResult; |
| import org.aspectj.ajdt.internal.core.builder.AjBuildConfig.BinarySourceFile; |
| import org.aspectj.apache.bcel.classfile.ClassParser; |
| import org.aspectj.asm.AsmManager; |
| import org.aspectj.bridge.IMessage; |
| import org.aspectj.bridge.Message; |
| import org.aspectj.bridge.SourceLocation; |
| import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.CompilationResult; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.batch.FileSystem; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.env.IBinaryField; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.env.IBinaryMethod; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.env.IBinaryNestedType; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.env.IBinaryType; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.env.INameEnvironment; |
| import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; |
| import org.aspectj.org.eclipse.jdt.internal.core.builder.ReferenceCollection; |
| import org.aspectj.org.eclipse.jdt.internal.core.builder.StringSet; |
| import org.aspectj.util.FileUtil; |
| import org.aspectj.weaver.BCException; |
| import org.aspectj.weaver.CompressingDataOutputStream; |
| import org.aspectj.weaver.ReferenceType; |
| import org.aspectj.weaver.ReferenceTypeDelegate; |
| import org.aspectj.weaver.ResolvedType; |
| import org.aspectj.weaver.bcel.BcelWeaver; |
| import org.aspectj.weaver.bcel.BcelWorld; |
| import org.aspectj.weaver.bcel.TypeDelegateResolver; |
| import org.aspectj.weaver.bcel.UnwovenClassFile; |
| |
| /** |
| * Maintains state needed for incremental compilation |
| */ |
| public class AjState implements CompilerConfigurationChangeFlags, TypeDelegateResolver { |
| |
| // SECRETAPI configures whether we use state instead of lastModTime - see pr245566 |
| public static boolean CHECK_STATE_FIRST = true; |
| |
| // SECRETAPI static so beware of multi-threading bugs... |
| public static IStateListener stateListener = null; |
| |
| public static boolean FORCE_INCREMENTAL_DURING_TESTING = false; |
| |
| static int PATHID_CLASSPATH = 0; |
| static int PATHID_ASPECTPATH = 1; |
| static int PATHID_INPATH = 2; |
| |
| private static int CLASS_FILE_NO_CHANGES = 0; |
| private static int CLASS_FILE_CHANGED_THAT_NEEDS_INCREMENTAL_BUILD = 1; |
| private static int CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD = 2; |
| |
| private static final char[][] EMPTY_CHAR_ARRAY = new char[0][]; |
| |
| // now follows non static, but transient state - no need to write out, doesn't need reinitializing |
| // State recreated for each build: |
| |
| /** |
| * When looking at changes on the classpath, this set accumulates files in our state instance that affected by those changes. |
| * Then if we can do an incremental build - these must be compiled. |
| */ |
| private final Set<File> affectedFiles = new HashSet<File>(); |
| |
| // these are references created on a particular compile run - when looping round in |
| // addAffectedSourceFiles(), if some have been created then we look at which source files |
| // touch upon those and get them recompiled. |
| private StringSet qualifiedStrings = new StringSet(3); |
| |
| private StringSet simpleStrings = new StringSet(3); |
| |
| private Set<File> addedFiles; |
| private Set<File> deletedFiles; |
| private Set<BinarySourceFile> addedBinaryFiles; |
| private Set<BinarySourceFile> deletedBinaryFiles; |
| // For a particular build run, this set records the changes to classesFromName |
| public final Set<String> deltaAddedClasses = new HashSet<String>(); |
| |
| // now follows non static, but transient state - no need to write out, DOES need reinitializing when read AjState instance |
| // reloaded |
| |
| private final AjBuildManager buildManager; |
| private INameEnvironment nameEnvironment; |
| private FileSystem fileSystem; |
| |
| // now follows normal state that must be written out |
| |
| private boolean couldBeSubsequentIncrementalBuild = false; |
| private boolean batchBuildRequiredThisTime = false; |
| private AjBuildConfig buildConfig; |
| private long lastSuccessfulFullBuildTime = -1; |
| private final Hashtable<String, Long> structuralChangesSinceLastFullBuild = new Hashtable<String, Long>(); |
| private long lastSuccessfulBuildTime = -1; |
| private long currentBuildTime = -1; |
| private AsmManager structureModel; |
| |
| /** |
| * For a given source file, records the ClassFiles (which contain a fully qualified name and a file name) that were created when |
| * the source file was compiled. Populated in noteResult and used in addDependentsOf(File) |
| */ |
| private final Map<File, List<ClassFile>> fullyQualifiedTypeNamesResultingFromCompilationUnit = new HashMap<File, List<ClassFile>>(); |
| |
| /** |
| * Source files defining aspects Populated in noteResult and used in processDeletedFiles |
| */ |
| private final Set<File> sourceFilesDefiningAspects = new HashSet<File>(); |
| |
| /** |
| * Populated in noteResult to record the set of types that should be recompiled if the given file is modified or deleted. |
| * Referred to during addAffectedSourceFiles when calculating incremental compilation set. |
| */ |
| private final Map<File, ReferenceCollection> references = new HashMap<File, ReferenceCollection>(); |
| |
| /** |
| * Holds UnwovenClassFiles (byte[]s) originating from the given file source. This could be a jar file, a directory, or an |
| * individual .class file. This is an *expensive* map. It is cleared immediately following a batch build, and the cheaper |
| * inputClassFilesBySource map is kept for processing of any subsequent incremental builds. |
| * |
| * Populated during AjBuildManager.initBcelWorld(). |
| * |
| * Passed into AjCompiler adapter as the set of binary input files to reweave if the weaver determines a full weave is required. |
| * |
| * Cleared during initBcelWorld prior to repopulation. |
| * |
| * Used when a file is deleted during incremental compilation to delete all of the class files in the output directory that |
| * resulted from the weaving of File. |
| * |
| * Used during getBinaryFilesToCompile when compiling incrementally to determine which files should be recompiled if a given |
| * input file has changed. |
| * |
| */ |
| private Map<String, List<UnwovenClassFile>> binarySourceFiles = new HashMap<String, List<UnwovenClassFile>>(); |
| |
| /** |
| * Initially a duplicate of the information held in binarySourceFiles, with the key difference that the values are ClassFiles |
| * (type name, File) not UnwovenClassFiles (which also have all the byte code in them). After a batch build, binarySourceFiles |
| * is cleared, leaving just this much lighter weight map to use in processing subsequent incremental builds. |
| */ |
| private final Map<String, List<ClassFile>> inputClassFilesBySource = new HashMap<String, List<ClassFile>>(); |
| |
| /** |
| * A list of the .class files created by this state that contain aspects. |
| */ |
| private final List<String> aspectClassFiles = new ArrayList<String>(); |
| |
| /** |
| * Holds structure information on types as they were at the end of the last build. It would be nice to get rid of this too, but |
| * can't see an easy way to do that right now. |
| */ |
| private final Map<String, CompactTypeStructureRepresentation> resolvedTypeStructuresFromLastBuild = new HashMap<String, CompactTypeStructureRepresentation>(); |
| |
| /** |
| * Populated in noteResult to record the set of UnwovenClassFiles (intermediate results) that originated from compilation of the |
| * class with the given fully-qualified name. |
| * |
| * Used in removeAllResultsOfLastBuild to remove .class files from output directory. |
| * |
| * Passed into StatefulNameEnvironment during incremental compilation to support findType lookups. |
| */ |
| private final Map<String, File> classesFromName = new HashMap<String, File>(); |
| |
| /** |
| * Populated by AjBuildManager to record the aspects with the file name in which they're contained. This is later used when |
| * writing the outxml file in AjBuildManager. Need to record the file name because want to write an outxml file for each of the |
| * output directories and in order to ask the OutputLocationManager for the output location for a given aspect we need the file |
| * in which it is contained. |
| */ |
| private Map<String, char[]> aspectsFromFileNames; |
| |
| private Set<File> compiledSourceFiles = new HashSet<File>(); |
| private final Map<String, File> resources = new HashMap<String, File>(); |
| |
| SoftHashMap/* <baseDir,SoftHashMap<theFile,className>> */fileToClassNameMap = new SoftHashMap(); |
| |
| private BcelWeaver weaver; |
| private BcelWorld world; |
| |
| // --- below here is unsorted state |
| |
| // --- |
| |
| public AjState(AjBuildManager buildManager) { |
| this.buildManager = buildManager; |
| } |
| |
| public void setCouldBeSubsequentIncrementalBuild(boolean yesThereCould) { |
| this.couldBeSubsequentIncrementalBuild = yesThereCould; |
| } |
| |
| void successfulCompile(AjBuildConfig config, boolean wasFullBuild) { |
| buildConfig = config; |
| lastSuccessfulBuildTime = currentBuildTime; |
| if (stateListener != null) { |
| stateListener.buildSuccessful(wasFullBuild); |
| } |
| if (wasFullBuild) { |
| lastSuccessfulFullBuildTime = currentBuildTime; |
| } |
| } |
| |
| /** |
| * Returns false if a batch build is needed. |
| */ |
| public boolean prepareForNextBuild(AjBuildConfig newBuildConfig) { |
| currentBuildTime = System.currentTimeMillis(); |
| |
| if (!maybeIncremental()) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "Preparing for build: not going to be incremental because either not in AJDT or incremental deactivated"); |
| } |
| return false; |
| } |
| |
| if (this.batchBuildRequiredThisTime) { |
| this.batchBuildRequiredThisTime = false; |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "Preparing for build: not going to be incremental this time because batch build explicitly forced"); |
| } |
| return false; |
| } |
| |
| if (lastSuccessfulBuildTime == -1 || buildConfig == null) { |
| structuralChangesSinceLastFullBuild.clear(); |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "Preparing for build: not going to be incremental because no successful previous full build"); |
| } |
| return false; |
| } |
| |
| // we don't support incremental with an outjar yet |
| if (newBuildConfig.getOutputJar() != null) { |
| structuralChangesSinceLastFullBuild.clear(); |
| if (listenerDefined()) { |
| getListener().recordDecision("Preparing for build: not going to be incremental because outjar being used"); |
| } |
| return false; |
| } |
| |
| affectedFiles.clear(); |
| |
| // we can't do an incremental build if one of our paths |
| // has changed, or a jar on a path has been modified |
| if (pathChange(buildConfig, newBuildConfig)) { |
| // last time we built, .class files and resource files from jars on the |
| // inpath will have been copied to the output directory. |
| // these all need to be deleted in preparation for the clean build that is |
| // coming - otherwise a file that has been deleted from an inpath jar |
| // since the last build will not be deleted from the output directory. |
| removeAllResultsOfLastBuild(); |
| if (stateListener != null) { |
| stateListener.pathChangeDetected(); |
| } |
| structuralChangesSinceLastFullBuild.clear(); |
| if (listenerDefined()) { |
| getListener() |
| .recordDecision( |
| "Preparing for build: not going to be incremental because path change detected (one of classpath/aspectpath/inpath/injars)"); |
| } |
| return false; |
| } |
| |
| if (simpleStrings.elementSize > 20) { |
| simpleStrings = new StringSet(3); |
| } else { |
| simpleStrings.clear(); |
| } |
| if (qualifiedStrings.elementSize > 20) { |
| qualifiedStrings = new StringSet(3); |
| } else { |
| qualifiedStrings.clear(); |
| } |
| |
| if ((newBuildConfig.getChanged() & PROJECTSOURCEFILES_CHANGED) == 0) { |
| addedFiles = Collections.emptySet(); |
| deletedFiles = Collections.emptySet(); |
| } else { |
| Set<File> oldFiles = new HashSet<File>(buildConfig.getFiles()); |
| Set<File> newFiles = new HashSet<File>(newBuildConfig.getFiles()); |
| |
| addedFiles = new HashSet<File>(newFiles); |
| addedFiles.removeAll(oldFiles); |
| deletedFiles = new HashSet<File>(oldFiles); |
| deletedFiles.removeAll(newFiles); |
| } |
| |
| Set<BinarySourceFile> oldBinaryFiles = new HashSet<BinarySourceFile>(buildConfig.getBinaryFiles()); |
| Set<BinarySourceFile> newBinaryFiles = new HashSet<BinarySourceFile>(newBuildConfig.getBinaryFiles()); |
| |
| addedBinaryFiles = new HashSet<BinarySourceFile>(newBinaryFiles); |
| addedBinaryFiles.removeAll(oldBinaryFiles); |
| deletedBinaryFiles = new HashSet<BinarySourceFile>(oldBinaryFiles); |
| deletedBinaryFiles.removeAll(newBinaryFiles); |
| |
| boolean couldStillBeIncremental = processDeletedFiles(deletedFiles); |
| |
| if (!couldStillBeIncremental) { |
| if (listenerDefined()) { |
| getListener().recordDecision("Preparing for build: not going to be incremental because an aspect was deleted"); |
| } |
| return false; |
| } |
| |
| if (listenerDefined()) { |
| getListener().recordDecision("Preparing for build: planning to be an incremental build"); |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if any of the files in the set passed in contains an aspect declaration. If one is found then we start the process of |
| * batch building, i.e. we remove all the results of the last build, call any registered listener to tell them whats happened |
| * and return false. |
| * |
| * @return false if we discovered an aspect declaration |
| */ |
| private boolean processDeletedFiles(Set<File> deletedFiles) { |
| for (File deletedFile : deletedFiles) { |
| if (this.sourceFilesDefiningAspects.contains(deletedFile)) { |
| removeAllResultsOfLastBuild(); |
| if (stateListener != null) { |
| stateListener.detectedAspectDeleted(deletedFile); |
| } |
| return false; |
| } |
| List<ClassFile> classes = fullyQualifiedTypeNamesResultingFromCompilationUnit.get(deletedFile); |
| if (classes != null) { |
| for (ClassFile cf : classes) { |
| resolvedTypeStructuresFromLastBuild.remove(cf.fullyQualifiedTypeName); |
| } |
| } |
| } |
| return true; |
| } |
| |
| private Collection<File> getModifiedFiles() { |
| return getModifiedFiles(lastSuccessfulBuildTime); |
| } |
| |
| Collection<File> getModifiedFiles(long lastBuildTime) { |
| Set<File> ret = new HashSet<File>(); |
| |
| // Check if the build configuration knows what files have changed... |
| List<File> modifiedFiles = buildConfig.getModifiedFiles(); |
| |
| if (modifiedFiles == null) { |
| // do not know, so need to go looking |
| // not our job to account for new and deleted files |
| for (Iterator<File> i = buildConfig.getFiles().iterator(); i.hasNext();) { |
| File file = i.next(); |
| if (!file.exists()) { |
| continue; |
| } |
| |
| long modTime = file.lastModified(); |
| // System.out.println("check: " + file + " mod " + modTime + " build " + lastBuildTime); |
| // need to add 1000 since lastModTime is only accurate to a second on some (all?) platforms |
| if (modTime + 1000 > lastBuildTime) { |
| ret.add(file); |
| } |
| } |
| } else { |
| ret.addAll(modifiedFiles); |
| } |
| ret.addAll(affectedFiles); |
| return ret; |
| } |
| |
| private Collection<BinarySourceFile> getModifiedBinaryFiles() { |
| return getModifiedBinaryFiles(lastSuccessfulBuildTime); |
| } |
| |
| Collection<BinarySourceFile> getModifiedBinaryFiles(long lastBuildTime) { |
| List<BinarySourceFile> ret = new ArrayList<BinarySourceFile>(); |
| // not our job to account for new and deleted files |
| for (Iterator<BinarySourceFile> i = buildConfig.getBinaryFiles().iterator(); i.hasNext();) { |
| AjBuildConfig.BinarySourceFile bsfile = i.next(); |
| File file = bsfile.binSrc; |
| if (!file.exists()) { |
| continue; |
| } |
| |
| long modTime = file.lastModified(); |
| // System.out.println("check: " + file + " mod " + modTime + " build " + lastBuildTime); |
| // need to add 1000 since lastModTime is only accurate to a second on some (all?) platforms |
| if (modTime + 1000 >= lastBuildTime) { |
| ret.add(bsfile); |
| } |
| } |
| return ret; |
| } |
| |
| private void recordDecision(String decision) { |
| getListener().recordDecision(decision); |
| } |
| |
| /** |
| * Analyse .class files in the directory specified, if they have changed since the last successful build then see if we can |
| * determine which source files in our project depend on the change. If we can then we can still do an incremental build, if we |
| * can't then we have to do a full build. |
| * |
| */ |
| private int classFileChangedInDirSinceLastBuildRequiringFullBuild(File dir, int pathid) { |
| |
| if (!dir.isDirectory()) { |
| if (listenerDefined()) { |
| recordDecision("ClassFileChangeChecking: not a directory so forcing full build: '" + dir.getPath() + "'"); |
| } |
| return CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD; |
| } |
| |
| // Are we managing that output directory? |
| AjState state = IncrementalStateManager.findStateManagingOutputLocation(dir); |
| if (listenerDefined()) { |
| if (state != null) { |
| recordDecision("ClassFileChangeChecking: found state instance managing output location : " + dir); |
| } else { |
| recordDecision("ClassFileChangeChecking: failed to find a state instance managing output location : " + dir + " (could be getting managed by JDT)"); |
| } |
| } |
| |
| // pr268827 - this guard will cause us to exit quickly if the state says there really is |
| // nothing of interest. This will not catch the case where a user modifies the .class files outside of |
| // eclipse because the state will not be aware of it. But that seems an unlikely scenario and |
| // we are paying a heavy price to check it |
| if (state != null && !state.hasAnyStructuralChangesSince(lastSuccessfulBuildTime)) { |
| if (listenerDefined()) { |
| getListener().recordDecision("ClassFileChangeChecking: no reported changes in that state"); |
| } |
| return CLASS_FILE_NO_CHANGES; |
| } |
| |
| if (state == null) { |
| // This may be because the directory is the output path of a Java project upon which we depend |
| // we need to call back into AJDT to ask about that projects state. |
| CompilationResultDestinationManager crdm = buildConfig.getCompilationResultDestinationManager(); |
| if (crdm != null) { |
| int i = crdm.discoverChangesSince(dir, lastSuccessfulBuildTime); |
| // 0 = dontknow if it has changed |
| // 1 = definetly not changed at all |
| // further numbers can determine more granular changes |
| if (i == 1) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: queried JDT and '" + dir |
| + "' is apparently unchanged so not performing timestamp check"); |
| } |
| return CLASS_FILE_NO_CHANGES; |
| } |
| } |
| } |
| |
| List<File> classFiles = FileUtil.listClassFiles(dir); |
| |
| for (Iterator<File> iterator = classFiles.iterator(); iterator.hasNext();) { |
| File classFile = iterator.next(); |
| if (CHECK_STATE_FIRST && state != null) { |
| // Next section reworked based on bug 270033: |
| // if it is an aspect we may or may not be in trouble depending on whether (a) we depend on it (b) it is on the |
| // classpath or the aspectpath |
| if (state.isAspect(classFile)) { |
| boolean hasStructuralChanges = state.hasStructuralChangedSince(classFile, lastSuccessfulBuildTime); |
| if (hasStructuralChanges || isTypeWeReferTo(classFile)) { |
| if (hasStructuralChanges) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: aspect found that has structurally changed : " + classFile); |
| } |
| return CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD; |
| } else { |
| // must be 'isTypeWeReferTo()' |
| if (pathid == PATHID_CLASSPATH) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: aspect found that this project refers to : " + classFile |
| + " but only referred to via classpath"); |
| } |
| } else { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: aspect found that this project refers to : " + classFile |
| + " from either inpath/aspectpath, switching to full build"); |
| } |
| return CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD; |
| } |
| } |
| |
| } else { |
| // it is an aspect but we don't refer to it: |
| // - for CLASSPATH I think this is OK, we can continue and try an |
| // incremental build |
| // - for ASPECTPATH we don't know what else might be touched in this project |
| // and must rebuild |
| if (pathid == PATHID_CLASSPATH) { |
| if (listenerDefined()) { |
| getListener() |
| .recordDecision( |
| "ClassFileChangeChecking: found aspect on classpath but this project doesn't reference it, continuing to try for incremental build : " |
| + classFile); |
| } |
| } else { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: found aspect on aspectpath/inpath - can't determine if this project is affected, must full build: " |
| + classFile); |
| } |
| return CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD; |
| } |
| } |
| |
| } |
| if (state.hasStructuralChangedSince(classFile, lastSuccessfulBuildTime)) { |
| if (listenerDefined()) { |
| getListener().recordDecision("ClassFileChangeChecking: structural change detected in : " + classFile); |
| } |
| isTypeWeReferTo(classFile); |
| } |
| } else { |
| long modTime = classFile.lastModified(); |
| if ((modTime + 1000) >= lastSuccessfulBuildTime) { |
| // so the class on disk has changed since the last successful build for this state object |
| |
| // BUG? we stop on the first change that leads us to an incremental build, surely we need to continue and look |
| // at all files incase another change means we need to incremental a bit more stuff? |
| |
| // To work out if it is a real change we should ask any state |
| // object managing the output location whether the file has |
| // structurally changed or not |
| if (state != null) { |
| if (state.isAspect(classFile)) { |
| if (state.hasStructuralChangedSince(classFile, lastSuccessfulBuildTime) || isTypeWeReferTo(classFile)) { |
| // further improvements possible |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: aspect found that has structurally changed or that this project depends upon : " |
| + classFile); |
| } |
| return CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD; |
| } else { |
| // it is an aspect but we don't refer to it: |
| // - for CLASSPATH I think this is OK, we can continue and try an |
| // incremental build |
| // - for ASPECTPATH we don't know what else might be touched in this project |
| // and must rebuild |
| if (pathid == PATHID_CLASSPATH) { |
| if (listenerDefined()) { |
| getListener() |
| .recordDecision( |
| "ClassFileChangeChecking: found aspect on classpath but this project doesn't reference it, continuing to try for incremental build : " |
| + classFile); |
| } |
| } else { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: found aspect on aspectpath/inpath - can't determine if this project is affected, must full build: " |
| + classFile); |
| } |
| return CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD; |
| } |
| } |
| } |
| if (state.hasStructuralChangedSince(classFile, lastSuccessfulBuildTime)) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: structural change detected in : " + classFile); |
| } |
| isTypeWeReferTo(classFile); |
| } else { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "ClassFileChangeChecking: change detected in " + classFile + " but it is not structural"); |
| } |
| } |
| } else { |
| // No state object to ask, so it only matters if we know which type depends on this file |
| if (isTypeWeReferTo(classFile)) { |
| return CLASS_FILE_CHANGED_THAT_NEEDS_INCREMENTAL_BUILD; |
| } else { |
| return CLASS_FILE_NO_CHANGES; |
| } |
| } |
| } |
| } |
| } |
| return CLASS_FILE_NO_CHANGES; |
| } |
| |
| private boolean isAspect(File file) { |
| return aspectClassFiles.contains(file.getAbsolutePath()); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| public static class SoftHashMap extends AbstractMap { |
| |
| private final Map map; |
| |
| private final ReferenceQueue rq = new ReferenceQueue(); |
| |
| public SoftHashMap(Map map) { |
| this.map = map; |
| } |
| |
| public SoftHashMap() { |
| this(new HashMap()); |
| } |
| |
| public SoftHashMap(Map map, boolean b) { |
| this(map); |
| } |
| |
| class SoftReferenceKnownKey extends SoftReference { |
| |
| private final Object key; |
| |
| @SuppressWarnings("unchecked") |
| SoftReferenceKnownKey(Object k, Object v) { |
| super(v, rq); |
| this.key = k; |
| } |
| } |
| |
| private void processQueue() { |
| SoftReferenceKnownKey sv = null; |
| while ((sv = (SoftReferenceKnownKey) rq.poll()) != null) { |
| map.remove(sv.key); |
| } |
| } |
| |
| @Override |
| public Object get(Object key) { |
| SoftReferenceKnownKey value = (SoftReferenceKnownKey) map.get(key); |
| if (value == null) { |
| return null; |
| } |
| if (value.get() == null) { |
| // it got GC'd |
| map.remove(value.key); |
| return null; |
| } else { |
| return value.get(); |
| } |
| } |
| |
| @Override |
| public Object put(Object k, Object v) { |
| processQueue(); |
| return map.put(k, new SoftReferenceKnownKey(k, v)); |
| } |
| |
| @Override |
| public Set entrySet() { |
| return map.entrySet(); |
| } |
| |
| @Override |
| public void clear() { |
| processQueue(); |
| map.clear(); |
| } |
| |
| @Override |
| public int size() { |
| processQueue(); |
| return map.size(); |
| } |
| |
| @Override |
| public Object remove(Object k) { |
| processQueue(); |
| SoftReferenceKnownKey value = (SoftReferenceKnownKey) map.remove(k); |
| if (value == null) { |
| return null; |
| } |
| if (value.get() != null) { |
| return value.get(); |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * If a class file has changed in a path on our classpath, it may not be for a type that any of our source files care about. |
| * This method checks if any of our source files have a dependency on the class in question and if not, we don't consider it an |
| * interesting change. |
| */ |
| private boolean isTypeWeReferTo(File file) { |
| String fpath = file.getAbsolutePath(); |
| int finalSeparator = fpath.lastIndexOf(File.separator); |
| String baseDir = fpath.substring(0, finalSeparator); |
| String theFile = fpath.substring(finalSeparator + 1); |
| SoftHashMap classNames = (SoftHashMap) fileToClassNameMap.get(baseDir); |
| if (classNames == null) { |
| classNames = new SoftHashMap(); |
| fileToClassNameMap.put(baseDir, classNames); |
| } |
| char[] className = (char[]) classNames.get(theFile); |
| if (className == null) { |
| // if (listenerDefined()) |
| // getListener().recordDecision("Cache miss, looking up classname for : " + fpath); |
| |
| ClassFileReader cfr; |
| try { |
| cfr = ClassFileReader.read(file); |
| } catch (ClassFormatException e) { |
| return true; |
| } catch (IOException e) { |
| return true; |
| } |
| className = cfr.getName(); |
| classNames.put(theFile, className); |
| // } else { |
| // if (listenerDefined()) |
| // getListener().recordDecision("Cache hit, looking up classname for : " + fpath); |
| } |
| |
| char[][][] qualifiedNames = null; |
| char[][] simpleNames = null; |
| if (CharOperation.indexOf('/', className) != -1) { |
| qualifiedNames = new char[1][][]; |
| qualifiedNames[0] = CharOperation.splitOn('/', className); |
| qualifiedNames = ReferenceCollection.internQualifiedNames(qualifiedNames); |
| } else { |
| simpleNames = new char[1][]; |
| simpleNames[0] = className; |
| simpleNames = ReferenceCollection.internSimpleNames(simpleNames, true); |
| } |
| int newlyAffectedFiles = 0; |
| for (Iterator<Map.Entry<File, ReferenceCollection>> i = references.entrySet().iterator(); i.hasNext();) { |
| Map.Entry<File, ReferenceCollection> entry = i.next(); |
| ReferenceCollection refs = entry.getValue(); |
| if (refs != null && refs.includes(qualifiedNames, simpleNames)) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| toString() + ": type " + new String(className) + " is depended upon by '" + entry.getKey() + "'"); |
| } |
| newlyAffectedFiles++; |
| // possibly the beginnings of addressing the second point in 270033 comment 3 |
| // List/*ClassFile*/ cfs = (List)this.fullyQualifiedTypeNamesResultingFromCompilationUnit.get(entry.getKey()); |
| affectedFiles.add(entry.getKey()); |
| } |
| } |
| if (newlyAffectedFiles > 0) { |
| return true; |
| } |
| if (listenerDefined()) { |
| getListener().recordDecision(toString() + ": type " + new String(className) + " is not depended upon by this state"); |
| } |
| return false; |
| } |
| |
| // /** |
| // * For a given class file, determine which source file it came from. This will only succeed if the class file is from a source |
| // * file within this project. |
| // */ |
| // private File getSourceFileForClassFile(File classfile) { |
| // Set sourceFiles = fullyQualifiedTypeNamesResultingFromCompilationUnit.keySet(); |
| // for (Iterator sourceFileIterator = sourceFiles.iterator(); sourceFileIterator.hasNext();) { |
| // File sourceFile = (File) sourceFileIterator.next(); |
| // List/* ClassFile */classesFromSourceFile = (List/* ClassFile */) fullyQualifiedTypeNamesResultingFromCompilationUnit |
| // .get(sourceFile); |
| // for (int i = 0; i < classesFromSourceFile.size(); i++) { |
| // if (((ClassFile) classesFromSourceFile.get(i)).locationOnDisk.equals(classfile)) |
| // return sourceFile; |
| // } |
| // } |
| // return null; |
| // } |
| |
| @Override |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| // null config means failed build i think as it is only set on successful full build? |
| sb.append("AjState(").append((buildConfig == null ? "NULLCONFIG" : buildConfig.getConfigFile().toString())).append(")"); |
| return sb.toString(); |
| } |
| |
| /** |
| * Determine if a file has changed since a given time, using the local information recorded in the structural changes data |
| * structure. |
| * |
| * @param file the file we are wondering about |
| * @param lastSuccessfulBuildTime the last build time for the state asking the question |
| */ |
| private boolean hasStructuralChangedSince(File file, long lastSuccessfulBuildTime) { |
| // long lastModTime = file.lastModified(); |
| Long l = structuralChangesSinceLastFullBuild.get(file.getAbsolutePath()); |
| long strucModTime = -1; |
| if (l != null) { |
| strucModTime = l.longValue(); |
| } else { |
| strucModTime = this.lastSuccessfulFullBuildTime; |
| } |
| // we now have: |
| // 'strucModTime'-> the last time the class was structurally changed |
| return (strucModTime > lastSuccessfulBuildTime); |
| } |
| |
| /** |
| * Determine if anything has changed since a given time. |
| */ |
| private boolean hasAnyStructuralChangesSince(long lastSuccessfulBuildTime) { |
| Set<Map.Entry<String, Long>> entries = structuralChangesSinceLastFullBuild.entrySet(); |
| for (Iterator<Map.Entry<String, Long>> iterator = entries.iterator(); iterator.hasNext();) { |
| Map.Entry<String, Long> entry = iterator.next(); |
| Long l = entry.getValue(); |
| if (l != null) { |
| long lvalue = l.longValue(); |
| if (lvalue > lastSuccessfulBuildTime) { |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "Seems this has changed " + entry.getKey() + "modtime=" + lvalue + " lsbt=" |
| + this.lastSuccessfulFullBuildTime + " incoming check value=" + lastSuccessfulBuildTime); |
| } |
| return true; |
| } |
| } |
| } |
| return (this.lastSuccessfulFullBuildTime > lastSuccessfulBuildTime); |
| } |
| |
| /** |
| * Determine if something has changed on the classpath/inpath/aspectpath and a full build is required rather than an incremental |
| * one. |
| * |
| * @param previousConfig the previous configuration used |
| * @param newConfig the new configuration being used |
| * @return true if full build required |
| */ |
| private boolean pathChange(AjBuildConfig previousConfig, AjBuildConfig newConfig) { |
| int changes = newConfig.getChanged(); |
| |
| if ((changes & (CLASSPATH_CHANGED | ASPECTPATH_CHANGED | INPATH_CHANGED | OUTPUTDESTINATIONS_CHANGED | INJARS_CHANGED)) != 0) { |
| List<File> oldOutputLocs = getOutputLocations(previousConfig); |
| |
| Set<String> alreadyAnalysedPaths = new HashSet<String>(); |
| |
| List<String> oldClasspath = previousConfig.getClasspath(); |
| List<String> newClasspath = newConfig.getClasspath(); |
| if (stateListener != null) { |
| stateListener.aboutToCompareClasspaths(oldClasspath, newClasspath); |
| } |
| if (classpathChangedAndNeedsFullBuild(oldClasspath, newClasspath, true, oldOutputLocs, alreadyAnalysedPaths)) { |
| return true; |
| } |
| |
| List<File> oldAspectpath = previousConfig.getAspectpath(); |
| List<File> newAspectpath = newConfig.getAspectpath(); |
| if (changedAndNeedsFullBuild(oldAspectpath, newAspectpath, true, oldOutputLocs, alreadyAnalysedPaths, PATHID_ASPECTPATH)) { |
| return true; |
| } |
| |
| List<File> oldInPath = previousConfig.getInpath(); |
| List<File> newInPath = newConfig.getInpath(); |
| if (changedAndNeedsFullBuild(oldInPath, newInPath, false, oldOutputLocs, alreadyAnalysedPaths, PATHID_INPATH)) { |
| return true; |
| } |
| |
| List<File> oldInJars = previousConfig.getInJars(); |
| List<File> newInJars = newConfig.getInJars(); |
| if (changedAndNeedsFullBuild(oldInJars, newInJars, false, oldOutputLocs, alreadyAnalysedPaths, PATHID_INPATH)) { |
| return true; |
| } |
| } else if (newConfig.getClasspathElementsWithModifiedContents() != null) { |
| // Although the classpath entries themselves are the same as before, the contents of one of the |
| // directories on the classpath has changed - rather than go digging around to find it, let's ask |
| // the compiler configuration. This will allow for projects with long classpaths where classpaths |
| // are also capturing project dependencies - when a project we depend on is rebuilt, we can just check |
| // it as a standalone element on our classpath rather than going through them all |
| List<String> modifiedCpElements = newConfig.getClasspathElementsWithModifiedContents(); |
| for (Iterator<String> iterator = modifiedCpElements.iterator(); iterator.hasNext();) { |
| File cpElement = new File(iterator.next()); |
| if (cpElement.exists() && !cpElement.isDirectory()) { |
| if (cpElement.lastModified() > lastSuccessfulBuildTime) { |
| return true; |
| } |
| } else { |
| int classFileChanges = classFileChangedInDirSinceLastBuildRequiringFullBuild(cpElement, PATHID_CLASSPATH); |
| if (classFileChanges == CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Return a list of the output locations - this includes any 'default' output location and then any known by a registered |
| * CompilationResultDestinationManager. |
| * |
| * @param config the build configuration for which the output locations should be determined |
| * @return a list of file objects |
| */ |
| private List<File> getOutputLocations(AjBuildConfig config) { |
| List<File> outputLocs = new ArrayList<File>(); |
| // Is there a default location? |
| if (config.getOutputDir() != null) { |
| try { |
| outputLocs.add(config.getOutputDir().getCanonicalFile()); |
| } catch (IOException e) { |
| } |
| } |
| if (config.getCompilationResultDestinationManager() != null) { |
| List<File> dirs = config.getCompilationResultDestinationManager().getAllOutputLocations(); |
| for (Iterator<File> iterator = dirs.iterator(); iterator.hasNext();) { |
| File f = iterator.next(); |
| try { |
| File cf = f.getCanonicalFile(); |
| if (!outputLocs.contains(cf)) { |
| outputLocs.add(cf); |
| } |
| } catch (IOException e) { |
| } |
| } |
| } |
| return outputLocs; |
| } |
| |
| private File getOutputLocationFor(AjBuildConfig config, File aResourceFile) { |
| if (config.getCompilationResultDestinationManager() != null) { |
| File outputLoc = config.getCompilationResultDestinationManager().getOutputLocationForResource(aResourceFile); |
| if (outputLoc != null) { |
| return outputLoc; |
| } |
| } |
| // Is there a default location? |
| if (config.getOutputDir() != null) { |
| return config.getOutputDir(); |
| } |
| return null; |
| } |
| |
| /** |
| * Check the old and new paths, if they vary by length or individual elements then that is considered a change. Or if the last |
| * modified time of a path entry has changed (or last modified time of a classfile in that path entry has changed) then return |
| * true. The outputlocations are supplied so they can be 'ignored' in the comparison. |
| * |
| * @param oldPath |
| * @param newPath |
| * @param checkClassFiles whether to examine individual class files within directories |
| * @param outputLocs the output locations that should be ignored if they occur on the paths being compared |
| * @return true if a change is detected that requires a full build |
| */ |
| private boolean changedAndNeedsFullBuild(List oldPath, List newPath, boolean checkClassFiles, List<File> outputLocs, |
| Set<String> alreadyAnalysedPaths, int pathid) { |
| if (oldPath.size() != newPath.size()) { |
| return true; |
| } |
| for (int i = 0; i < oldPath.size(); i++) { |
| if (!oldPath.get(i).equals(newPath.get(i))) { |
| return true; |
| } |
| Object o = oldPath.get(i); // String on classpath, File on other paths |
| File f = null; |
| if (o instanceof String) { |
| f = new File((String) o); |
| } else { |
| f = (File) o; |
| } |
| if (f.exists() && !f.isDirectory() && (f.lastModified() >= lastSuccessfulBuildTime)) { |
| return true; |
| } |
| if (checkClassFiles && f.exists() && f.isDirectory()) { |
| |
| // We should use here a list/set of directories we know have or have not changed - some kind of |
| // List<File> buildConfig.getClasspathEntriesWithChangedContents() |
| // and then only proceed to look inside directories if it is one of these, ignoring others - |
| // that should save a massive amount of processing for incremental builds in a multi project scenario |
| |
| boolean foundMatch = false; |
| for (Iterator<File> iterator = outputLocs.iterator(); !foundMatch && iterator.hasNext();) { |
| File dir = iterator.next(); |
| if (f.equals(dir)) { |
| foundMatch = true; |
| } |
| } |
| if (!foundMatch) { |
| if (!alreadyAnalysedPaths.contains(f.getAbsolutePath())) { // Do not check paths more than once |
| alreadyAnalysedPaths.add(f.getAbsolutePath()); |
| int classFileChanges = classFileChangedInDirSinceLastBuildRequiringFullBuild(f, pathid); |
| if (classFileChanges == CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check the old and new paths, if they vary by length or individual elements then that is considered a change. Or if the last |
| * modified time of a path entry has changed (or last modified time of a classfile in that path entry has changed) then return |
| * true. The outputlocations are supplied so they can be 'ignored' in the comparison. |
| * |
| * @param oldPath |
| * @param newPath |
| * @param checkClassFiles whether to examine individual class files within directories |
| * @param outputLocs the output locations that should be ignored if they occur on the paths being compared |
| * @return true if a change is detected that requires a full build |
| */ |
| private boolean classpathChangedAndNeedsFullBuild(List<String> oldPath, List<String> newPath, boolean checkClassFiles, |
| List<File> outputLocs, Set<String> alreadyAnalysedPaths) { |
| if (oldPath.size() != newPath.size()) { |
| return true; |
| } |
| for (int i = 0; i < oldPath.size(); i++) { |
| if (!oldPath.get(i).equals(newPath.get(i))) { |
| return true; |
| } |
| File f = new File(oldPath.get(i)); |
| if (f.exists() && !f.isDirectory() && (f.lastModified() >= lastSuccessfulBuildTime)) { |
| return true; |
| } |
| if (checkClassFiles && f.exists() && f.isDirectory()) { |
| |
| // We should use here a list/set of directories we know have or have not changed - some kind of |
| // List<File> buildConfig.getClasspathEntriesWithChangedContents() |
| // and then only proceed to look inside directories if it is one of these, ignoring others - |
| // that should save a massive amount of processing for incremental builds in a multi project scenario |
| |
| boolean foundMatch = false; |
| for (Iterator<File> iterator = outputLocs.iterator(); !foundMatch && iterator.hasNext();) { |
| File dir = iterator.next(); |
| if (f.equals(dir)) { |
| foundMatch = true; |
| } |
| } |
| if (!foundMatch) { |
| if (!alreadyAnalysedPaths.contains(f.getAbsolutePath())) { // Do not check paths more than once |
| alreadyAnalysedPaths.add(f.getAbsolutePath()); |
| int classFileChanges = classFileChangedInDirSinceLastBuildRequiringFullBuild(f, PATHID_CLASSPATH); |
| if (classFileChanges == CLASS_FILE_CHANGED_THAT_NEEDS_FULL_BUILD) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| public Set<File> getFilesToCompile(boolean firstPass) { |
| Set<File> thisTime = new HashSet<File>(); |
| if (firstPass) { |
| compiledSourceFiles = new HashSet<File>(); |
| Collection<File> modifiedFiles = getModifiedFiles(); |
| // System.out.println("modified: " + modifiedFiles); |
| thisTime.addAll(modifiedFiles); |
| // ??? eclipse IncrementalImageBuilder appears to do this |
| // for (Iterator i = modifiedFiles.iterator(); i.hasNext();) { |
| // File file = (File) i.next(); |
| // addDependentsOf(file); |
| // } |
| |
| if (addedFiles != null) { |
| for (Iterator<File> fIter = addedFiles.iterator(); fIter.hasNext();) { |
| File o = fIter.next(); |
| // TODO isn't it a set?? why do this |
| if (!thisTime.contains(o)) { |
| thisTime.add(o); |
| } |
| } |
| // thisTime.addAll(addedFiles); |
| } |
| |
| deleteClassFiles(); |
| // Do not delete resources on incremental build, AJDT will handle |
| // copying updates to the output folder. AspectJ only does a copy |
| // of them on full build (see copyResourcesToDestination() call |
| // in AjBuildManager) |
| // deleteResources(); |
| |
| addAffectedSourceFiles(thisTime, thisTime); |
| } else { |
| addAffectedSourceFiles(thisTime, compiledSourceFiles); |
| } |
| compiledSourceFiles = thisTime; |
| return thisTime; |
| } |
| |
| private boolean maybeIncremental() { |
| return (FORCE_INCREMENTAL_DURING_TESTING || this.couldBeSubsequentIncrementalBuild); |
| } |
| |
| public Map<String, List<UnwovenClassFile>> getBinaryFilesToCompile(boolean firstTime) { |
| if (lastSuccessfulBuildTime == -1 || buildConfig == null || !maybeIncremental()) { |
| return binarySourceFiles; |
| } |
| // else incremental... |
| Map<String, List<UnwovenClassFile>> toWeave = new HashMap<String, List<UnwovenClassFile>>(); |
| if (firstTime) { |
| List<BinarySourceFile> addedOrModified = new ArrayList<BinarySourceFile>(); |
| addedOrModified.addAll(addedBinaryFiles); |
| addedOrModified.addAll(getModifiedBinaryFiles()); |
| for (Iterator<BinarySourceFile> iter = addedOrModified.iterator(); iter.hasNext();) { |
| AjBuildConfig.BinarySourceFile bsf = iter.next(); |
| UnwovenClassFile ucf = createUnwovenClassFile(bsf); |
| if (ucf == null) { |
| continue; |
| } |
| List<UnwovenClassFile> ucfs = new ArrayList<UnwovenClassFile>(); |
| ucfs.add(ucf); |
| recordTypeChanged(ucf.getClassName()); |
| binarySourceFiles.put(bsf.binSrc.getPath(), ucfs); |
| List<ClassFile> cfs = new ArrayList<ClassFile>(1); |
| cfs.add(getClassFileFor(ucf)); |
| this.inputClassFilesBySource.put(bsf.binSrc.getPath(), cfs); |
| toWeave.put(bsf.binSrc.getPath(), ucfs); |
| } |
| deleteBinaryClassFiles(); |
| } else { |
| // return empty set... we've already done our bit. |
| } |
| return toWeave; |
| } |
| |
| /** |
| * Called when a path change is about to trigger a full build, but we haven't cleaned up from the last incremental build... |
| */ |
| private void removeAllResultsOfLastBuild() { |
| // remove all binarySourceFiles, and all classesFromName... |
| for (Iterator<List<ClassFile>> iter = this.inputClassFilesBySource.values().iterator(); iter.hasNext();) { |
| List<ClassFile> cfs = iter.next(); |
| for (ClassFile cf : cfs) { |
| cf.deleteFromFileSystem(buildConfig); |
| } |
| } |
| for (Iterator<File> iterator = classesFromName.values().iterator(); iterator.hasNext();) { |
| File f = iterator.next(); |
| new ClassFile("", f).deleteFromFileSystem(buildConfig); |
| } |
| Set<Map.Entry<String, File>> resourceEntries = resources.entrySet(); |
| for (Iterator<Map.Entry<String, File>> iter = resourceEntries.iterator(); iter.hasNext();) { |
| Map.Entry<String, File> resourcePair = iter.next(); |
| File sourcePath = resourcePair.getValue(); |
| File outputLoc = getOutputLocationFor(buildConfig, sourcePath); |
| if (outputLoc != null) { |
| outputLoc = new File(outputLoc, resourcePair.getKey()); |
| if (!outputLoc.getPath().equals(sourcePath.getPath()) && outputLoc.exists()) { |
| outputLoc.delete(); |
| if (buildConfig.getCompilationResultDestinationManager() != null) { |
| buildConfig.getCompilationResultDestinationManager().reportFileRemove(outputLoc.getPath(), |
| CompilationResultDestinationManager.FILETYPE_RESOURCE); |
| } |
| } |
| } |
| } |
| } |
| |
| private void deleteClassFiles() { |
| if (deletedFiles == null) { |
| return; |
| } |
| for (File deletedFile : deletedFiles) { |
| addDependentsOf(deletedFile); |
| |
| List<ClassFile> cfs = this.fullyQualifiedTypeNamesResultingFromCompilationUnit.get(deletedFile); |
| this.fullyQualifiedTypeNamesResultingFromCompilationUnit.remove(deletedFile); |
| |
| if (cfs != null) { |
| for (ClassFile cf : cfs) { |
| deleteClassFile(cf); |
| } |
| } |
| |
| } |
| } |
| |
| private void deleteBinaryClassFiles() { |
| // range of bsf is ucfs, domain is files (.class and jars) in inpath/jars |
| for (BinarySourceFile deletedFile : deletedBinaryFiles) { |
| List<ClassFile> cfs = this.inputClassFilesBySource.get(deletedFile.binSrc.getPath()); |
| for (Iterator<ClassFile> iterator = cfs.iterator(); iterator.hasNext();) { |
| deleteClassFile(iterator.next()); |
| } |
| this.inputClassFilesBySource.remove(deletedFile.binSrc.getPath()); |
| } |
| } |
| |
| // private void deleteResources() { |
| // List oldResources = new ArrayList(); |
| // oldResources.addAll(resources); |
| // |
| // // note - this deliberately ignores resources in jars as we don't yet handle jar changes |
| // // with incremental compilation |
| // for (Iterator i = buildConfig.getInpath().iterator(); i.hasNext();) { |
| // File inPathElement = (File) i.next(); |
| // if (inPathElement.isDirectory() && AjBuildManager.COPY_INPATH_DIR_RESOURCES) { |
| // deleteResourcesFromDirectory(inPathElement, oldResources); |
| // } |
| // } |
| // |
| // if (buildConfig.getSourcePathResources() != null) { |
| // for (Iterator i = buildConfig.getSourcePathResources().keySet().iterator(); i.hasNext();) { |
| // String resource = (String) i.next(); |
| // maybeDeleteResource(resource, oldResources); |
| // } |
| // } |
| // |
| // // oldResources need to be deleted... |
| // for (Iterator iter = oldResources.iterator(); iter.hasNext();) { |
| // String victim = (String) iter.next(); |
| // List outputDirs = getOutputLocations(buildConfig); |
| // for (Iterator iterator = outputDirs.iterator(); iterator.hasNext();) { |
| // File dir = (File) iterator.next(); |
| // File f = new File(dir, victim); |
| // if (f.exists()) { |
| // f.delete(); |
| // } |
| // resources.remove(victim); |
| // } |
| // } |
| // } |
| |
| // private void maybeDeleteResource(String resName, List oldResources) { |
| // if (resources.contains(resName)) { |
| // oldResources.remove(resName); |
| // List outputDirs = getOutputLocations(buildConfig); |
| // for (Iterator iterator = outputDirs.iterator(); iterator.hasNext();) { |
| // File dir = (File) iterator.next(); |
| // File source = new File(dir, resName); |
| // if (source.exists() && (source.lastModified() >= lastSuccessfulBuildTime)) { |
| // resources.remove(resName); // will ensure it is re-copied |
| // } |
| // } |
| // } |
| // } |
| |
| // private void deleteResourcesFromDirectory(File dir, List oldResources) { |
| // File[] files = FileUtil.listFiles(dir, new FileFilter() { |
| // public boolean accept(File f) { |
| // boolean accept = !(f.isDirectory() || f.getName().endsWith(".class")); |
| // return accept; |
| // } |
| // }); |
| // |
| // // For each file, add it either as a real .class file or as a resource |
| // for (int i = 0; i < files.length; i++) { |
| // // ASSERT: files[i].getAbsolutePath().startsWith(inFile.getAbsolutePath() |
| // // or we are in trouble... |
| // String filename = null; |
| // try { |
| // filename = files[i].getCanonicalPath().substring(dir.getCanonicalPath().length() + 1); |
| // } catch (IOException e) { |
| // // we are in trouble if this happens... |
| // IMessage msg = new Message("call to getCanonicalPath() failed for file " + files[i] + " with: " + e.getMessage(), |
| // new SourceLocation(files[i], 0), false); |
| // buildManager.handler.handleMessage(msg); |
| // filename = files[i].getAbsolutePath().substring(dir.getAbsolutePath().length() + 1); |
| // } |
| // |
| // maybeDeleteResource(filename, oldResources); |
| // } |
| // } |
| |
| private void deleteClassFile(ClassFile cf) { |
| classesFromName.remove(cf.fullyQualifiedTypeName); |
| weaver.deleteClassFile(cf.fullyQualifiedTypeName); |
| cf.deleteFromFileSystem(buildConfig); |
| } |
| |
| private UnwovenClassFile createUnwovenClassFile(AjBuildConfig.BinarySourceFile bsf) { |
| UnwovenClassFile ucf = null; |
| try { |
| File outputDir = buildConfig.getOutputDir(); |
| if (buildConfig.getCompilationResultDestinationManager() != null) { |
| // createUnwovenClassFile is called only for classes that are on the inpath, |
| // all inpath classes are put in the defaultOutputLocation, therefore, |
| // this is the output dir |
| outputDir = buildConfig.getCompilationResultDestinationManager().getDefaultOutputLocation(); |
| } |
| ucf = weaver.addClassFile(bsf.binSrc, bsf.fromInPathDirectory, outputDir); |
| } catch (IOException ex) { |
| IMessage msg = new Message("can't read class file " + bsf.binSrc.getPath(), new SourceLocation(bsf.binSrc, 0), false); |
| buildManager.handler.handleMessage(msg); |
| } |
| return ucf; |
| } |
| |
| public void noteResult(InterimCompilationResult result) { |
| if (!maybeIncremental()) { |
| return; |
| } |
| |
| File sourceFile = new File(result.fileName()); |
| CompilationResult cr = result.result(); |
| |
| references.put(sourceFile, new ReferenceCollection(cr.qualifiedReferences, cr.simpleNameReferences,cr.rootReferences)); |
| |
| UnwovenClassFile[] unwovenClassFiles = result.unwovenClassFiles(); |
| for (int i = 0; i < unwovenClassFiles.length; i++) { |
| File lastTimeRound = classesFromName.get(unwovenClassFiles[i].getClassName()); |
| recordClassFile(unwovenClassFiles[i], lastTimeRound); |
| String name = unwovenClassFiles[i].getClassName(); |
| if (lastTimeRound == null) { |
| deltaAddedClasses.add(name); |
| } |
| classesFromName.put(name, new File(unwovenClassFiles[i].getFilename())); |
| } |
| |
| // need to do this before types are deleted from the World... |
| recordWhetherCompilationUnitDefinedAspect(sourceFile, cr); |
| deleteTypesThatWereInThisCompilationUnitLastTimeRoundButHaveBeenDeletedInThisIncrement(sourceFile, unwovenClassFiles); |
| |
| recordFQNsResultingFromCompilationUnit(sourceFile, result); |
| } |
| |
| public void noteNewResult(CompilationResult cr) { |
| // if (!maybeIncremental()) { |
| // return; |
| // } |
| // |
| // // File sourceFile = new File(result.fileName()); |
| // // CompilationResult cr = result.result(); |
| // if (new String(cr.getFileName()).indexOf("C") != -1) { |
| // cr.references.put(new String(cr.getFileName()), |
| // new ReferenceCollection(cr.qualifiedReferences, cr.simpleNameReferences)); |
| // int stop = 1; |
| // } |
| |
| // references.put(sourceFile, new ReferenceCollection(cr.qualifiedReferences, cr.simpleNameReferences)); |
| // |
| // UnwovenClassFile[] unwovenClassFiles = cr.unwovenClassFiles(); |
| // for (int i = 0; i < unwovenClassFiles.length; i++) { |
| // File lastTimeRound = (File) classesFromName.get(unwovenClassFiles[i].getClassName()); |
| // recordClassFile(unwovenClassFiles[i], lastTimeRound); |
| // classesFromName.put(unwovenClassFiles[i].getClassName(), new File(unwovenClassFiles[i].getFilename())); |
| // } |
| |
| // need to do this before types are deleted from the World... |
| // recordWhetherCompilationUnitDefinedAspect(sourceFile, cr); |
| // deleteTypesThatWereInThisCompilationUnitLastTimeRoundButHaveBeenDeletedInThisIncrement(sourceFile, unwovenClassFiles); |
| // |
| // recordFQNsResultingFromCompilationUnit(sourceFile, result); |
| } |
| |
| /** |
| * @param sourceFile |
| * @param unwovenClassFiles |
| */ |
| private void deleteTypesThatWereInThisCompilationUnitLastTimeRoundButHaveBeenDeletedInThisIncrement(File sourceFile, |
| UnwovenClassFile[] unwovenClassFiles) { |
| List<ClassFile> classFiles = this.fullyQualifiedTypeNamesResultingFromCompilationUnit.get(sourceFile); |
| if (classFiles != null) { |
| |
| for (int i = 0; i < unwovenClassFiles.length; i++) { |
| // deleting also deletes types from the weaver... don't do this if they are |
| // still present this time around... |
| removeFromClassFilesIfPresent(unwovenClassFiles[i].getClassName(), classFiles); |
| } |
| for (ClassFile cf : classFiles) { |
| recordTypeChanged(cf.fullyQualifiedTypeName); |
| resolvedTypeStructuresFromLastBuild.remove(cf.fullyQualifiedTypeName); |
| // } |
| // for (ClassFile cf : classFiles) { |
| deleteClassFile(cf); |
| } |
| } |
| } |
| |
| private void removeFromClassFilesIfPresent(String className, List<ClassFile> classFiles) { |
| ClassFile victim = null; |
| for (ClassFile cf : classFiles) { |
| if (cf.fullyQualifiedTypeName.equals(className)) { |
| victim = cf; |
| break; |
| } |
| } |
| if (victim != null) { |
| classFiles.remove(victim); |
| } |
| } |
| |
| /** |
| * Record the fully-qualified names of the types that were declared in the given source file. |
| * |
| * @param sourceFile, the compilation unit |
| * @param icr, the CompilationResult from compiling it |
| */ |
| private void recordFQNsResultingFromCompilationUnit(File sourceFile, InterimCompilationResult icr) { |
| List<ClassFile> classFiles = new ArrayList<ClassFile>(); |
| UnwovenClassFile[] types = icr.unwovenClassFiles(); |
| for (int i = 0; i < types.length; i++) { |
| classFiles.add(new ClassFile(types[i].getClassName(), new File(types[i].getFilename()))); |
| } |
| this.fullyQualifiedTypeNamesResultingFromCompilationUnit.put(sourceFile, classFiles); |
| } |
| |
| /** |
| * If this compilation unit defined an aspect, we need to know in case it is modified in a future increment. |
| * |
| * @param sourceFile |
| * @param cr |
| */ |
| private void recordWhetherCompilationUnitDefinedAspect(File sourceFile, CompilationResult cr) { |
| this.sourceFilesDefiningAspects.remove(sourceFile); |
| |
| if (cr != null) { |
| Map compiledTypes = cr.compiledTypes; |
| if (compiledTypes != null) { |
| for (Iterator<char[]> iterator = compiledTypes.keySet().iterator(); iterator.hasNext();) { |
| char[] className = iterator.next(); |
| String typeName = new String(className).replace('/', '.'); |
| if (typeName.indexOf(BcelWeaver.SYNTHETIC_CLASS_POSTFIX) == -1) { |
| ResolvedType rt = world.resolve(typeName); |
| if (rt.isMissing()) { |
| // This can happen in a case where another problem has occurred that prevented it being |
| // correctly added to the world. Eg. pr148285. Duplicate types |
| // throw new IllegalStateException("Type '" + rt.getSignature() + "' not found in world!"); |
| } else if (rt.isAspect()) { |
| this.sourceFilesDefiningAspects.add(sourceFile); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| } |
| |
| // private UnwovenClassFile removeFromPreviousIfPresent(UnwovenClassFile cf, InterimCompilationResult previous) { |
| // if (previous == null) |
| // return null; |
| // UnwovenClassFile[] unwovenClassFiles = previous.unwovenClassFiles(); |
| // for (int i = 0; i < unwovenClassFiles.length; i++) { |
| // UnwovenClassFile candidate = unwovenClassFiles[i]; |
| // if ((candidate != null) && candidate.getFilename().equals(cf.getFilename())) { |
| // unwovenClassFiles[i] = null; |
| // return candidate; |
| // } |
| // } |
| // return null; |
| // } |
| |
| private void recordClassFile(UnwovenClassFile thisTime, File lastTime) { |
| if (simpleStrings == null) { |
| // batch build |
| // record resolved type for structural comparisons in future increments |
| // this records a second reference to a structure already held in memory |
| // by the world. |
| ResolvedType rType = world.resolve(thisTime.getClassName()); |
| if (!rType.isMissing()) { |
| try { |
| ClassFileReader reader = new ClassFileReader(thisTime.getBytes(), null); |
| boolean isAspect = false; |
| if (rType instanceof ReferenceType && ((ReferenceType) rType).getDelegate() != null) { |
| isAspect = ((ReferenceType) rType).isAspect(); |
| } |
| this.resolvedTypeStructuresFromLastBuild.put(thisTime.getClassName(), new CompactTypeStructureRepresentation( |
| reader, isAspect)); |
| } catch (ClassFormatException cfe) { |
| throw new BCException("Unexpected problem processing class", cfe); |
| } |
| } |
| return; |
| } |
| |
| CompactTypeStructureRepresentation existingStructure = this.resolvedTypeStructuresFromLastBuild |
| .get(thisTime.getClassName()); |
| ResolvedType newResolvedType = world.resolve(thisTime.getClassName()); |
| if (!newResolvedType.isMissing()) { |
| try { |
| ClassFileReader reader = new ClassFileReader(thisTime.getBytes(), null); |
| boolean isAspect = false; |
| if (newResolvedType instanceof ReferenceType && ((ReferenceType) newResolvedType).getDelegate() != null) { |
| isAspect = ((ReferenceType) newResolvedType).isAspect(); |
| } |
| this.resolvedTypeStructuresFromLastBuild.put(thisTime.getClassName(), new CompactTypeStructureRepresentation( |
| reader, isAspect)); |
| } catch (ClassFormatException cfe) { |
| try { |
| String s = System.getProperty("aspectj.debug377096","false"); |
| if (s.equalsIgnoreCase("true")) { |
| String location = System.getProperty("java.io.tmpdir","/tmp"); |
| String name = thisTime.getClassName(); |
| File f = File.createTempFile(location+File.separator+name, ".class"); |
| StringBuilder debug = new StringBuilder(); |
| debug.append("Debug377096: Dumping class called "+name+" to "+f.getName()+" size:"+thisTime.getBytes().length); |
| DataOutputStream dos = new DataOutputStream(new FileOutputStream(f)); |
| dos.write(thisTime.getBytes()); |
| dos.close(); |
| throw new BCException(debug.toString(), cfe); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| throw new BCException("Unexpected problem processing class", cfe); |
| } |
| } |
| |
| if (lastTime == null) { |
| recordTypeChanged(thisTime.getClassName()); |
| return; |
| } |
| |
| if (newResolvedType.isMissing()) { |
| return; |
| } |
| world.ensureAdvancedConfigurationProcessed(); |
| byte[] newBytes = thisTime.getBytes(); |
| try { |
| ClassFileReader reader = new ClassFileReader(newBytes, lastTime.getAbsolutePath().toCharArray()); |
| // ignore local types since they're only visible inside a single method |
| if (!(reader.isLocal() || reader.isAnonymous())) { |
| if (hasStructuralChanges(reader, existingStructure)) { |
| if (listenerDefined()) { |
| // if (world.forDEBUG_structuralChangesCode) { |
| // System.err.println("Detected a structural change in " + thisTime.getFilename()); |
| printStructuralChanges(thisTime.getFilename(),reader, existingStructure); |
| } |
| structuralChangesSinceLastFullBuild.put(thisTime.getFilename(), new Long(currentBuildTime)); |
| recordTypeChanged(new String(reader.getName()).replace('/', '.')); |
| } |
| } |
| } catch (ClassFormatException e) { |
| recordTypeChanged(thisTime.getClassName()); |
| } |
| } |
| |
| /** |
| * Compare the class structure of the new intermediate (unwoven) class with the existingResolvedType of the same class that we |
| * have in the world, looking for any structural differences (and ignoring aj members resulting from weaving....) |
| * |
| * Some notes from Andy... lot of problems here, which I've eventually resolved by building the compactstructure based on a |
| * classfilereader, rather than on a ResolvedType. There are accessors for inner types and funky fields that the compiler |
| * creates to support the language - for non-static inner types it also mangles ctors to be prefixed with an instance of the |
| * surrounding type. |
| * |
| * @param reader |
| * @param existingType |
| * @return |
| */ |
| private boolean hasStructuralChanges(ClassFileReader reader, CompactTypeStructureRepresentation existingType) { |
| if (existingType == null) { |
| return true; |
| } |
| |
| // modifiers |
| if (!modifiersEqual(reader.getModifiers(), existingType.modifiers)) { |
| return true; |
| } |
| |
| // generic signature |
| if (!CharOperation.equals(reader.getGenericSignature(), existingType.genericSignature)) { |
| return true; |
| } |
| |
| // superclass name |
| if (!CharOperation.equals(reader.getSuperclassName(), existingType.superclassName)) { |
| return true; |
| } |
| |
| // have annotations changed on the type? |
| IBinaryAnnotation[] newAnnos = reader.getAnnotations(); |
| if (newAnnos == null || newAnnos.length == 0) { |
| if (existingType.annotations != null && existingType.annotations.length != 0) { |
| return true; |
| } |
| } else { |
| IBinaryAnnotation[] existingAnnos = existingType.annotations; |
| if (existingAnnos == null || existingAnnos.length != newAnnos.length) { |
| return true; |
| } |
| // Does not allow for an order switch |
| // Does not cope with a change in values set on the annotation (hard to create a testcase where this is a problem tho) |
| for (int i = 0; i < newAnnos.length; i++) { |
| if (!CharOperation.equals(newAnnos[i].getTypeName(), existingAnnos[i].getTypeName())) { |
| return true; |
| } |
| } |
| |
| } |
| |
| // interfaces |
| char[][] existingIfs = existingType.interfaces; |
| char[][] newIfsAsChars = reader.getInterfaceNames(); |
| if (newIfsAsChars == null) { |
| newIfsAsChars = EMPTY_CHAR_ARRAY; |
| } // damn I'm lazy... |
| if (existingIfs == null) { |
| existingIfs = EMPTY_CHAR_ARRAY; |
| } |
| if (existingIfs.length != newIfsAsChars.length) { |
| return true; |
| } |
| new_interface_loop: for (int i = 0; i < newIfsAsChars.length; i++) { |
| for (int j = 0; j < existingIfs.length; j++) { |
| if (CharOperation.equals(existingIfs[j], newIfsAsChars[i])) { |
| continue new_interface_loop; |
| } |
| } |
| return true; |
| } |
| |
| // fields |
| // CompactMemberStructureRepresentation[] existingFields = existingType.fields; |
| IBinaryField[] newFields = reader.getFields(); |
| if (newFields == null) { |
| newFields = CompactTypeStructureRepresentation.NoField; |
| } |
| |
| // all redundant for now ... could be an optimization at some point... |
| // remove any ajc$XXX fields from those we compare with |
| // the existing fields - bug 129163 |
| // List nonGenFields = new ArrayList(); |
| // for (int i = 0; i < newFields.length; i++) { |
| // IBinaryField field = newFields[i]; |
| // //if (!CharOperation.prefixEquals(NameMangler.AJC_DOLLAR_PREFIX,field.getName())) { // this would skip ajc$ fields |
| // //if ((field.getModifiers()&0x1000)==0) // 0x1000 => synthetic - this will skip synthetic fields (eg. this$0) |
| // nonGenFields.add(field); |
| // //} |
| // } |
| IBinaryField[] existingFs = existingType.binFields; |
| if (newFields.length != existingFs.length) { |
| return true; |
| } |
| new_field_loop: for (int i = 0; i < newFields.length; i++) { |
| IBinaryField field = newFields[i]; |
| char[] fieldName = field.getName(); |
| for (int j = 0; j < existingFs.length; j++) { |
| if (CharOperation.equals(existingFs[j].getName(), fieldName)) { |
| IBinaryField existing = existingFs[j]; |
| if (!modifiersEqual(field.getModifiers(), existing.getModifiers())) { |
| return true; |
| } |
| if (!CharOperation.equals(existing.getTypeName(), field.getTypeName())) { |
| return true; |
| } |
| |
| char[] existingGSig = existing.getGenericSignature(); |
| char[] fieldGSig = field.getGenericSignature(); |
| if ((existingGSig == null && fieldGSig != null) || (existingGSig != null && fieldGSig == null)) { |
| return true; |
| } |
| if (existingGSig != null) { |
| if (!CharOperation.equals(existingGSig, fieldGSig)) { |
| return true; |
| } |
| } |
| |
| continue new_field_loop; |
| } |
| } |
| return true; |
| } |
| |
| // methods |
| // CompactMemberStructureRepresentation[] existingMethods = existingType.methods; |
| IBinaryMethod[] newMethods = reader.getMethods(); |
| if (newMethods == null) { |
| newMethods = CompactTypeStructureRepresentation.NoMethod; |
| } |
| |
| // all redundant for now ... could be an optimization at some point... |
| |
| // Ctors in a non-static inner type have an 'extra parameter' of the enclosing type. |
| // If skippableDescriptorPrefix gets set here then it is set to the descriptor portion |
| // for this 'extra parameter'. For an inner class of pkg.Foo the skippable descriptor |
| // prefix will be '(Lpkg/Foo;' - so later when comparing <init> methods we know what to |
| // compare. |
| // IF THIS CODE NEEDS TO GET MORE COMPLICATED, I THINK ITS WORTH RIPPING IT ALL OUT AND |
| // CREATING THE STRUCTURAL CHANGES OBJECT BASED ON CLASSREADER OUTPUT RATHER THAN |
| // THE RESOLVEDTYPE - THEN THERE WOULD BE NO NEED TO TREAT SOME METHODS IN A PECULIAR |
| // WAY. |
| // char[] skippableDescriptorPrefix = null; |
| // char[] enclosingTypeName = reader.getEnclosingTypeName(); |
| // boolean isStaticType = Modifier.isStatic(reader.getModifiers()); |
| // if (!isStaticType && enclosingTypeName!=null) { |
| // StringBuffer sb = new StringBuffer(); |
| // sb.append("(L").append(new String(enclosingTypeName)).append(";"); |
| // skippableDescriptorPrefix = sb.toString().toCharArray(); |
| // } |
| // |
| // |
| // // remove the aspectOf, hasAspect, clinit and ajc$XXX methods |
| // // from those we compare with the existing methods - bug 129163 |
| // List nonGenMethods = new ArrayList(); |
| // for (int i = 0; i < newMethods.length; i++) { |
| // IBinaryMethod method = newMethods[i]; |
| // // if ((method.getModifiers() & 0x1000)!=0) continue; // 0x1000 => synthetic - will cause us to skip access$0 - is this |
| // always safe? |
| // char[] methodName = method.getSelector(); |
| // // if (!CharOperation.equals(methodName,NameMangler.METHOD_ASPECTOF) && |
| // // !CharOperation.equals(methodName,NameMangler.METHOD_HASASPECT) && |
| // // !CharOperation.equals(methodName,NameMangler.STATIC_INITIALIZER) && |
| // // !CharOperation.prefixEquals(NameMangler.AJC_DOLLAR_PREFIX,methodName) && |
| // // !CharOperation.prefixEquals(NameMangler.CLINIT,methodName)) { |
| // nonGenMethods.add(method); |
| // // } |
| // } |
| IBinaryMethod[] existingMs = existingType.binMethods; |
| if (newMethods.length != existingMs.length) { |
| return true; |
| } |
| new_method_loop: for (int i = 0; i < newMethods.length; i++) { |
| IBinaryMethod method = newMethods[i]; |
| char[] methodName = method.getSelector(); |
| for (int j = 0; j < existingMs.length; j++) { |
| if (CharOperation.equals(existingMs[j].getSelector(), methodName)) { |
| // candidate match |
| if (!CharOperation.equals(method.getMethodDescriptor(), existingMs[j].getMethodDescriptor())) { |
| // ok, the descriptors don't match, but is this a funky ctor on a non-static inner |
| // type? |
| // boolean mightBeOK = |
| // skippableDescriptorPrefix!=null && // set for inner types |
| // CharOperation.equals(methodName,NameMangler.INIT) && // ctor |
| // CharOperation.prefixEquals(skippableDescriptorPrefix,method.getMethodDescriptor()); // checking for |
| // prefix on the descriptor |
| // if (mightBeOK) { |
| // // OK, so the descriptor starts something like '(Lpkg/Foo;' - we now may need to look at the rest of the |
| // // descriptor if it takes >1 parameter. |
| // // eg. could be (Lpkg/C;Ljava/lang/String;) where the skippablePrefix is (Lpkg/C; |
| // char [] md = method.getMethodDescriptor(); |
| // char[] remainder = CharOperation.subarray(md, skippableDescriptorPrefix.length, md.length); |
| // if (CharOperation.equals(remainder,BRACKET_V)) continue new_method_loop; // no other parameters to worry |
| // about |
| // char[] comparableSig = CharOperation.subarray(existingMethods[j].signature, 1, |
| // existingMethods[j].signature.length); |
| // boolean match = CharOperation.equals(comparableSig, remainder); |
| // if (match) continue new_method_loop; |
| // } |
| continue; // might be overloading |
| } else { |
| // matching sigs |
| IBinaryMethod existing = existingMs[j]; |
| if (!modifiersEqual(method.getModifiers(), existing.getModifiers())) { |
| return true; |
| } |
| |
| if (exceptionClausesDiffer(existing, method)) { |
| return true; |
| } |
| |
| char[] existingGSig = existing.getGenericSignature(); |
| char[] methodGSig = method.getGenericSignature(); |
| if ((existingGSig == null && methodGSig != null) || (existingGSig != null && methodGSig == null)) { |
| return true; |
| } |
| if (existingGSig != null) { |
| if (!CharOperation.equals(existingGSig, methodGSig)) { |
| return true; |
| } |
| } |
| |
| continue new_method_loop; |
| } |
| } |
| } |
| return true; // (no match found) |
| } |
| |
| // check for differences in inner types |
| // TODO could make order insensitive |
| IBinaryNestedType[] binaryNestedTypes = reader.getMemberTypes(); |
| IBinaryNestedType[] existingBinaryNestedTypes = existingType.getMemberTypes(); |
| if ((binaryNestedTypes == null && existingBinaryNestedTypes != null) |
| || (binaryNestedTypes != null && existingBinaryNestedTypes == null)) { |
| return true; |
| } |
| if (binaryNestedTypes != null) { |
| int bnLength = binaryNestedTypes.length; |
| if (existingBinaryNestedTypes.length != bnLength) { |
| return true; |
| } |
| for (int m = 0; m < bnLength; m++) { |
| IBinaryNestedType bnt = binaryNestedTypes[m]; |
| IBinaryNestedType existingBnt = existingBinaryNestedTypes[m]; |
| if (!CharOperation.equals(bnt.getName(), existingBnt.getName())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void logAnalysis(String filename, String info) { |
| if (listenerDefined()) { |
| getListener().recordDecision("StructuralAnalysis["+filename+"]: "+info); |
| } |
| } |
| |
| private boolean printStructuralChanges(String filename, ClassFileReader reader, CompactTypeStructureRepresentation existingType) { |
| logAnalysis(filename,"appears to have structurally changed, printing changes:"); |
| if (existingType == null) { |
| logAnalysis(filename,"have not seen this type before"); |
| return true; |
| } |
| |
| // modifiers |
| if (!modifiersEqual(reader.getModifiers(), existingType.modifiers)) { |
| logAnalysis(filename,"modifiers changed. old=0x"+Integer.toHexString(existingType.getModifiers())+" new=0x"+Integer.toHexString(reader.getModifiers())); |
| return true; |
| } |
| |
| // generic signature |
| if (!CharOperation.equals(reader.getGenericSignature(), existingType.genericSignature)) { |
| logAnalysis(filename,"generic signature changed. old="+stringify(existingType.genericSignature)+" new="+stringify(reader.getGenericSignature())); |
| return true; |
| } |
| |
| // superclass name |
| if (!CharOperation.equals(reader.getSuperclassName(), existingType.superclassName)) { |
| logAnalysis(filename,"superclass name changed. old="+stringify(existingType.superclassName)+" new="+stringify(reader.getSuperclassName())); |
| return true; |
| } |
| |
| // have annotations changed on the type? |
| IBinaryAnnotation[] newAnnos = reader.getAnnotations(); |
| if (newAnnos == null || newAnnos.length == 0) { |
| if (existingType.annotations != null && existingType.annotations.length != 0) { |
| logAnalysis(filename,"type used to have annotations and now does not: "+stringify(existingType.annotations)); |
| return true; |
| } |
| } else { |
| IBinaryAnnotation[] existingAnnos = existingType.annotations; |
| if (existingAnnos == null || existingAnnos.length != newAnnos.length) { |
| logAnalysis(filename,"type now has annotations which it did not used to have: "+stringify(newAnnos)); |
| return true; |
| } |
| // Does not allow for an order switch |
| // Does not cope with a change in values set on the annotation (hard to create a testcase where this is a problem tho) |
| for (int i = 0; i < newAnnos.length; i++) { |
| if (!CharOperation.equals(newAnnos[i].getTypeName(), existingAnnos[i].getTypeName())) { |
| logAnalysis(filename,"type annotation change at position "+i+" old="+new String(existingAnnos[i].getTypeName())+" new="+new String(newAnnos[i].getTypeName())); |
| return true; |
| } |
| } |
| |
| } |
| |
| // interfaces |
| char[][] existingIfs = existingType.interfaces; |
| char[][] newIfsAsChars = reader.getInterfaceNames(); |
| if (newIfsAsChars == null) { |
| newIfsAsChars = EMPTY_CHAR_ARRAY; |
| } // damn I'm lazy... |
| if (existingIfs == null) { |
| existingIfs = EMPTY_CHAR_ARRAY; |
| } |
| if (existingIfs.length != newIfsAsChars.length) { |
| return true; |
| } |
| new_interface_loop: for (int i = 0; i < newIfsAsChars.length; i++) { |
| for (int j = 0; j < existingIfs.length; j++) { |
| if (CharOperation.equals(existingIfs[j], newIfsAsChars[i])) { |
| continue new_interface_loop; |
| } |
| } |
| logAnalysis(filename,"set of interfaces changed. old="+stringify(existingIfs)+" new="+stringify(newIfsAsChars)); |
| return true; |
| } |
| |
| // fields |
| // CompactMemberStructureRepresentation[] existingFields = existingType.fields; |
| IBinaryField[] newFields = reader.getFields(); |
| if (newFields == null) { |
| newFields = CompactTypeStructureRepresentation.NoField; |
| } |
| |
| // all redundant for now ... could be an optimization at some point... |
| // remove any ajc$XXX fields from those we compare with |
| // the existing fields - bug 129163 |
| // List nonGenFields = new ArrayList(); |
| // for (int i = 0; i < newFields.length; i++) { |
| // IBinaryField field = newFields[i]; |
| // //if (!CharOperation.prefixEquals(NameMangler.AJC_DOLLAR_PREFIX,field.getName())) { // this would skip ajc$ fields |
| // //if ((field.getModifiers()&0x1000)==0) // 0x1000 => synthetic - this will skip synthetic fields (eg. this$0) |
| // nonGenFields.add(field); |
| // //} |
| // } |
| IBinaryField[] existingFs = existingType.binFields; |
| if (newFields.length != existingFs.length) { |
| logAnalysis(filename,"number of fields changed. old="+stringify(existingFs)+" new="+stringify(newFields)); |
| return true; |
| } |
| new_field_loop: for (int i = 0; i < newFields.length; i++) { |
| IBinaryField field = newFields[i]; |
| char[] fieldName = field.getName(); |
| for (int j = 0; j < existingFs.length; j++) { |
| if (CharOperation.equals(existingFs[j].getName(), fieldName)) { |
| IBinaryField existing = existingFs[j]; |
| if (!modifiersEqual(field.getModifiers(), existing.getModifiers())) { |
| logAnalysis(filename,"field modifiers changed '"+existing+"' old=0x"+Integer.toHexString(existing.getModifiers())+" new=0x"+Integer.toHexString(field.getModifiers())); |
| return true; |
| } |
| if (!CharOperation.equals(existing.getTypeName(), field.getTypeName())) { |
| logAnalysis(filename,"field type changed '"+existing+"' old="+new String(existing.getTypeName())+" new="+new String(field.getTypeName())); |
| return true; |
| } |
| |
| char[] existingGSig = existing.getGenericSignature(); |
| char[] fieldGSig = field.getGenericSignature(); |
| if ((existingGSig == null && fieldGSig != null) || (existingGSig != null && fieldGSig == null)) { |
| logAnalysis(filename,"field generic sig changed '"+existing+"' old="+ |
| (existingGSig==null?"null":new String(existingGSig))+" new="+(fieldGSig==null?"null":new String(fieldGSig))); |
| return true; |
| } |
| if (existingGSig != null) { |
| if (!CharOperation.equals(existingGSig, fieldGSig)) { |
| logAnalysis(filename,"field generic sig changed '"+existing+"' old="+ |
| (existingGSig==null?"null":new String(existingGSig))+" new="+(fieldGSig==null?"null":new String(fieldGSig))); |
| return true; |
| } |
| } |
| |
| continue new_field_loop; |
| } |
| } |
| logAnalysis(filename,"field changed. New field detected '"+field+"'"); |
| return true; |
| } |
| |
| // methods |
| // CompactMemberStructureRepresentation[] existingMethods = existingType.methods; |
| IBinaryMethod[] newMethods = reader.getMethods(); |
| if (newMethods == null) { |
| newMethods = CompactTypeStructureRepresentation.NoMethod; |
| } |
| |
| // all redundant for now ... could be an optimization at some point... |
| |
| // Ctors in a non-static inner type have an 'extra parameter' of the enclosing type. |
| // If skippableDescriptorPrefix gets set here then it is set to the descriptor portion |
| // for this 'extra parameter'. For an inner class of pkg.Foo the skippable descriptor |
| // prefix will be '(Lpkg/Foo;' - so later when comparing <init> methods we know what to |
| // compare. |
| // IF THIS CODE NEEDS TO GET MORE COMPLICATED, I THINK ITS WORTH RIPPING IT ALL OUT AND |
| // CREATING THE STRUCTURAL CHANGES OBJECT BASED ON CLASSREADER OUTPUT RATHER THAN |
| // THE RESOLVEDTYPE - THEN THERE WOULD BE NO NEED TO TREAT SOME METHODS IN A PECULIAR |
| // WAY. |
| // char[] skippableDescriptorPrefix = null; |
| // char[] enclosingTypeName = reader.getEnclosingTypeName(); |
| // boolean isStaticType = Modifier.isStatic(reader.getModifiers()); |
| // if (!isStaticType && enclosingTypeName!=null) { |
| // StringBuffer sb = new StringBuffer(); |
| // sb.append("(L").append(new String(enclosingTypeName)).append(";"); |
| // skippableDescriptorPrefix = sb.toString().toCharArray(); |
| // } |
| // |
| // |
| // // remove the aspectOf, hasAspect, clinit and ajc$XXX methods |
| // // from those we compare with the existing methods - bug 129163 |
| // List nonGenMethods = new ArrayList(); |
| // for (int i = 0; i < newMethods.length; i++) { |
| // IBinaryMethod method = newMethods[i]; |
| // // if ((method.getModifiers() & 0x1000)!=0) continue; // 0x1000 => synthetic - will cause us to skip access$0 - is this |
| // always safe? |
| // char[] methodName = method.getSelector(); |
| // // if (!CharOperation.equals(methodName,NameMangler.METHOD_ASPECTOF) && |
| // // !CharOperation.equals(methodName,NameMangler.METHOD_HASASPECT) && |
| // // !CharOperation.equals(methodName,NameMangler.STATIC_INITIALIZER) && |
| // // !CharOperation.prefixEquals(NameMangler.AJC_DOLLAR_PREFIX,methodName) && |
| // // !CharOperation.prefixEquals(NameMangler.CLINIT,methodName)) { |
| // nonGenMethods.add(method); |
| // // } |
| // } |
| IBinaryMethod[] existingMs = existingType.binMethods; |
| if (newMethods.length != existingMs.length) { |
| logAnalysis(filename,"number of methods changed. old="+stringify(existingMs)+" new="+stringify(newMethods)); |
| return true; |
| } |
| new_method_loop: for (int i = 0; i < newMethods.length; i++) { |
| IBinaryMethod method = newMethods[i]; |
| char[] methodName = method.getSelector(); |
| for (int j = 0; j < existingMs.length; j++) { |
| if (CharOperation.equals(existingMs[j].getSelector(), methodName)) { |
| // candidate match |
| if (!CharOperation.equals(method.getMethodDescriptor(), existingMs[j].getMethodDescriptor())) { |
| // ok, the descriptors don't match, but is this a funky ctor on a non-static inner |
| // type? |
| // boolean mightBeOK = |
| // skippableDescriptorPrefix!=null && // set for inner types |
| // CharOperation.equals(methodName,NameMangler.INIT) && // ctor |
| // CharOperation.prefixEquals(skippableDescriptorPrefix,method.getMethodDescriptor()); // checking for |
| // prefix on the descriptor |
| // if (mightBeOK) { |
| // // OK, so the descriptor starts something like '(Lpkg/Foo;' - we now may need to look at the rest of the |
| // // descriptor if it takes >1 parameter. |
| // // eg. could be (Lpkg/C;Ljava/lang/String;) where the skippablePrefix is (Lpkg/C; |
| // char [] md = method.getMethodDescriptor(); |
| // char[] remainder = CharOperation.subarray(md, skippableDescriptorPrefix.length, md.length); |
| // if (CharOperation.equals(remainder,BRACKET_V)) continue new_method_loop; // no other parameters to worry |
| // about |
| // char[] comparableSig = CharOperation.subarray(existingMethods[j].signature, 1, |
| // existingMethods[j].signature.length); |
| // boolean match = CharOperation.equals(comparableSig, remainder); |
| // if (match) continue new_method_loop; |
| // } |
| continue; // might be overloading |
| } else { |
| // matching sigs |
| IBinaryMethod existing = existingMs[j]; |
| if (!modifiersEqual(method.getModifiers(), existing.getModifiers())) { |
| logAnalysis(filename,"method modifiers changed '"+existing+"' old=0x"+Integer.toHexString(existing.getModifiers())+" new=0x"+Integer.toHexString(method.getModifiers())); |
| return true; |
| } |
| |
| if (exceptionClausesDiffer(existing, method)) { |
| logAnalysis(filename,"method exception clauses changed '"+existing+"' old="+existing+" new="+method); |
| return true; |
| } |
| |
| char[] existingGSig = existing.getGenericSignature(); |
| char[] methodGSig = method.getGenericSignature(); |
| if ((existingGSig == null && methodGSig != null) || (existingGSig != null && methodGSig == null)) { |
| logAnalysis(filename,"method generic sig changed '"+existing+"' old="+ |
| (existingGSig==null?"null":new String(existingGSig))+" new="+(methodGSig==null?"null":new String(methodGSig))); |
| return true; |
| } |
| if (existingGSig != null) { |
| if (!CharOperation.equals(existingGSig, methodGSig)) { |
| logAnalysis(filename,"method generic sig changed '"+existing+"' old="+ |
| (existingGSig==null?"null":new String(existingGSig))+" new="+(methodGSig==null?"null":new String(methodGSig))); |
| return true; |
| } |
| } |
| |
| continue new_method_loop; |
| } |
| } |
| // TODO missing a return true here? Meaning we have a field in the new that we can't find in the old! |
| } |
| |
| logAnalysis(filename,"method changed. New method detected '"+stringify(method)+"' (might be a rename)"); |
| return true; // (no match found) |
| } |
| |
| // check for differences in inner types |
| // TODO could make order insensitive |
| IBinaryNestedType[] binaryNestedTypes = reader.getMemberTypes(); |
| IBinaryNestedType[] existingBinaryNestedTypes = existingType.getMemberTypes(); |
| if ((binaryNestedTypes == null && existingBinaryNestedTypes != null) |
| || (binaryNestedTypes != null && existingBinaryNestedTypes == null)) { |
| logAnalysis(filename,"nested types changed"); |
| return true; |
| } |
| if (binaryNestedTypes != null) { |
| int bnLength = binaryNestedTypes.length; |
| if (existingBinaryNestedTypes.length != bnLength) { |
| logAnalysis(filename,"nested types changed. old="+stringify(existingBinaryNestedTypes)+" new="+stringify(binaryNestedTypes)); |
| return true; |
| } |
| for (int m = 0; m < bnLength; m++) { |
| IBinaryNestedType bnt = binaryNestedTypes[m]; |
| IBinaryNestedType existingBnt = existingBinaryNestedTypes[m]; |
| if (!CharOperation.equals(bnt.getName(), existingBnt.getName())) { |
| logAnalysis(filename,"nested type changed name at position "+m+" old="+stringify(existingBinaryNestedTypes)+" new="+stringify(binaryNestedTypes)); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private String stringify(char[] chars) { |
| if (chars == null) { |
| return "null"; |
| } |
| return new String(chars); |
| } |
| |
| private String stringify(IBinaryNestedType[] binaryNestedTypes) { |
| StringBuilder buf = new StringBuilder(); |
| for (IBinaryNestedType binaryNestedType: binaryNestedTypes) { |
| buf.append(binaryNestedType).append(" "); |
| } |
| return buf.toString().trim(); |
| } |
| |
| private String stringify(IBinaryMethod[] methods) { |
| StringBuilder buf = new StringBuilder(); |
| for (IBinaryMethod method: methods) { |
| buf.append(stringify(method)).append(" "); |
| } |
| return "["+buf.toString().trim()+"]"; |
| } |
| |
| private String stringify(IBinaryMethod m) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("0x").append(Integer.toHexString(m.getModifiers())).append(" "); |
| buf.append(m.getSelector()).append(m.getMethodDescriptor()); |
| // IBinaryAnnotation[] annos = m.getAnnotations(); |
| // TODO include annotations, generic sig, etc |
| return buf.toString().trim(); |
| } |
| |
| private String stringify(IBinaryField[] fields) { |
| StringBuilder buf = new StringBuilder(); |
| for (IBinaryField field: fields) { |
| buf.append(stringify(field)).append(" "); |
| } |
| return "["+buf.toString().trim()+"]"; |
| } |
| |
| private Object stringify(IBinaryField f) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("0x").append(Integer.toHexString(f.getModifiers())).append(" "); |
| buf.append(f.getTypeName()).append(f.getName()); |
| return buf.toString().trim(); |
| } |
| |
| private String stringify(char[][] arrayOfCharArrays) { |
| StringBuilder buf = new StringBuilder(); |
| for (char[] charArray: arrayOfCharArrays) { |
| buf.append(charArray).append(" "); |
| } |
| return buf.toString().trim(); |
| } |
| |
| private String stringify(IBinaryAnnotation[] annotations) { |
| StringBuilder buf = new StringBuilder(); |
| for (IBinaryAnnotation anno: annotations) { |
| buf.append(anno).append(" "); |
| } |
| return buf.toString().trim(); |
| } |
| |
| /** |
| * For two methods, discover if there has been a change in the exception types specified. |
| * |
| * @return true if the exception types have changed |
| */ |
| private boolean exceptionClausesDiffer(IBinaryMethod lastMethod, IBinaryMethod newMethod) { |
| char[][] previousExceptionTypeNames = lastMethod.getExceptionTypeNames(); |
| char[][] newExceptionTypeNames = newMethod.getExceptionTypeNames(); |
| int pLength = previousExceptionTypeNames.length; |
| int nLength = newExceptionTypeNames.length; |
| if (pLength != nLength) { |
| return true; |
| } |
| if (pLength == 0) { |
| return false; |
| } |
| // TODO could be insensitive to an order change |
| for (int i = 0; i < pLength; i++) { |
| if (!CharOperation.equals(previousExceptionTypeNames[i], newExceptionTypeNames[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean modifiersEqual(int eclipseModifiers, int resolvedTypeModifiers) { |
| resolvedTypeModifiers = resolvedTypeModifiers & ExtraCompilerModifiers.AccJustFlag; |
| eclipseModifiers = eclipseModifiers & ExtraCompilerModifiers.AccJustFlag; |
| // if ((eclipseModifiers & CompilerModifiers.AccSuper) != 0) { |
| // eclipseModifiers -= CompilerModifiers.AccSuper; |
| // } |
| return (eclipseModifiers == resolvedTypeModifiers); |
| } |
| |
| // private static StringSet makeStringSet(List strings) { |
| // StringSet ret = new StringSet(strings.size()); |
| // for (Iterator iter = strings.iterator(); iter.hasNext();) { |
| // String element = (String) iter.next(); |
| // ret.add(element); |
| // } |
| // return ret; |
| // } |
| |
| private String stringifySet(Set<?> l) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("{"); |
| for (Iterator<?> iter = l.iterator(); iter.hasNext();) { |
| Object el = iter.next(); |
| sb.append(el); |
| if (iter.hasNext()) { |
| sb.append(","); |
| } |
| } |
| sb.append("}"); |
| return sb.toString(); |
| } |
| |
| protected void addAffectedSourceFiles(Set<File> addTo, Set<File> lastTimeSources) { |
| if (qualifiedStrings.elementSize == 0 && simpleStrings.elementSize == 0) { |
| return; |
| } |
| if (listenerDefined()) { |
| getListener().recordDecision( |
| "Examining whether any other files now need compilation based on just compiling: '" |
| + stringifySet(lastTimeSources) + "'"); |
| } |
| // the qualifiedStrings are of the form 'p1/p2' & the simpleStrings are just 'X' |
| char[][][] qualifiedNames = ReferenceCollection.internQualifiedNames(qualifiedStrings); |
| // if a well known qualified name was found then we can skip over these |
| if (qualifiedNames.length < qualifiedStrings.elementSize) { |
| qualifiedNames = null; |
| } |
| char[][] simpleNames = ReferenceCollection.internSimpleNames(simpleStrings, true); |
| // if a well known name was found then we can skip over these |
| if (simpleNames.length < simpleStrings.elementSize) { |
| simpleNames = null; |
| } |
| |
| // System.err.println("simple: " + simpleStrings); |
| // System.err.println("qualif: " + qualifiedStrings); |
| |
| for (Iterator<Map.Entry<File, ReferenceCollection>> i = references.entrySet().iterator(); i.hasNext();) { |
| Map.Entry<File, ReferenceCollection> entry = i.next(); |
| ReferenceCollection refs = entry.getValue(); |
| if (refs != null && refs.includes(qualifiedNames, simpleNames)) { |
| File file = entry.getKey(); |
| if (file.exists()) { |
| if (!lastTimeSources.contains(file)) { // ??? O(n**2) |
| if (listenerDefined()) { |
| getListener().recordDecision("Need to recompile '" + file.getName().toString() + "'"); |
| } |
| addTo.add(file); |
| } |
| } |
| } |
| } |
| // add in the things we compiled previously - I know that seems crap but otherwise we may pull woven |
| // stuff off disk (since we no longer have UnwovenClassFile objects) in order to satisfy references |
| // in the new files we are about to compile (see pr133532) |
| if (addTo.size() > 0) { |
| addTo.addAll(lastTimeSources); |
| } |
| // // XXX Promote addTo to a Set - then we don't need this rubbish? but does it need to be ordered? |
| // if (addTo.size()>0) { |
| // for (Iterator iter = lastTimeSources.iterator(); iter.hasNext();) { |
| // Object element = (Object) iter.next(); |
| // if (!addTo.contains(element)) addTo.add(element); |
| // } |
| // } |
| |
| qualifiedStrings.clear(); |
| simpleStrings.clear(); |
| } |
| |
| /** |
| * Record that a particular type has been touched during a compilation run. Information is used to ensure any types depending |
| * upon this one are also recompiled. |
| * |
| * @param typename (possibly qualified) type name |
| */ |
| protected void recordTypeChanged(String typename) { |
| int lastDot = typename.lastIndexOf('.'); |
| String typeName; |
| if (lastDot != -1) { |
| String packageName = typename.substring(0, lastDot).replace('.', '/'); |
| qualifiedStrings.add(packageName); |
| typeName = typename.substring(lastDot + 1); |
| } else { |
| qualifiedStrings.add(""); |
| typeName = typename; |
| } |
| |
| int memberIndex = typeName.indexOf('$'); |
| if (memberIndex > 0) { |
| typeName = typeName.substring(0, memberIndex); |
| } |
| simpleStrings.add(typeName); |
| } |
| |
| /** |
| * Record some additional dependencies between types. When any of the types specified in fullyQualifiedTypeNames changes, we |
| * need to recompile the file named in the CompilationResult. This method patches that information into the existing data |
| * structures. |
| */ |
| public boolean recordDependencies(File file, String[] typeNameDependencies) { |
| try { |
| File sourceFile = new File(new String(file.getCanonicalPath())); |
| ReferenceCollection existingCollection = references.get(sourceFile); |
| if (existingCollection != null) { |
| existingCollection.addDependencies(typeNameDependencies); |
| return true; |
| } else { |
| ReferenceCollection rc = new ReferenceCollection(null, null, null); |
| rc.addDependencies(typeNameDependencies); |
| references.put(sourceFile, rc); |
| return true; |
| } |
| } catch (IOException ioe) { |
| ioe.printStackTrace(); |
| } |
| return false; |
| } |
| |
| protected void addDependentsOf(File sourceFile) { |
| List<ClassFile> cfs = this.fullyQualifiedTypeNamesResultingFromCompilationUnit.get(sourceFile); |
| if (cfs != null) { |
| for (ClassFile cf : cfs) { |
| recordTypeChanged(cf.fullyQualifiedTypeName); |
| } |
| } |
| } |
| |
| public void setStructureModel(AsmManager structureModel) { |
| this.structureModel = structureModel; |
| } |
| |
| public AsmManager getStructureModel() { |
| return structureModel; |
| } |
| |
| public void setWeaver(BcelWeaver bw) { |
| weaver = bw; |
| } |
| |
| public BcelWeaver getWeaver() { |
| return weaver; |
| } |
| |
| public void setWorld(BcelWorld bw) { |
| world = bw; |
| world.addTypeDelegateResolver(this); |
| } |
| |
| public BcelWorld getBcelWorld() { |
| return world; |
| } |
| |
| // |
| // public void setRelationshipMap(IRelationshipMap irm) { |
| // relmap = irm; |
| // } |
| // |
| // public IRelationshipMap getRelationshipMap() { |
| // return relmap; |
| // } |
| |
| public int getNumberOfStructuralChangesSinceLastFullBuild() { |
| return structuralChangesSinceLastFullBuild.size(); |
| } |
| |
| /** Returns last time we did a full or incremental build. */ |
| public long getLastBuildTime() { |
| return lastSuccessfulBuildTime; |
| } |
| |
| /** Returns last time we did a full build */ |
| public long getLastFullBuildTime() { |
| return lastSuccessfulFullBuildTime; |
| } |
| |
| /** |
| * @return Returns the buildConfig. |
| */ |
| public AjBuildConfig getBuildConfig() { |
| return this.buildConfig; |
| } |
| |
| public void clearBinarySourceFiles() { |
| this.binarySourceFiles = new HashMap<String, List<UnwovenClassFile>>(); |
| } |
| |
| public void recordBinarySource(String fromPathName, List<UnwovenClassFile> unwovenClassFiles) { |
| this.binarySourceFiles.put(fromPathName, unwovenClassFiles); |
| if (this.maybeIncremental()) { |
| List<ClassFile> simpleClassFiles = new LinkedList<ClassFile>(); |
| for (UnwovenClassFile ucf : unwovenClassFiles) { |
| ClassFile cf = getClassFileFor(ucf); |
| simpleClassFiles.add(cf); |
| } |
| this.inputClassFilesBySource.put(fromPathName, simpleClassFiles); |
| } |
| } |
| |
| /** |
| * @param ucf |
| * @return |
| */ |
| private ClassFile getClassFileFor(UnwovenClassFile ucf) { |
| return new ClassFile(ucf.getClassName(), new File(ucf.getFilename())); |
| } |
| |
| public Map<String, List<UnwovenClassFile>> getBinarySourceMap() { |
| return this.binarySourceFiles; |
| } |
| |
| public Map<String, File> getClassNameToFileMap() { |
| return this.classesFromName; |
| } |
| |
| public boolean hasResource(String resourceName) { |
| return this.resources.keySet().contains(resourceName); |
| } |
| |
| public void recordResource(String resourceName, File resourceSourceLocation) { |
| this.resources.put(resourceName, resourceSourceLocation); |
| } |
| |
| /** |
| * @return Returns the addedFiles. |
| */ |
| public Set<File> getAddedFiles() { |
| return this.addedFiles; |
| } |
| |
| /** |
| * @return Returns the deletedFiles. |
| */ |
| public Set<File> getDeletedFiles() { |
| return this.deletedFiles; |
| } |
| |
| public void forceBatchBuildNextTimeAround() { |
| this.batchBuildRequiredThisTime = true; |
| } |
| |
| public boolean requiresFullBatchBuild() { |
| return this.batchBuildRequiredThisTime; |
| } |
| |
| private static class ClassFile { |
| public String fullyQualifiedTypeName; |
| public File locationOnDisk; |
| |
| public ClassFile(String fqn, File location) { |
| this.fullyQualifiedTypeName = fqn; |
| this.locationOnDisk = location; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder s = new StringBuilder(); |
| s.append("ClassFile(type=").append(fullyQualifiedTypeName).append(",location=").append(locationOnDisk).append(")"); |
| return s.toString(); |
| } |
| |
| public void deleteFromFileSystem(AjBuildConfig buildConfig) { |
| String namePrefix = locationOnDisk.getName(); |
| namePrefix = namePrefix.substring(0, namePrefix.lastIndexOf('.')); |
| final String targetPrefix = namePrefix + BcelWeaver.CLOSURE_CLASS_PREFIX; |
| File dir = locationOnDisk.getParentFile(); |
| if (dir != null) { |
| File[] weaverGenerated = dir.listFiles(new FilenameFilter() { |
| @Override |
| public boolean accept(File dir, String name) { |
| return name.startsWith(targetPrefix); |
| } |
| }); |
| if (weaverGenerated != null) { |
| for (int i = 0; i < weaverGenerated.length; i++) { |
| weaverGenerated[i].delete(); |
| if (buildConfig != null && buildConfig.getCompilationResultDestinationManager() != null) { |
| buildConfig.getCompilationResultDestinationManager().reportFileRemove(weaverGenerated[i].getPath(), |
| CompilationResultDestinationManager.FILETYPE_CLASS); |
| } |
| } |
| } |
| } |
| locationOnDisk.delete(); |
| if (buildConfig != null && buildConfig.getCompilationResultDestinationManager() != null) { |
| buildConfig.getCompilationResultDestinationManager().reportFileRemove(locationOnDisk.getPath(), |
| CompilationResultDestinationManager.FILETYPE_CLASS); |
| } |
| } |
| } |
| |
| public void wipeAllKnowledge() { |
| buildManager.state = null; |
| // buildManager.setStructureModel(null); |
| } |
| |
| public Map<String, char[]> getAspectNamesToFileNameMap() { |
| return aspectsFromFileNames; |
| } |
| |
| public void initializeAspectNamesToFileNameMap() { |
| this.aspectsFromFileNames = new HashMap<String, char[]>(); |
| } |
| |
| // Will allow us to record decisions made during incremental processing, hopefully aid in debugging |
| public boolean listenerDefined() { |
| return stateListener != null; |
| } |
| |
| public IStateListener getListener() { |
| return stateListener; |
| } |
| |
| public IBinaryType checkPreviousBuild(String name) { |
| return resolvedTypeStructuresFromLastBuild.get(name); |
| } |
| |
| public AjBuildManager getAjBuildManager() { |
| return buildManager; |
| } |
| |
| public INameEnvironment getNameEnvironment() { |
| return this.nameEnvironment; |
| } |
| |
| public void setNameEnvironment(INameEnvironment nameEnvironment) { |
| this.nameEnvironment = nameEnvironment; |
| } |
| |
| public FileSystem getFileSystem() { |
| return this.fileSystem; |
| } |
| |
| public void setFileSystem(FileSystem fileSystem) { |
| this.fileSystem = fileSystem; |
| } |
| |
| /** |
| * Record an aspect that came in on the aspect path. When a .class file changes on the aspect path we can then recognize it as |
| * an aspect and know to do more than just a tiny incremental build. <br> |
| * TODO but this doesn't allow for a new aspect created on the aspectpath? |
| * |
| * @param aspectFile path to the file, eg. c:/temp/foo/Fred.class |
| */ |
| public void recordAspectClassFile(String aspectFile) { |
| aspectClassFiles.add(aspectFile); |
| } |
| |
| public void write(CompressingDataOutputStream dos) throws IOException { |
| // weaver |
| weaver.write(dos); |
| // world |
| // model |
| // local state |
| } |
| |
| /** |
| * See if we can create a delegate from a CompactTypeStructure - TODO better comment |
| */ |
| @Override |
| public ReferenceTypeDelegate getDelegate(ReferenceType referenceType) { |
| File f = classesFromName.get(referenceType.getName()); |
| if (f == null) { |
| return null; // not heard of it |
| } |
| try { |
| ClassParser parser = new ClassParser(f.toString()); |
| return world.buildBcelDelegate(referenceType, parser.parse(), true, false); |
| } catch (IOException e) { |
| IMessage msg = new Message("Failed to recover " + referenceType, referenceType.getDelegate()!=null?referenceType.getSourceLocation():null, false); |
| buildManager.handler.handleMessage(msg); |
| } |
| return null; |
| } |
| } |