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 ModuleNameReader moduleNameReader;
  93 
  94     public ModuleNameFromSourceReader moduleNameFromSourceReader;
  95 
  96     /** Get the ModuleFinder instance for this invocation. */
  97     public static ModuleFinder instance(Context context) {
  98         ModuleFinder instance = context.get(moduleFinderKey);
  99         if (instance == null)
 100             instance = new ModuleFinder(context);
 101         return instance;
 102     }
 103 
 104     /** Construct a new module finder. */
 105     protected ModuleFinder(Context context) {
 106         context.put(moduleFinderKey, this);
 107         names = Names.instance(context);
 108         syms = Symtab.instance(context);
 109         fileManager = context.get(JavaFileManager.class);
 110         log = Log.instance(context);
 111         classFinder = ClassFinder.instance(context);
 112 
 113         diags = JCDiagnostic.Factory.instance(context);
 114     }
 115 
 116     class ModuleLocationIterator implements Iterator<Set<Location>> {
 117         StandardLocation outer;
 118         Set<Location> next = null;
 119 
 120         Iterator<StandardLocation> outerIter = Arrays.asList(
 121                 StandardLocation.MODULE_SOURCE_PATH,
 122                 StandardLocation.UPGRADE_MODULE_PATH,
 123                 StandardLocation.SYSTEM_MODULES,
 124                 StandardLocation.MODULE_PATH
 125         ).iterator();
 126         Iterator<Set<Location>> innerIter = null;
 127 
 128         @Override
 129         public boolean hasNext() {
 130             while (next == null) {
 131                 while (innerIter == null || !innerIter.hasNext()) {
 132                     if (outerIter.hasNext()) {
 133                         outer = outerIter.next();
 134                         try {
 135                             innerIter = fileManager.listLocationsForModules(outer).iterator();
 136                         } catch (IOException e) {
 137                             System.err.println("error listing module locations for " + outer + ": " + e);  // FIXME
 138                         }
 139                     } else
 140                         return false;
 141                 }
 142 
 143                 if (innerIter.hasNext())
 144                     next = innerIter.next();
 145             }
 146             return true;
 147         }
 148 
 149         @Override
 150         public Set<Location> next() {
 151             hasNext();
 152             if (next != null) {
 153                 Set<Location> result = next;
 154                 next = null;
 155                 return result;
 156             }
 157             throw new NoSuchElementException();
 158         }
 159 
 160     }
 161 
 162     ModuleLocationIterator moduleLocationIterator = new ModuleLocationIterator();
 163 
 164     public ModuleSymbol findModule(Name name) {
 165         return findModule(syms.enterModule(name));
 166     }
 167 
 168     public ModuleSymbol findModule(ModuleSymbol msym) {
 169         if (msym.kind != ERR && msym.sourceLocation == null && msym.classLocation == null) {
 170             // fill in location
 171             List<ModuleSymbol> list = scanModulePath(msym);
 172             if (list.isEmpty()) {
 173                 msym.kind = ERR;
 174             }
 175         }
 176         if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
 177             // fill in module-info
 178             findModuleInfo(msym);
 179         }
 180         return msym;
 181     }
 182 
 183     public List<ModuleSymbol> findAllModules() {
 184         List<ModuleSymbol> list = scanModulePath(null);
 185         for (ModuleSymbol msym: list) {
 186             if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
 187                 // fill in module-info
 188                 findModuleInfo(msym);
 189             }
 190         }
 191         return list;
 192     }
 193 
 194     public ModuleSymbol findSingleModule() {
 195         try {
 196             JavaFileObject src_fo = getModuleInfoFromLocation(StandardLocation.SOURCE_PATH, Kind.SOURCE);
 197             JavaFileObject class_fo = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
 198             JavaFileObject fo = (src_fo == null) ? class_fo
 199                     : (class_fo == null) ? src_fo
 200                             : classFinder.preferredFileObject(src_fo, class_fo);
 201 
 202             ModuleSymbol msym;
 203             if (fo == null) {
 204                 msym = syms.unnamedModule;
 205             } else {
 206                 msym = readModule(fo);
 207             }
 208 
 209             if (msym.patchLocation == null) {
 210                 msym.classLocation = StandardLocation.CLASS_OUTPUT;
 211             } else {
 212                 msym.patchOutputLocation = StandardLocation.CLASS_OUTPUT;
 213             }
 214             return msym;
 215 
 216         } catch (IOException e) {
 217             throw new Error(e); // FIXME
 218         }
 219     }
 220 
 221     private ModuleSymbol readModule(JavaFileObject fo) throws IOException {
 222         Name name;
 223         switch (fo.getKind()) {
 224             case SOURCE:
 225                 name = moduleNameFromSourceReader.readModuleName(fo);
 226                 if (name == null) {
 227                     JCDiagnostic diag =
 228                         diags.fragment(Fragments.FileDoesNotContainModule);
 229                     ClassSymbol errModuleInfo = syms.defineClass(names.module_info, syms.errModule);
 230                     throw new ClassFinder.BadClassFile(errModuleInfo, fo, diag, diags);
 231                 }
 232                 break;
 233             case CLASS:
 234                 try {
 235                     name = names.fromString(readModuleName(fo));
 236                 } catch (BadClassFile | IOException ex) {
 237                     //fillIn will report proper errors:
 238                     name = names.error;
 239                 }
 240                 break;
 241             default:
 242                 Assert.error();
 243                 name = names.error;
 244                 break;
 245         }
 246 
 247         ModuleSymbol msym = syms.enterModule(name);
 248         msym.module_info.classfile = fo;
 249         if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) && name != names.error) {
 250             msym.patchLocation = fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH, name.toString());
 251 
 252             if (msym.patchLocation != null) {
 253                 JavaFileObject patchFO = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
 254                 patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), patchFO);
 255                 patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), patchFO);
 256 
 257                 if (patchFO != null) {
 258                     msym.module_info.classfile = patchFO;
 259                 }
 260             }
 261         }
 262 
 263         msym.completer = Completer.NULL_COMPLETER;
 264         classFinder.fillIn(msym.module_info);
 265 
 266         return msym;
 267     }
 268 
 269     private String readModuleName(JavaFileObject jfo) throws IOException, ModuleNameReader.BadClassFile {
 270         if (moduleNameReader == null)
 271             moduleNameReader = new ModuleNameReader();
 272         return moduleNameReader.readModuleName(jfo);
 273     }
 274 
 275     private JavaFileObject getModuleInfoFromLocation(Location location, Kind kind) throws IOException {
 276         if (location == null || !fileManager.hasLocation(location))
 277             return null;
 278 
 279         return fileManager.getJavaFileForInput(location,
 280                                                names.module_info.toString(),
 281                                                kind);
 282     }
 283 
 284     private List<ModuleSymbol> scanModulePath(ModuleSymbol toFind) {
 285         ListBuffer<ModuleSymbol> results = new ListBuffer<>();
 286         Map<Name, Location> namesInSet = new HashMap<>();
 287         boolean multiModuleMode = fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH);
 288         while (moduleLocationIterator.hasNext()) {
 289             Set<Location> locns = (moduleLocationIterator.next());
 290             namesInSet.clear();
 291             for (Location l: locns) {
 292                 try {
 293                     Name n = names.fromString(fileManager.inferModuleName(l));
 294                     if (namesInSet.put(n, l) == null) {
 295                         ModuleSymbol msym = syms.enterModule(n);
 296                         if (msym.sourceLocation != null || msym.classLocation != null) {
 297                             // module has already been found, so ignore this instance
 298                             continue;
 299                         }
 300                         if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) &&
 301                             msym.patchLocation == null) {
 302                             msym.patchLocation =
 303                                     fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH,
 304                                                                      msym.name.toString());
 305                             if (msym.patchLocation != null &&
 306                                 multiModuleMode &&
 307                                 fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 308                                 msym.patchOutputLocation =
 309                                         fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
 310                                                                          msym.name.toString());
 311                             }
 312                         }
 313                         if (moduleLocationIterator.outer == StandardLocation.MODULE_SOURCE_PATH) {
 314                             msym.sourceLocation = l;
 315                             if (fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 316                                 msym.classLocation =
 317                                         fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
 318                                                                          msym.name.toString());
 319                             }
 320                         } else {
 321                             msym.classLocation = l;
 322                         }
 323                         if (moduleLocationIterator.outer == StandardLocation.SYSTEM_MODULES ||
 324                             moduleLocationIterator.outer == StandardLocation.UPGRADE_MODULE_PATH) {
 325                             msym.flags_field |= Flags.SYSTEM_MODULE;
 326                         }
 327                         if (toFind == null ||
 328                             (toFind == msym && (msym.sourceLocation != null || msym.classLocation != null))) {
 329                             // Note: cannot return msym directly, because we must finish
 330                             // processing this set first
 331                             results.add(msym);
 332                         }
 333                     } else {
 334                         log.error(Errors.DuplicateModuleOnPath(
 335                                 getDescription(moduleLocationIterator.outer), n));
 336                     }
 337                 } catch (IOException e) {
 338                     // skip location for now?  log error?
 339                 }
 340             }
 341             if (toFind != null && results.nonEmpty())
 342                 return results.toList();
 343         }
 344 
 345         return results.toList();
 346     }
 347 
 348     private void findModuleInfo(ModuleSymbol msym) {
 349         try {
 350             JavaFileObject fo;
 351 
 352             fo = getModuleInfoFromLocation(msym.patchOutputLocation, Kind.CLASS);
 353             fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), fo);
 354             fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), fo);
 355 
 356             if (fo == null) {
 357                 fo = getModuleInfoFromLocation(msym.classLocation, Kind.CLASS);
 358                 fo = preferredFileObject(getModuleInfoFromLocation(msym.sourceLocation, Kind.SOURCE), fo);
 359             }
 360 
 361             if (fo == null) {
 362                 String moduleName = msym.sourceLocation == null && msym.classLocation != null ?
 363                     fileManager.inferModuleName(msym.classLocation) : null;
 364                 if (moduleName != null) {
 365                     msym.module_info.classfile = null;
 366                     msym.flags_field |= Flags.AUTOMATIC_MODULE;
 367                 } else {
 368                     msym.kind = ERR;
 369                 }
 370             } else {
 371                 msym.module_info.classfile = fo;
 372                 msym.module_info.completer = new Symbol.Completer() {
 373                     @Override
 374                     public void complete(Symbol sym) throws CompletionFailure {
 375                         classFinder.fillIn(msym.module_info);
 376                     }
 377                     @Override
 378                     public String toString() {
 379                         return "ModuleInfoCompleter";
 380                     }
 381                 };
 382             }
 383         } catch (IOException e) {
 384             msym.kind = ERR;
 385         }
 386     }
 387 
 388     private JavaFileObject preferredFileObject(JavaFileObject fo1, JavaFileObject fo2) {
 389         if (fo1 == null) return fo2;
 390         if (fo2 == null) return fo1;
 391         return classFinder.preferredFileObject(fo1, fo2);
 392     }
 393 
 394     Fragment getDescription(StandardLocation l) {
 395         switch (l) {
 396             case MODULE_PATH: return Fragments.LocnModule_path;
 397             case MODULE_SOURCE_PATH: return Fragments.LocnModule_source_path;
 398             case SYSTEM_MODULES: return Fragments.LocnSystem_modules;
 399             case UPGRADE_MODULE_PATH: return Fragments.LocnUpgrade_module_path;
 400             default:
 401                 throw new AssertionError();
 402         }
 403     }
 404 
 405     public interface ModuleNameFromSourceReader {
 406         public Name readModuleName(JavaFileObject file);
 407     }
 408 
 409 }