content: /*
   * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package openjdk.tools.sjavac.comp;
  
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.stream.Stream;
  
  import openjdk.tools.javac.file.JavacFileManager;
  import openjdk.tools.javac.main.Main;
  import openjdk.tools.javac.main.Main.Result;
  import openjdk.tools.javac.util.Context;
  import openjdk.tools.sjavac.JavacState;
  import openjdk.tools.sjavac.Log;
  import openjdk.tools.sjavac.Module;
  import openjdk.tools.sjavac.ProblemException;
  import openjdk.tools.sjavac.Source;
  import openjdk.tools.sjavac.Transformer;
  import openjdk.tools.sjavac.Util;
  import openjdk.tools.sjavac.options.Option;
  import openjdk.tools.sjavac.options.Options;
  import openjdk.tools.sjavac.options.SourceLocation;
  import openjdk.tools.sjavac.server.Sjavac;
  import java.io.UncheckedIOException;
  
  import jdkx.tools.JavaFileManager;
  
  /**
   * The sjavac implementation that interacts with javac and performs the actual
   * compilation.
   *
   *  <p><b>This is NOT part of any supported API.
   *  If you write code that depends on this, you do so at your own risk.
   *  This code and its internal interfaces are subject to change or
   *  deletion without notice.</b>
   */
  public class SjavacImpl implements Sjavac {
  
      @Override
      public Result compile(String[] args) {
          Options options;
          try {
              options = Options.parseArgs(args);
          } catch (IllegalArgumentException e) {
              Log.error(e.getMessage());
              return Result.CMDERR;
          }
  
          if (!validateOptions(options))
              return Result.CMDERR;
  
          if (srcDstOverlap(options.getSources(), options.getDestDir())) {
              return Result.CMDERR;
          }
  
          if (!createIfMissing(options.getDestDir()))
              return Result.ERROR;
  
          Path stateDir = options.getStateDir();
          if (stateDir != null && !createIfMissing(options.getStateDir()))
              return Result.ERROR;
  
          Path gensrc = options.getGenSrcDir();
          if (gensrc != null && !createIfMissing(gensrc))
              return Result.ERROR;
  
          Path hdrdir = options.getHeaderDir();
          if (hdrdir != null && !createIfMissing(hdrdir))
              return Result.ERROR;
  
          if (stateDir == null) {
              // Prepare context. Direct logging to our byte array stream.
              Context context = new Context();
              StringWriter strWriter = new StringWriter();
              PrintWriter printWriter = new PrintWriter(strWriter);
              openjdk.tools.javac.util.Log.preRegister(context, printWriter);
              JavacFileManager.preRegister(context);
  
              // Prepare arguments
              String[] passThroughArgs = Stream.of(args)
                                               .filter(arg -> !arg.startsWith(Option.SERVER.arg))
                                               .toArray(String[]::new);
              // Compile
              Result result = new Main("javac", printWriter).compile(passThroughArgs, context);
  
              // Process compiler output (which is always errors)
              printWriter.flush();
              Util.getLines(strWriter.toString()).forEach(Log::error);
  
              // Clean up
              JavaFileManager fileManager = context.get(JavaFileManager.class);
              if (fileManager instanceof JavacFileManager) {
                  try {
                      ((JavacFileManager) fileManager).close();
                  } catch (IOException es) {
                      throw new UncheckedIOException(es);
                  }
              }
              return result;
  
          } else {
              // Load the prev build state database.
              JavacState javac_state = JavacState.load(options);
  
              // Setup the suffix rules from the command line.
              Map<String, Transformer> suffixRules = new HashMap<>();
  
              // Handling of .java-compilation
              suffixRules.putAll(javac_state.getJavaSuffixRule());
  
              // Handling of -copy and -tr
              suffixRules.putAll(options.getTranslationRules());
  
              // All found modules are put here.
              Map<String,Module> modules = new HashMap<>();
              // We start out in the legacy empty no-name module.
              // As soon as we stumble on a module-info.java file we change to that module.
              Module current_module = new Module("", "");
              modules.put("", current_module);
  
              try {
                  // Find all sources, use the suffix rules to know which files are sources.
                  Map<String,Source> sources = new HashMap<>();
  
                  // Find the files, this will automatically populate the found modules
                  // with found packages where the sources are found!
                  findSourceFiles(options.getSources(),
                                  suffixRules.keySet(),
                                  sources,
                                  modules,
                                  current_module,
                                  options.isDefaultPackagePermitted(),
                                  false);
  
                  if (sources.isEmpty()) {
                      Log.error("Found nothing to compile!");
                      return Result.ERROR;
                  }
  
  
                  // Create a map of all source files that are available for linking. Both -src and
                  // -sourcepath point to such files. It is possible to specify multiple
                  // -sourcepath options to enable different filtering rules. If the
                  // filters are the same for multiple sourcepaths, they may be concatenated
                  // using :(;). Before sending the list of sourcepaths to javac, they are
                  // all concatenated. The list created here is used by the SmartFileWrapper to
                  // make sure only the correct sources are actually available.
                  // We might find more modules here as well.
                  Map<String,Source> sources_to_link_to = new HashMap<>();
  
                  List<SourceLocation> sourceResolutionLocations = new ArrayList<>();
                  sourceResolutionLocations.addAll(options.getSources());
                  sourceResolutionLocations.addAll(options.getSourceSearchPaths());
                  findSourceFiles(sourceResolutionLocations,
                                  Collections.singleton(".java"),
                                  sources_to_link_to,
                                  modules,
                                  current_module,
                                  options.isDefaultPackagePermitted(),
                                  true);
  
                  // Add the set of sources to the build database.
                  javac_state.now().flattenPackagesSourcesAndArtifacts(modules);
                  javac_state.now().checkInternalState("checking sources", false, sources);
                  javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
                  javac_state.setVisibleSources(sources_to_link_to);
  
                  int round = 0;
                  printRound(round);
  
                  // If there is any change in the source files, taint packages
                  // and mark the database in need of saving.
                  javac_state.checkSourceStatus(false);
  
                  // Find all existing artifacts. Their timestamp will match the last modified timestamps stored
                  // in javac_state, simply because loading of the JavacState will clean out all artifacts
                  // that do not match the javac_state database.
                  javac_state.findAllArtifacts();
  
                  // Remove unidentified artifacts from the bin, gensrc and header dirs.
                  // (Unless we allow them to be there.)
                  // I.e. artifacts that are not known according to the build database (javac_state).
                  // For examples, files that have been manually copied into these dirs.
                  // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
                  // in javac_state) have already been removed when the javac_state was loaded.
                  if (!options.areUnidentifiedArtifactsPermitted()) {
                      javac_state.removeUnidentifiedArtifacts();
                  }
                  // Go through all sources and taint all packages that miss artifacts.
                  javac_state.taintPackagesThatMissArtifacts();
  
                  // Check recorded classpath public apis. Taint packages that depend on
                  // classpath classes whose public apis have changed.
                  javac_state.taintPackagesDependingOnChangedClasspathPackages();
  
                  // Now clean out all known artifacts belonging to tainted packages.
                  javac_state.deleteClassArtifactsInTaintedPackages();
                  // Copy files, for example property files, images files, xml files etc etc.
                  javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules);
                  // Translate files, for example compile properties or compile idls.
                  javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules);
                  // Add any potentially generated java sources to the tobe compiled list.
                  // (Generated sources must always have a package.)
                  Map<String,Source> generated_sources = new HashMap<>();
  
                  Source.scanRoot(Util.pathToFile(options.getGenSrcDir()),
                                  Util.set(".java"),
                                  Collections.emptyList(),
                                  Collections.emptyList(),
                                  generated_sources,
                                  modules,
                                  current_module,
                                  false,
                                  true,
                                  false);
                  javac_state.now().flattenPackagesSourcesAndArtifacts(modules);
                  // Recheck the the source files and their timestamps again.
                  javac_state.checkSourceStatus(true);
  
                  // Now do a safety check that the list of source files is identical
                  // to the list Make believes we are compiling. If we do not get this
                  // right, then incremental builds will fail with subtility.
                  // If any difference is detected, then we will fail hard here.
                  // This is an important safety net.
                  javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList()));
  
                  // Do the compilations, repeatedly until no tainted packages exist.
                  boolean again;
                  // Collect the name of all compiled packages.
                  Set<String> recently_compiled = new HashSet<>();
                  boolean[] rc = new boolean[1];
  
                  CompilationService compilationService = new CompilationService();
                  do {
                      if (round > 0)
                          printRound(round);
                      // Clean out artifacts in tainted packages.
                      javac_state.deleteClassArtifactsInTaintedPackages();
                      again = javac_state.performJavaCompilations(compilationService,
                                                                  options,
                                                                  recently_compiled,
                                                                  rc);
                      if (!rc[0]) {
                          Log.debug("Compilation failed.");
                          break;
                      }
                      if (!again) {
                          Log.debug("Nothing left to do.");
                      }
                      round++;
                  } while (again);
                  Log.debug("No need to do another round.");
  
                  // Only update the state if the compile went well.
                  if (rc[0]) {
                      javac_state.save();
                      // Reflatten only the artifacts.
                      javac_state.now().flattenArtifacts(modules);
                      // Remove artifacts that were generated during the last compile, but not this one.
                      javac_state.removeSuperfluousArtifacts(recently_compiled);
                  }
  
                  return rc[0] ? Result.OK : Result.ERROR;
              } catch (ProblemException e) {
                  // For instance make file list mismatch.
                  Log.error(e.getMessage());
                  Log.debug(e);
                  return Result.ERROR;
              } catch (Exception e) {
                  Log.error(e);
                  return Result.ERROR;
              }
          }
      }
  
      @Override
      public void shutdown() {
          // Nothing to clean up
      }
  
      private static boolean validateOptions(Options options) {
  
          String err = null;
  
          if (options.getDestDir() == null) {
              err = "Please specify output directory.";
          } else if (options.isJavaFilesAmongJavacArgs()) {
              err = "Sjavac does not handle explicit compilation of single .java files.";
          } else if (!options.getImplicitPolicy().equals("none")) {
              err = "The only allowed setting for sjavac is -implicit:none";
          } else if (options.getSources().isEmpty() && options.getStateDir() != null) {
              err = "You have to specify -src when using --state-dir.";
          } else if (options.getTranslationRules().size() > 1
                  && options.getGenSrcDir() == null) {
              err = "You have translators but no gensrc dir (-s) specified!";
          }
  
          if (err != null)
              Log.error(err);
  
          return err == null;
  
      }
  
      private static boolean srcDstOverlap(List<SourceLocation> locs, Path dest) {
          for (SourceLocation loc : locs) {
              if (isOverlapping(loc.getPath(), dest)) {
                  Log.error("Source location " + loc.getPath() + " overlaps with destination " + dest);
                  return true;
              }
          }
          return false;
      }
  
      private static boolean isOverlapping(Path p1, Path p2) {
          p1 = p1.toAbsolutePath().normalize();
          p2 = p2.toAbsolutePath().normalize();
          return p1.startsWith(p2) || p2.startsWith(p1);
      }
  
      private static boolean createIfMissing(Path dir) {
  
          if (Files.isDirectory(dir))
              return true;
  
          if (Files.exists(dir)) {
              Log.error(dir + " is not a directory.");
              return false;
          }
  
          try {
              Files.createDirectories(dir);
          } catch (IOException e) {
              Log.error("Could not create directory: " + e.getMessage());
              return false;
          }
  
          return true;
      }
  
      /** Find source files in the given source locations. */
      public static void findSourceFiles(List<SourceLocation> sourceLocations,
                                         Set<String> sourceTypes,
                                         Map<String,Source> foundFiles,
                                         Map<String, Module> foundModules,
                                         Module currentModule,
                                         boolean permitSourcesInDefaultPackage,
                                         boolean inLinksrc)
                                                 throws IOException {
  
          for (SourceLocation source : sourceLocations) {
              source.findSourceFiles(sourceTypes,
                                     foundFiles,
                                     foundModules,
                                     currentModule,
                                     permitSourcesInDefaultPackage,
                                     inLinksrc);
          }
      }
  
      private static void printRound(int round) {
          Log.debug("****************************************");
          Log.debug("* Round " + round + "                              *");
          Log.debug("****************************************");
      }
  }
  