Package org.aspectj.ajdt.internal.core.builder

Source Code of org.aspectj.ajdt.internal.core.builder.AjState$SoftHashMap$SoftReferenceKnownKey

/* *******************************************************************
* 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.File;
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.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;

  // 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);
      }
    }

    // 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);
      }
    }

    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();
      }
    }

    public Object put(Object k, Object v) {
      processQueue();
      return map.put(k, new SoftReferenceKnownKey(k, v));
    }

    public Set entrySet() {
      return map.entrySet();
    }

    public void clear() {
      processQueue();
      map.clear();
    }

    public int size() {
      processQueue();
      return map.size();
    }

    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;
  // }

  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));

    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);
          this.resolvedTypeStructuresFromLastBuild.put(thisTime.getClassName(), new CompactTypeStructureRepresentation(
              reader));
        } 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);
        this.resolvedTypeStructuresFromLastBuild.put(thisTime.getClassName(),
            new CompactTypeStructureRepresentation(reader));
      } catch (ClassFormatException cfe) {
        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 (world.forDEBUG_structuralChangesCode) {
            System.err.println("Detected a structural change in " + thisTime.getFilename());
          }
          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;
  }

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

    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() {
          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;
  }

  /**
   * 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
   */
  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) {
      System.err.println("Failed to recover " + referenceType);
      e.printStackTrace();
    }
    return null;
  }
}
TOP

Related Classes of org.aspectj.ajdt.internal.core.builder.AjState$SoftHashMap$SoftReferenceKnownKey

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.