content: /*
   * Copyright (c) 2015, 2020, 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.javac.code;
  
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Set;
  
  import jdkx.tools.JavaFileManager;
  import jdkx.tools.JavaFileManager.Location;
  import jdkx.tools.JavaFileObject;
  import jdkx.tools.JavaFileObject.Kind;
  import jdkx.tools.StandardLocation;
  
  import openjdk.tools.javac.code.Symbol.ClassSymbol;
  import openjdk.tools.javac.code.Symbol.Completer;
  import openjdk.tools.javac.code.Symbol.CompletionFailure;
  import openjdk.tools.javac.code.Symbol.ModuleSymbol;
  import openjdk.tools.javac.jvm.ModuleNameReader;
  import openjdk.tools.javac.jvm.ModuleNameReader.BadClassFile;
  import openjdk.tools.javac.resources.CompilerProperties.Errors;
  import openjdk.tools.javac.resources.CompilerProperties.Fragments;
  import openjdk.tools.javac.util.Assert;
  import openjdk.tools.javac.util.Context;
  import openjdk.tools.javac.util.JCDiagnostic;
  import openjdk.tools.javac.util.JCDiagnostic.Fragment;
  import openjdk.tools.javac.util.List;
  import openjdk.tools.javac.util.ListBuffer;
  import openjdk.tools.javac.util.Log;
  import openjdk.tools.javac.util.Name;
  import openjdk.tools.javac.util.Names;
  
  import static openjdk.tools.javac.code.Kinds.Kind.*;
  import openjdk.tools.javac.comp.Check;
  
  /**
   *  This class provides operations to locate module definitions
   *  from the source and class files on the paths provided to javac.
   *
   *  <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 ModuleFinder {
      /** The context key for the module finder. */
      protected static final Context.Key<ModuleFinder> moduleFinderKey = new Context.Key<>();
  
      /** The log to use for verbose output. */
      private final Log log;
  
      /** The symbol table. */
      private final Symtab syms;
  
      /** The name table. */
      private final Names names;
  
      private final ClassFinder classFinder;
      private final Check chk;
  
      /** Access to files
       */
      private final JavaFileManager fileManager;
  
      private final JCDiagnostic.Factory diags;
  
      private final DeferredCompletionFailureHandler dcfh;
  
      private ModuleNameReader moduleNameReader;
  
      public ModuleNameFromSourceReader moduleNameFromSourceReader;
  
      /** Get the ModuleFinder instance for this invocation. */
      public static ModuleFinder instance(Context context) {
          ModuleFinder instance = context.get(moduleFinderKey);
          if (instance == null)
              instance = new ModuleFinder(context);
          return instance;
      }
  
      /** Construct a new module finder. */
      protected ModuleFinder(Context context) {
          context.put(moduleFinderKey, this);
          names = Names.instance(context);
          syms = Symtab.instance(context);
          fileManager = context.get(JavaFileManager.class);
          log = Log.instance(context);
          classFinder = ClassFinder.instance(context);
          chk = Check.instance(context);
  
          diags = JCDiagnostic.Factory.instance(context);
          dcfh = DeferredCompletionFailureHandler.instance(context);
      }
  
      class ModuleLocationIterator implements Iterator<Set<Location>> {
          StandardLocation outer;
          Set<Location> next = null;
  
          Iterator<StandardLocation> outerIter = Arrays.asList(
                  StandardLocation.MODULE_SOURCE_PATH,
                  StandardLocation.UPGRADE_MODULE_PATH,
                  StandardLocation.SYSTEM_MODULES,
                  StandardLocation.MODULE_PATH
          ).iterator();
          Iterator<Set<Location>> innerIter = null;
  
          @Override
          public boolean hasNext() {
              while (next == null) {
                  while (innerIter == null || !innerIter.hasNext()) {
                      if (outerIter.hasNext()) {
                          outer = outerIter.next();
                          try {
                              innerIter = fileManager.listLocationsForModules(outer).iterator();
                          } catch (IOException e) {
                              System.err.println("error listing module locations for " + outer + ": " + e);  // FIXME
                          }
                      } else
                          return false;
                  }
  
                  if (innerIter.hasNext())
                      next = innerIter.next();
              }
              return true;
          }
  
          @Override
          public Set<Location> next() {
              hasNext();
              if (next != null) {
                  Set<Location> result = next;
                  next = null;
                  return result;
              }
              throw new NoSuchElementException();
          }
  
      }
  
      ModuleLocationIterator moduleLocationIterator = new ModuleLocationIterator();
  
      public ModuleSymbol findModule(Name name) {
          return findModule(syms.enterModule(name));
      }
  
      public ModuleSymbol findModule(ModuleSymbol msym) {
          if (msym.kind != ERR && msym.sourceLocation == null && msym.classLocation == null) {
              // fill in location
              List<ModuleSymbol> list = scanModulePath(msym);
              if (list.isEmpty()) {
                  msym.kind = ERR;
              }
          }
          if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
              // fill in module-info
              findModuleInfo(msym);
          }
          return msym;
      }
  
      public List<ModuleSymbol> findAllModules() {
          List<ModuleSymbol> list = scanModulePath(null);
          for (ModuleSymbol msym: list) {
              if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
                  // fill in module-info
                  findModuleInfo(msym);
              }
          }
          return list;
      }
  
      public ModuleSymbol findSingleModule() {
          try {
              JavaFileObject src_fo = getModuleInfoFromLocation(StandardLocation.SOURCE_PATH, Kind.SOURCE);
              JavaFileObject class_fo = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
              JavaFileObject fo = (src_fo == null) ? class_fo
                      : (class_fo == null) ? src_fo
                              : classFinder.preferredFileObject(src_fo, class_fo);
  
              ModuleSymbol msym;
              if (fo == null) {
                  msym = syms.unnamedModule;
              } else {
                  try {
                      msym = readModule(fo);
                  } catch (CompletionFailure ex) {
                      chk.completionError(null, ex);
                      msym = syms.unnamedModule;
                  }
                      
              }
  
              if (msym.patchLocation == null) {
                  msym.classLocation = StandardLocation.CLASS_OUTPUT;
              } else {
                  msym.patchOutputLocation = StandardLocation.CLASS_OUTPUT;
              }
              return msym;
  
          } catch (IOException e) {
              throw new Error(e); // FIXME
          }
      }
  
      private ModuleSymbol readModule(JavaFileObject fo) throws IOException {
          Name name;
          switch (fo.getKind()) {
              case SOURCE:
                  name = moduleNameFromSourceReader.readModuleName(fo);
                  if (name == null) {
                      JCDiagnostic diag =
                          diags.fragment(Fragments.FileDoesNotContainModule);
                      ClassSymbol errModuleInfo = syms.defineClass(names.module_info, syms.errModule);
                      throw new ClassFinder.BadClassFile(errModuleInfo, fo, diag, diags, dcfh);
                  }
                  break;
              case CLASS:
                  try {
                      name = names.fromString(readModuleName(fo));
                  } catch (BadClassFile | IOException ex) {
                      //fillIn will report proper errors:
                      name = names.error;
                  }
                  break;
              default:
                  Assert.error();
                  name = names.error;
                  break;
          }
  
          ModuleSymbol msym = syms.enterModule(name);
  
          if (msym.module_info.classfile == null) {
              msym.module_info.classfile = fo;
              if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) && name != names.error) {
                  msym.patchLocation = fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH, name.toString());
  
                  if (msym.patchLocation != null) {
                      JavaFileObject patchFO = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
                      patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), patchFO);
                      patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), patchFO);
  
                      if (patchFO != null) {
                          msym.module_info.classfile = patchFO;
                      }
                  }
              }
  
              msym.completer = Completer.NULL_COMPLETER;
              classFinder.fillIn(msym.module_info);
          }
  
          return msym;
      }
  
      private String readModuleName(JavaFileObject jfo) throws IOException, ModuleNameReader.BadClassFile {
          if (moduleNameReader == null)
              moduleNameReader = new ModuleNameReader();
          return moduleNameReader.readModuleName(jfo);
      }
  
      private JavaFileObject getModuleInfoFromLocation(Location location, Kind kind) throws IOException {
          if (location == null || !fileManager.hasLocation(location))
              return null;
  
          return fileManager.getJavaFileForInput(location,
                                                 names.module_info.toString(),
                                                 kind);
      }
  
      private List<ModuleSymbol> scanModulePath(ModuleSymbol toFind) {
          ListBuffer<ModuleSymbol> results = new ListBuffer<>();
          Map<Name, Location> namesInSet = new HashMap<>();
          boolean multiModuleMode = fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH);
          while (moduleLocationIterator.hasNext()) {
              Set<Location> locns = (moduleLocationIterator.next());
              namesInSet.clear();
              for (Location l: locns) {
                  try {
                      Name n = names.fromString(fileManager.inferModuleName(l));
                      if (namesInSet.put(n, l) == null) {
                          ModuleSymbol msym = syms.enterModule(n);
                          if (msym.sourceLocation != null || msym.classLocation != null) {
                              // module has already been found, so ignore this instance
                              continue;
                          }
                          if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) &&
                              msym.patchLocation == null) {
                              msym.patchLocation =
                                      fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH,
                                                                       msym.name.toString());
                              if (msym.patchLocation != null &&
                                  multiModuleMode &&
                                  fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
                                  msym.patchOutputLocation =
                                          fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
                                                                           msym.name.toString());
                              }
                          }
                          if (moduleLocationIterator.outer == StandardLocation.MODULE_SOURCE_PATH) {
                              msym.sourceLocation = l;
                              if (fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
                                  msym.classLocation =
                                          fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
                                                                           msym.name.toString());
                              }
                          } else {
                              msym.classLocation = l;
                          }
                          if (moduleLocationIterator.outer == StandardLocation.SYSTEM_MODULES ||
                              moduleLocationIterator.outer == StandardLocation.UPGRADE_MODULE_PATH) {
                              msym.flags_field |= Flags.SYSTEM_MODULE;
                          }
                          if (toFind == null ||
                              (toFind == msym && (msym.sourceLocation != null || msym.classLocation != null))) {
                              // Note: cannot return msym directly, because we must finish
                              // processing this set first
                              results.add(msym);
                          }
                      } else {
                          log.error(Errors.DuplicateModuleOnPath(
                                  getDescription(moduleLocationIterator.outer), n));
                      }
                  } catch (IOException e) {
                      // skip location for now?  log error?
                  }
              }
              if (toFind != null && results.nonEmpty())
                  return results.toList();
          }
  
          return results.toList();
      }
  
      private void findModuleInfo(ModuleSymbol msym) {
          try {
              JavaFileObject fo;
  
              fo = getModuleInfoFromLocation(msym.patchOutputLocation, Kind.CLASS);
              fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), fo);
              fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), fo);
  
              if (fo == null) {
                  fo = getModuleInfoFromLocation(msym.classLocation, Kind.CLASS);
                  fo = preferredFileObject(getModuleInfoFromLocation(msym.sourceLocation, Kind.SOURCE), fo);
              }
  
              if (fo == null) {
                  String moduleName = msym.sourceLocation == null && msym.classLocation != null ?
                      fileManager.inferModuleName(msym.classLocation) : null;
                  if (moduleName != null) {
                      msym.module_info.classfile = null;
                      msym.flags_field |= Flags.AUTOMATIC_MODULE;
                  } else {
                      msym.kind = ERR;
                  }
              } else {
                  msym.module_info.classfile = fo;
                  msym.module_info.completer = new Symbol.Completer() {
                      @Override
                      public void complete(Symbol sym) throws CompletionFailure {
                          try {
                              classFinder.fillIn(msym.module_info);
                          } catch (Exception ex) {
                              msym.kind = ERR;
                              //make sure the module is initialized:
                              msym.directives = List.nil();
                              msym.exports = List.nil();
                              msym.provides = List.nil();
                              msym.requires = List.nil();
                              msym.uses = List.nil();
                          }
                      }
                      @Override
                      public String toString() {
                          return "ModuleInfoCompleter";
                      }
                  };
              }
          } catch (IOException e) {
              msym.kind = ERR;
          }
      }
  
      private JavaFileObject preferredFileObject(JavaFileObject fo1, JavaFileObject fo2) {
          if (fo1 == null) return fo2;
          if (fo2 == null) return fo1;
          return classFinder.preferredFileObject(fo1, fo2);
      }
  
      Fragment getDescription(StandardLocation l) {
          switch (l) {
              case MODULE_PATH: return Fragments.LocnModule_path;
              case MODULE_SOURCE_PATH: return Fragments.LocnModule_source_path;
              case SYSTEM_MODULES: return Fragments.LocnSystem_modules;
              case UPGRADE_MODULE_PATH: return Fragments.LocnUpgrade_module_path;
              default:
                  throw new AssertionError();
          }
      }
  
      public interface ModuleNameFromSourceReader {
          public Name readModuleName(JavaFileObject file);
      }
  
  }
  