/** Return those files belonging to now, but not prev. */
 private Set<Source> calculateAddedSources() {
   Set<Source> added = new HashSet<>();
   for (String src : now.sources().keySet()) {
     if (prev.sources().get(src) == null) {
       added.add(now.sources().get(src));
     }
   }
   return added;
 }
 /** Return those files belonging to prev, but not now. */
 private Set<Source> calculateRemovedSources() {
   Set<Source> removed = new HashSet<>();
   for (String src : prev.sources().keySet()) {
     if (now.sources().get(src) == null) {
       removed.add(prev.sources().get(src));
     }
   }
   return removed;
 }
 /**
  * Return those files where the timestamp is newer. If a source file timestamp suddenly is older
  * than what is known about it in javac_state, then consider it modified, but print a warning!
  */
 private Set<Source> calculateModifiedSources() {
   Set<Source> modified = new HashSet<>();
   for (String src : now.sources().keySet()) {
     Source n = now.sources().get(src);
     Source t = prev.sources().get(src);
     if (prev.sources().get(src) != null) {
       if (t != null) {
         if (n.lastModified() > t.lastModified()) {
           modified.add(n);
         } else if (n.lastModified() < t.lastModified()) {
           modified.add(n);
           Log.warn("The source file " + n.name() + " timestamp has moved backwards in time.");
         }
       }
     }
   }
   return modified;
 }
  /**
   * Compare the calculate source list, with an explicit list, usually supplied from the makefile.
   * Used to detect bugs where the makefile and sjavac have different opinions on which files should
   * be compiled.
   */
  public void compareWithMakefileList(File makefileSourceList) throws ProblemException {
    // If we are building on win32 using for example cygwin the paths in the makefile source list
    // might be /cygdrive/c/.... which does not match c:\....
    // We need to adjust our calculated sources to be identical, if necessary.
    boolean mightNeedRewriting = File.pathSeparatorChar == ';';

    if (makefileSourceList == null) return;

    Set<String> calculatedSources = new HashSet<>();
    Set<String> listedSources = new HashSet<>();

    // Create a set of filenames with full paths.
    for (Source s : now.sources().values()) {
      // Don't include link only sources when comparing sources to compile
      if (!s.isLinkedOnly()) {
        calculatedSources.add(s.file().getPath());
      }
    }
    // Read in the file and create another set of filenames with full paths.
    try {
      BufferedReader in = new BufferedReader(new FileReader(makefileSourceList));
      for (; ; ) {
        String l = in.readLine();
        if (l == null) break;
        l = l.trim();
        if (mightNeedRewriting) {
          if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) {
            // Everything a-ok, the format is already C:\foo\bar
          } else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) {
            // The format is C:/foo/bar, rewrite into the above format.
            l = l.replaceAll("/", "\\\\");
          } else if (l.charAt(0) == '/' && l.indexOf("/", 1) != -1) {
            // The format might be: /cygdrive/c/foo/bar, rewrite into the above format.
            // Do not hardcode the name cygdrive here.
            int slash = l.indexOf("/", 1);
            l = l.replaceAll("/", "\\\\");
            l = "" + l.charAt(slash + 1) + ":" + l.substring(slash + 2);
          }
          if (Character.isLowerCase(l.charAt(0))) {
            l = Character.toUpperCase(l.charAt(0)) + l.substring(1);
          }
        }
        listedSources.add(l);
      }
    } catch (FileNotFoundException e) {
      throw new ProblemException(
          "Could not open " + makefileSourceList.getPath() + " since it does not exist!");
    } catch (IOException e) {
      throw new ProblemException("Could not read " + makefileSourceList.getPath());
    }

    for (String s : listedSources) {
      if (!calculatedSources.contains(s)) {
        throw new ProblemException(
            "The makefile listed source " + s + " was not calculated by the smart javac wrapper!");
      }
    }

    for (String s : calculatedSources) {
      if (!listedSources.contains(s)) {
        throw new ProblemException(
            "The smart javac wrapper calculated source " + s + " was not listed by the makefiles!");
      }
    }
  }
  /**
   * For all packages, find all sources belonging to the package, group the sources based on their
   * transformers and apply the transformers on each source code group.
   */
  private boolean perform(File outputDir, Map<String, Transformer> suffixRules) {
    boolean rc = true;
    // Group sources based on transforms. A source file can only belong to a single transform.
    Map<Transformer, Map<String, Set<URI>>> groupedSources = new HashMap<>();
    for (Source src : now.sources().values()) {
      Transformer t = suffixRules.get(src.suffix());
      if (t != null) {
        if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) {
          addFileToTransform(groupedSources, t, src);
        }
      }
    }
    // Go through the transforms and transform them.
    for (Map.Entry<Transformer, Map<String, Set<URI>>> e : groupedSources.entrySet()) {
      Transformer t = e.getKey();
      Map<String, Set<URI>> srcs = e.getValue();
      // These maps need to be synchronized since multiple threads will be writing results into
      // them.
      Map<String, Set<URI>> packageArtifacts =
          Collections.synchronizedMap(new HashMap<String, Set<URI>>());
      Map<String, Set<String>> packageDependencies =
          Collections.synchronizedMap(new HashMap<String, Set<String>>());
      Map<String, String> packagePublicApis =
          Collections.synchronizedMap(new HashMap<String, String>());

      boolean r =
          t.transform(
              srcs,
              visibleSrcs,
              visibleClasses,
              prev.dependents(),
              outputDir.toURI(),
              packageArtifacts,
              packageDependencies,
              packagePublicApis,
              0,
              isIncremental(),
              numCores,
              out,
              err);
      if (!r) rc = false;

      for (String p : srcs.keySet()) {
        recompiledPackages.add(p);
      }
      // The transform is done! Extract all the artifacts and store the info into the Package
      // objects.
      for (Map.Entry<String, Set<URI>> a : packageArtifacts.entrySet()) {
        Module mnow = now.findModuleFromPackageName(a.getKey());
        mnow.addArtifacts(a.getKey(), a.getValue());
      }
      // Extract all the dependencies and store the info into the Package objects.
      for (Map.Entry<String, Set<String>> a : packageDependencies.entrySet()) {
        Set<String> deps = a.getValue();
        Module mnow = now.findModuleFromPackageName(a.getKey());
        mnow.setDependencies(a.getKey(), deps);
      }
      // Extract all the pubapis and store the info into the Package objects.
      for (Map.Entry<String, String> a : packagePublicApis.entrySet()) {
        Module mprev = prev.findModuleFromPackageName(a.getKey());
        List<String> pubapi = Package.pubapiToList(a.getValue());
        Module mnow = now.findModuleFromPackageName(a.getKey());
        mnow.setPubapi(a.getKey(), pubapi);
        if (mprev.hasPubapiChanged(a.getKey(), pubapi)) {
          // Aha! The pubapi of this package has changed!
          // It can also be a new compile from scratch.
          if (mprev.lookupPackage(a.getKey()).existsInJavacState()) {
            // This is an incremental compile! The pubapi
            // did change. Trigger recompilation of dependents.
            packagesWithChangedPublicApis.add(a.getKey());
            Log.info("The pubapi of " + Util.justPackageName(a.getKey()) + " has changed!");
          }
        }
      }
    }
    return rc;
  }
 /** Returns true if this is an incremental build. */
 public boolean isIncremental() {
   return !prev.sources().isEmpty();
 }