1 /*
   2  * Copyright (c) 2015, 2016, 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.Completer;
  43 import com.sun.tools.javac.code.Symbol.CompletionFailure;
  44 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
  45 import com.sun.tools.javac.jvm.ModuleNameReader;
  46 import com.sun.tools.javac.jvm.ModuleNameReader.BadClassFile;
  47 import com.sun.tools.javac.resources.CompilerProperties.Errors;
  48 import com.sun.tools.javac.resources.CompilerProperties.Fragments;
  49 import com.sun.tools.javac.util.Assert;
  50 import com.sun.tools.javac.util.Context;
  51 import com.sun.tools.javac.util.JCDiagnostic;
  52 import com.sun.tools.javac.util.JCDiagnostic.Fragment;
  53 import com.sun.tools.javac.util.List;
  54 import com.sun.tools.javac.util.ListBuffer;
  55 import com.sun.tools.javac.util.Log;
  56 import com.sun.tools.javac.util.Name;
  57 import com.sun.tools.javac.util.Names;
  58 
  59 import static com.sun.tools.javac.code.Kinds.Kind.*;
  60 
  61 /**
  62  *  This class provides operations to locate module definitions
  63  *  from the source and class files on the paths provided to javac.
  64  *
  65  *  <p><b>This is NOT part of any supported API.
  66  *  If you write code that depends on this, you do so at your own risk.
  67  *  This code and its internal interfaces are subject to change or
  68  *  deletion without notice.</b>
  69  */
  70 public class ModuleFinder {
  71     /** The context key for the module finder. */
  72     protected static final Context.Key<ModuleFinder> moduleFinderKey = new Context.Key<>();
  73 
  74     /** The log to use for verbose output. */
  75     private final Log log;
  76 
  77     /** The symbol table. */
  78     private final Symtab syms;
  79 
  80     /** The name table. */
  81     private final Names names;
  82 
  83     private final ClassFinder classFinder;
  84 
  85     /** Access to files
  86      */
  87     private final JavaFileManager fileManager;
  88 
  89     private final JCDiagnostic.Factory diags;
  90 
  91     private ModuleNameReader moduleNameReader;
  92 
  93     public ModuleInfoSourceFileCompleter sourceFileCompleter;
  94 
  95     /** Get the ModuleFinder instance for this invocation. */
  96     public static ModuleFinder instance(Context context) {
  97         ModuleFinder instance = context.get(moduleFinderKey);
  98         if (instance == null)
  99             instance = new ModuleFinder(context);
 100         return instance;
 101     }
 102 
 103     /** Construct a new module finder. */
 104     protected ModuleFinder(Context context) {
 105         context.put(moduleFinderKey, this);
 106         names = Names.instance(context);
 107         syms = Symtab.instance(context);
 108         fileManager = context.get(JavaFileManager.class);
 109         log = Log.instance(context);
 110         classFinder = ClassFinder.instance(context);
 111 
 112         diags = JCDiagnostic.Factory.instance(context);
 113     }
 114 
 115     class ModuleLocationIterator implements Iterator<Set<Location>> {
 116         StandardLocation outer;
 117         Set<Location> next = null;
 118 
 119         Iterator<StandardLocation> outerIter = Arrays.asList(
 120                 StandardLocation.MODULE_SOURCE_PATH,
 121                 StandardLocation.UPGRADE_MODULE_PATH,
 122                 StandardLocation.SYSTEM_MODULES,
 123                 StandardLocation.MODULE_PATH
 124         ).iterator();
 125         Iterator<Set<Location>> innerIter = null;
 126 
 127         @Override
 128         public boolean hasNext() {
 129             while (next == null) {
 130                 while (innerIter == null || !innerIter.hasNext()) {
 131                     if (outerIter.hasNext()) {
 132                         outer = outerIter.next();
 133                         try {
 134                             innerIter = fileManager.listLocationsForModules(outer).iterator();
 135                         } catch (IOException e) {
 136                             System.err.println("error listing module locations for " + outer + ": " + e);  // FIXME
 137                         }
 138                     } else
 139                         return false;
 140                 }
 141 
 142                 if (innerIter.hasNext())
 143                     next = innerIter.next();
 144             }
 145             return true;
 146         }
 147 
 148         @Override
 149         public Set<Location> next() {
 150             hasNext();
 151             if (next != null) {
 152                 Set<Location> result = next;
 153                 next = null;
 154                 return result;
 155             }
 156             throw new NoSuchElementException();
 157         }
 158 
 159     }
 160 
 161     ModuleLocationIterator moduleLocationIterator = new ModuleLocationIterator();
 162 
 163     public ModuleSymbol findModule(Name name) {
 164         return findModule(syms.enterModule(name));
 165     }
 166 
 167     public ModuleSymbol findModule(ModuleSymbol msym) {
 168         if (msym.kind != ERR && msym.sourceLocation == null && msym.classLocation == null) {
 169             // fill in location
 170             List<ModuleSymbol> list = scanModulePath(msym);
 171             if (list.isEmpty()) {
 172                 msym.kind = ERR;
 173             }
 174         }
 175         if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
 176             // fill in module-info
 177             findModuleInfo(msym);
 178         }
 179         return msym;
 180     }
 181 
 182     public List<ModuleSymbol> findAllModules() {
 183         List<ModuleSymbol> list = scanModulePath(null);
 184         for (ModuleSymbol msym: list) {
 185             if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
 186                 // fill in module-info
 187                 findModuleInfo(msym);
 188             }
 189         }
 190         return list;
 191     }
 192 
 193     private boolean inFindSingleModule;
 194 
 195     public ModuleSymbol findSingleModule() {
 196         try {
 197             JavaFileObject src_fo = getModuleInfoFromLocation(StandardLocation.SOURCE_PATH, Kind.SOURCE);
 198             JavaFileObject class_fo = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
 199             JavaFileObject fo = (src_fo == null) ? class_fo
 200                     : (class_fo == null) ? src_fo
 201                             : classFinder.preferredFileObject(src_fo, class_fo);
 202 
 203             ModuleSymbol msym;
 204             if (fo == null) {
 205                 msym = syms.unnamedModule;
 206             } else {
 207                 switch (fo.getKind()) {
 208                     case SOURCE:
 209                         if (!inFindSingleModule) {
 210                             try {
 211                                 inFindSingleModule = true;
 212                                 // Note: the following will trigger a re-entrant call to Modules.enter
 213                                 msym = sourceFileCompleter.complete(fo);
 214                                 msym.module_info.classfile = fo;
 215                             } finally {
 216                                 inFindSingleModule = false;
 217                             }
 218                         } else {
 219                             //the module-info.java does not contain a module declaration,
 220                             //avoid infinite recursion:
 221                             msym = syms.unnamedModule;
 222                         }
 223                         break;
 224                     case CLASS:
 225                         Name name;
 226                         try {
 227                             name = names.fromString(readModuleName(fo));
 228                         } catch (BadClassFile | IOException ex) {
 229                             //fillIn will report proper errors:
 230                             name = names.error;
 231                         }
 232                         msym = syms.enterModule(name);
 233                         msym.module_info.classfile = fo;
 234                         msym.completer = Completer.NULL_COMPLETER;
 235                         classFinder.fillIn(msym.module_info);
 236                         break;
 237                     default:
 238                         Assert.error();
 239                         msym = syms.unnamedModule;
 240                         break;
 241                 }
 242             }
 243 
 244             msym.classLocation = StandardLocation.CLASS_OUTPUT;
 245             return msym;
 246 
 247         } catch (IOException e) {
 248             throw new Error(e); // FIXME
 249         }
 250     }
 251 
 252     private String readModuleName(JavaFileObject jfo) throws IOException, ModuleNameReader.BadClassFile {
 253         if (moduleNameReader == null)
 254             moduleNameReader = new ModuleNameReader();
 255         return moduleNameReader.readModuleName(jfo);
 256     }
 257 
 258     private JavaFileObject getModuleInfoFromLocation(Location location, Kind kind) throws IOException {
 259         if (!fileManager.hasLocation(location))
 260             return null;
 261 
 262         return fileManager.getJavaFileForInput(location,
 263                                                names.module_info.toString(),
 264                                                kind);
 265     }
 266 
 267     private List<ModuleSymbol> scanModulePath(ModuleSymbol toFind) {
 268         ListBuffer<ModuleSymbol> results = new ListBuffer<>();
 269         Map<Name, Location> namesInSet = new HashMap<>();
 270         boolean multiModuleMode = fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH);
 271         while (moduleLocationIterator.hasNext()) {
 272             Set<Location> locns = (moduleLocationIterator.next());
 273             namesInSet.clear();
 274             for (Location l: locns) {
 275                 try {
 276                     Name n = names.fromString(fileManager.inferModuleName(l));
 277                     if (namesInSet.put(n, l) == null) {
 278                         ModuleSymbol msym = syms.enterModule(n);
 279                         if (msym.sourceLocation != null || msym.classLocation != null) {
 280                             // module has already been found, so ignore this instance
 281                             continue;
 282                         }
 283                         if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) &&
 284                             msym.patchLocation == null) {
 285                             msym.patchLocation =
 286                                     fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH,
 287                                                                      msym.name.toString());
 288                             checkModuleInfoOnLocation(msym.patchLocation, Kind.CLASS, Kind.SOURCE);
 289                             if (msym.patchLocation != null &&
 290                                 multiModuleMode &&
 291                                 fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 292                                 msym.patchOutputLocation =
 293                                         fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
 294                                                                          msym.name.toString());
 295                                 checkModuleInfoOnLocation(msym.patchOutputLocation, Kind.CLASS);
 296                             }
 297                         }
 298                         if (moduleLocationIterator.outer == StandardLocation.MODULE_SOURCE_PATH) {
 299                             if (msym.patchLocation == null) {
 300                                 msym.sourceLocation = l;
 301                                 if (fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 302                                     msym.classLocation =
 303                                             fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
 304                                                                              msym.name.toString());
 305                                 }
 306                             }
 307                         } else {
 308                             msym.classLocation = l;
 309                         }
 310                         if (moduleLocationIterator.outer == StandardLocation.SYSTEM_MODULES ||
 311                             moduleLocationIterator.outer == StandardLocation.UPGRADE_MODULE_PATH) {
 312                             msym.flags_field |= Flags.SYSTEM_MODULE;
 313                         }
 314                         if (toFind == null ||
 315                             (toFind == msym && (msym.sourceLocation != null || msym.classLocation != null))) {
 316                             // Note: cannot return msym directly, because we must finish
 317                             // processing this set first
 318                             results.add(msym);
 319                         }
 320                     } else {
 321                         log.error(Errors.DuplicateModuleOnPath(
 322                                 getDescription(moduleLocationIterator.outer), n));
 323                     }
 324                 } catch (IOException e) {
 325                     // skip location for now?  log error?
 326                 }
 327             }
 328             if (toFind != null && results.nonEmpty())
 329                 return results.toList();
 330         }
 331 
 332         return results.toList();
 333     }
 334 
 335     private void checkModuleInfoOnLocation(Location location, Kind... kinds) throws IOException {
 336         if (location == null)
 337             return ;
 338 
 339         for (Kind kind : kinds) {
 340             JavaFileObject file = fileManager.getJavaFileForInput(location,
 341                                                                   names.module_info.toString(),
 342                                                                   kind);
 343             if (file != null) {
 344                 log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(file));
 345                 return ;
 346             }
 347         }
 348     }
 349 
 350     private void findModuleInfo(ModuleSymbol msym) {
 351         try {
 352             JavaFileObject src_fo = (msym.sourceLocation == null) ? null
 353                     : fileManager.getJavaFileForInput(msym.sourceLocation,
 354                             names.module_info.toString(), Kind.SOURCE);
 355 
 356             JavaFileObject class_fo = (msym.classLocation == null) ? null
 357                     : fileManager.getJavaFileForInput(msym.classLocation,
 358                             names.module_info.toString(), Kind.CLASS);
 359 
 360             JavaFileObject fo = (src_fo == null) ? class_fo :
 361                     (class_fo == null) ? src_fo :
 362                     classFinder.preferredFileObject(src_fo, class_fo);
 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     Fragment getDescription(StandardLocation l) {
 392         switch (l) {
 393             case MODULE_PATH: return Fragments.LocnModule_path;
 394             case MODULE_SOURCE_PATH: return Fragments.LocnModule_source_path;
 395             case SYSTEM_MODULES: return Fragments.LocnSystem_modules;
 396             case UPGRADE_MODULE_PATH: return Fragments.LocnUpgrade_module_path;
 397             default:
 398                 throw new AssertionError();
 399         }
 400     }
 401 
 402     public interface ModuleInfoSourceFileCompleter {
 403         public ModuleSymbol complete(JavaFileObject file);
 404     }
 405 
 406 }