1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.tools.javac.code;
  26 
  27 import java.io.IOException;
  28 import java.util.Arrays;
  29 import java.util.HashMap;
  30 import java.util.Iterator;
  31 import java.util.Map;
  32 import java.util.NoSuchElementException;
  33 import java.util.Set;
  34 import java.util.function.Function;
  35 
  36 import javax.tools.JavaFileManager;
  37 import javax.tools.JavaFileManager.Location;
  38 import javax.tools.JavaFileObject;
  39 import javax.tools.JavaFileObject.Kind;
  40 import javax.tools.StandardLocation;
  41 
  42 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  43 import com.sun.tools.javac.code.Symbol.Completer;
  44 import com.sun.tools.javac.code.Symbol.CompletionFailure;
  45 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
  46 import com.sun.tools.javac.jvm.ModuleNameReader;
  47 import com.sun.tools.javac.jvm.ModuleNameReader.BadClassFile;
  48 import com.sun.tools.javac.resources.CompilerProperties.Errors;
  49 import com.sun.tools.javac.resources.CompilerProperties.Fragments;
  50 import com.sun.tools.javac.util.Assert;
  51 import com.sun.tools.javac.util.Context;
  52 import com.sun.tools.javac.util.JCDiagnostic;
  53 import com.sun.tools.javac.util.JCDiagnostic.Fragment;
  54 import com.sun.tools.javac.util.List;
  55 import com.sun.tools.javac.util.ListBuffer;
  56 import com.sun.tools.javac.util.Log;
  57 import com.sun.tools.javac.util.Name;
  58 import com.sun.tools.javac.util.Names;
  59 
  60 import static com.sun.tools.javac.code.Kinds.Kind.*;
  61 
  62 /**
  63  *  This class provides operations to locate module definitions
  64  *  from the source and class files on the paths provided to javac.
  65  *
  66  *  <p><b>This is NOT part of any supported API.
  67  *  If you write code that depends on this, you do so at your own risk.
  68  *  This code and its internal interfaces are subject to change or
  69  *  deletion without notice.</b>
  70  */
  71 public class ModuleFinder {
  72     /** The context key for the module finder. */
  73     protected static final Context.Key<ModuleFinder> moduleFinderKey = new Context.Key<>();
  74 
  75     /** The log to use for verbose output. */
  76     private final Log log;
  77 
  78     /** The symbol table. */
  79     private final Symtab syms;
  80 
  81     /** The name table. */
  82     private final Names names;
  83 
  84     private final ClassFinder classFinder;
  85 
  86     /** Access to files
  87      */
  88     private final JavaFileManager fileManager;
  89 
  90     private final JCDiagnostic.Factory diags;
  91 
  92     private final DeferredCompletionFailureHandler dcfh;
  93 
  94     private ModuleNameReader moduleNameReader;
  95 
  96     public ModuleNameFromSourceReader moduleNameFromSourceReader;
  97 
  98     /** Get the ModuleFinder instance for this invocation. */
  99     public static ModuleFinder instance(Context context) {
 100         ModuleFinder instance = context.get(moduleFinderKey);
 101         if (instance == null)
 102             instance = new ModuleFinder(context);
 103         return instance;
 104     }
 105 
 106     /** Construct a new module finder. */
 107     protected ModuleFinder(Context context) {
 108         context.put(moduleFinderKey, this);
 109         names = Names.instance(context);
 110         syms = Symtab.instance(context);
 111         fileManager = context.get(JavaFileManager.class);
 112         log = Log.instance(context);
 113         classFinder = ClassFinder.instance(context);
 114 
 115         diags = JCDiagnostic.Factory.instance(context);
 116         dcfh = DeferredCompletionFailureHandler.instance(context);
 117     }
 118 
 119     class ModuleLocationIterator implements Iterator<Set<Location>> {
 120         StandardLocation outer;
 121         Set<Location> next = null;
 122 
 123         Iterator<StandardLocation> outerIter = Arrays.asList(
 124                 StandardLocation.MODULE_SOURCE_PATH,
 125                 StandardLocation.UPGRADE_MODULE_PATH,
 126                 StandardLocation.SYSTEM_MODULES,
 127                 StandardLocation.MODULE_PATH
 128         ).iterator();
 129         Iterator<Set<Location>> innerIter = null;
 130 
 131         @Override
 132         public boolean hasNext() {
 133             while (next == null) {
 134                 while (innerIter == null || !innerIter.hasNext()) {
 135                     if (outerIter.hasNext()) {
 136                         outer = outerIter.next();
 137                         try {
 138                             innerIter = fileManager.listLocationsForModules(outer).iterator();
 139                         } catch (IOException e) {
 140                             System.err.println("error listing module locations for " + outer + ": " + e);  // FIXME
 141                         }
 142                     } else
 143                         return false;
 144                 }
 145 
 146                 if (innerIter.hasNext())
 147                     next = innerIter.next();
 148             }
 149             return true;
 150         }
 151 
 152         @Override
 153         public Set<Location> next() {
 154             hasNext();
 155             if (next != null) {
 156                 Set<Location> result = next;
 157                 next = null;
 158                 return result;
 159             }
 160             throw new NoSuchElementException();
 161         }
 162 
 163     }
 164 
 165     ModuleLocationIterator moduleLocationIterator = new ModuleLocationIterator();
 166 
 167     public ModuleSymbol findModule(Name name) {
 168         return findModule(syms.enterModule(name));
 169     }
 170 
 171     public ModuleSymbol findModule(ModuleSymbol msym) {
 172         if (msym.kind != ERR && msym.sourceLocation == null && msym.classLocation == null) {
 173             // fill in location
 174             List<ModuleSymbol> list = scanModulePath(msym);
 175             if (list.isEmpty()) {
 176                 msym.kind = ERR;
 177             }
 178         }
 179         if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
 180             // fill in module-info
 181             findModuleInfo(msym);
 182         }
 183         return msym;
 184     }
 185 
 186     public List<ModuleSymbol> findAllModules() {
 187         List<ModuleSymbol> list = scanModulePath(null);
 188         for (ModuleSymbol msym: list) {
 189             if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
 190                 // fill in module-info
 191                 findModuleInfo(msym);
 192             }
 193         }
 194         return list;
 195     }
 196 
 197     public ModuleSymbol findSingleModule() {
 198         try {
 199             JavaFileObject src_fo = getModuleInfoFromLocation(StandardLocation.SOURCE_PATH, Kind.SOURCE);
 200             JavaFileObject class_fo = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
 201             JavaFileObject fo = (src_fo == null) ? class_fo
 202                     : (class_fo == null) ? src_fo
 203                             : classFinder.preferredFileObject(src_fo, class_fo);
 204 
 205             ModuleSymbol msym;
 206             if (fo == null) {
 207                 msym = syms.unnamedModule;
 208             } else {
 209                 msym = readModule(fo);
 210             }
 211 
 212             if (msym.patchLocation == null) {
 213                 msym.classLocation = StandardLocation.CLASS_OUTPUT;
 214             } else {
 215                 msym.patchOutputLocation = StandardLocation.CLASS_OUTPUT;
 216             }
 217             return msym;
 218 
 219         } catch (IOException e) {
 220             throw new Error(e); // FIXME
 221         }
 222     }
 223 
 224     private ModuleSymbol readModule(JavaFileObject fo) throws IOException {
 225         Name name;
 226         switch (fo.getKind()) {
 227             case SOURCE:
 228                 name = moduleNameFromSourceReader.readModuleName(fo);
 229                 if (name == null) {
 230                     JCDiagnostic diag =
 231                         diags.fragment(Fragments.FileDoesNotContainModule);
 232                     ClassSymbol errModuleInfo = syms.defineClass(names.module_info, syms.errModule);
 233                     throw new ClassFinder.BadClassFile(errModuleInfo, fo, diag, diags, dcfh);
 234                 }
 235                 break;
 236             case CLASS:
 237                 try {
 238                     name = names.fromString(readModuleName(fo));
 239                 } catch (BadClassFile | IOException ex) {
 240                     //fillIn will report proper errors:
 241                     name = names.error;
 242                 }
 243                 break;
 244             default:
 245                 Assert.error();
 246                 name = names.error;
 247                 break;
 248         }
 249 
 250         ModuleSymbol msym = syms.enterModule(name);
 251         msym.module_info.classfile = fo;
 252         if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) && name != names.error) {
 253             msym.patchLocation = fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH, name.toString());
 254 
 255             if (msym.patchLocation != null) {
 256                 JavaFileObject patchFO = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
 257                 patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), patchFO);
 258                 patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), patchFO);
 259 
 260                 if (patchFO != null) {
 261                     msym.module_info.classfile = patchFO;
 262                 }
 263             }
 264         }
 265 
 266         msym.completer = Completer.NULL_COMPLETER;
 267         classFinder.fillIn(msym.module_info);
 268 
 269         return msym;
 270     }
 271 
 272     private String readModuleName(JavaFileObject jfo) throws IOException, ModuleNameReader.BadClassFile {
 273         if (moduleNameReader == null)
 274             moduleNameReader = new ModuleNameReader();
 275         return moduleNameReader.readModuleName(jfo);
 276     }
 277 
 278     private JavaFileObject getModuleInfoFromLocation(Location location, Kind kind) throws IOException {
 279         if (location == null || !fileManager.hasLocation(location))
 280             return null;
 281 
 282         return fileManager.getJavaFileForInput(location,
 283                                                names.module_info.toString(),
 284                                                kind);
 285     }
 286 
 287     private List<ModuleSymbol> scanModulePath(ModuleSymbol toFind) {
 288         ListBuffer<ModuleSymbol> results = new ListBuffer<>();
 289         Map<Name, Location> namesInSet = new HashMap<>();
 290         boolean multiModuleMode = fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH);
 291         while (moduleLocationIterator.hasNext()) {
 292             Set<Location> locns = (moduleLocationIterator.next());
 293             namesInSet.clear();
 294             for (Location l: locns) {
 295                 try {
 296                     Name n = names.fromString(fileManager.inferModuleName(l));
 297                     if (namesInSet.put(n, l) == null) {
 298                         ModuleSymbol msym = syms.enterModule(n);
 299                         if (msym.sourceLocation != null || msym.classLocation != null) {
 300                             // module has already been found, so ignore this instance
 301                             continue;
 302                         }
 303                         if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) &&
 304                             msym.patchLocation == null) {
 305                             msym.patchLocation =
 306                                     fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH,
 307                                                                      msym.name.toString());
 308                             if (msym.patchLocation != null &&
 309                                 multiModuleMode &&
 310                                 fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 311                                 msym.patchOutputLocation =
 312                                         fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
 313                                                                          msym.name.toString());
 314                             }
 315                         }
 316                         if (moduleLocationIterator.outer == StandardLocation.MODULE_SOURCE_PATH) {
 317                             msym.sourceLocation = l;
 318                             if (fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 319                                 msym.classLocation =
 320                                         fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
 321                                                                          msym.name.toString());
 322                             }
 323                         } else {
 324                             msym.classLocation = l;
 325                         }
 326                         if (moduleLocationIterator.outer == StandardLocation.SYSTEM_MODULES ||
 327                             moduleLocationIterator.outer == StandardLocation.UPGRADE_MODULE_PATH) {
 328                             msym.flags_field |= Flags.SYSTEM_MODULE;
 329                         }
 330                         if (toFind == null ||
 331                             (toFind == msym && (msym.sourceLocation != null || msym.classLocation != null))) {
 332                             // Note: cannot return msym directly, because we must finish
 333                             // processing this set first
 334                             results.add(msym);
 335                         }
 336                     } else {
 337                         log.error(Errors.DuplicateModuleOnPath(
 338                                 getDescription(moduleLocationIterator.outer), n));
 339                     }
 340                 } catch (IOException e) {
 341                     // skip location for now?  log error?
 342                 }
 343             }
 344             if (toFind != null && results.nonEmpty())
 345                 return results.toList();
 346         }
 347 
 348         return results.toList();
 349     }
 350 
 351     private void findModuleInfo(ModuleSymbol msym) {
 352         try {
 353             JavaFileObject fo;
 354 
 355             fo = getModuleInfoFromLocation(msym.patchOutputLocation, Kind.CLASS);
 356             fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), fo);
 357             fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), fo);
 358 
 359             if (fo == null) {
 360                 fo = getModuleInfoFromLocation(msym.classLocation, Kind.CLASS);
 361                 fo = preferredFileObject(getModuleInfoFromLocation(msym.sourceLocation, Kind.SOURCE), fo);
 362             }
 363 
 364             if (fo == null) {
 365                 String moduleName = msym.sourceLocation == null && msym.classLocation != null ?
 366                     fileManager.inferModuleName(msym.classLocation) : null;
 367                 if (moduleName != null) {
 368                     msym.module_info.classfile = null;
 369                     msym.flags_field |= Flags.AUTOMATIC_MODULE;
 370                 } else {
 371                     msym.kind = ERR;
 372                 }
 373             } else {
 374                 msym.module_info.classfile = fo;
 375                 msym.module_info.completer = new Symbol.Completer() {
 376                     @Override
 377                     public void complete(Symbol sym) throws CompletionFailure {
 378                         classFinder.fillIn(msym.module_info);
 379                     }
 380                     @Override
 381                     public String toString() {
 382                         return "ModuleInfoCompleter";
 383                     }
 384                 };
 385             }
 386         } catch (IOException e) {
 387             msym.kind = ERR;
 388         }
 389     }
 390 
 391     private JavaFileObject preferredFileObject(JavaFileObject fo1, JavaFileObject fo2) {
 392         if (fo1 == null) return fo2;
 393         if (fo2 == null) return fo1;
 394         return classFinder.preferredFileObject(fo1, fo2);
 395     }
 396 
 397     Fragment getDescription(StandardLocation l) {
 398         switch (l) {
 399             case MODULE_PATH: return Fragments.LocnModule_path;
 400             case MODULE_SOURCE_PATH: return Fragments.LocnModule_source_path;
 401             case SYSTEM_MODULES: return Fragments.LocnSystem_modules;
 402             case UPGRADE_MODULE_PATH: return Fragments.LocnUpgrade_module_path;
 403             default:
 404                 throw new AssertionError();
 405         }
 406     }
 407 
 408     public interface ModuleNameFromSourceReader {
 409         public Name readModuleName(JavaFileObject file);
 410     }
 411 
 412 }