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         while (moduleLocationIterator.hasNext()) {
 271             Set<Location> locns = (moduleLocationIterator.next());
 272             namesInSet.clear();
 273             for (Location l: locns) {
 274                 try {
 275                     Name n = names.fromString(fileManager.inferModuleName(l));
 276                     if (namesInSet.put(n, l) == null) {
 277                         ModuleSymbol msym = syms.enterModule(n);
 278                         if (msym.sourceLocation != null || msym.classLocation != null) {
 279                             // module has already been found, so ignore this instance
 280                             continue;
 281                         }
 282                         if (moduleLocationIterator.outer == StandardLocation.MODULE_SOURCE_PATH) {
 283                             msym.sourceLocation = l;
 284                             if (fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
 285                                 msym.classLocation = fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT, msym.name.toString());
 286                             }
 287                         } else {
 288                             msym.classLocation = l;
 289                         }
 290                         if (moduleLocationIterator.outer == StandardLocation.SYSTEM_MODULES ||
 291                             moduleLocationIterator.outer == StandardLocation.UPGRADE_MODULE_PATH) {
 292                             msym.flags_field |= Flags.SYSTEM_MODULE;
 293                         }
 294                         if (toFind == msym || toFind == null) {
 295                             // Note: cannot return msym directly, because we must finish
 296                             // processing this set first
 297                             results.add(msym);
 298                         }
 299                     } else {
 300                         log.error(Errors.DuplicateModuleOnPath(
 301                                 getDescription(moduleLocationIterator.outer), n));
 302                     }
 303                 } catch (IOException e) {
 304                     // skip location for now?  log error?
 305                 }
 306             }
 307             if (toFind != null && results.nonEmpty())
 308                 return results.toList();
 309         }
 310 
 311         return results.toList();
 312     }
 313 
 314     private void findModuleInfo(ModuleSymbol msym) {
 315         try {
 316             JavaFileObject src_fo = (msym.sourceLocation == null) ? null
 317                     : fileManager.getJavaFileForInput(msym.sourceLocation,
 318                             names.module_info.toString(), Kind.SOURCE);
 319 
 320             JavaFileObject class_fo = (msym.classLocation == null) ? null
 321                     : fileManager.getJavaFileForInput(msym.classLocation,
 322                             names.module_info.toString(), Kind.CLASS);
 323 
 324             JavaFileObject fo = (src_fo == null) ? class_fo :
 325                     (class_fo == null) ? src_fo :
 326                     classFinder.preferredFileObject(src_fo, class_fo);
 327 
 328             if (fo == null) {
 329                 String moduleName = msym.sourceLocation == null && msym.classLocation != null ?
 330                     fileManager.inferModuleName(msym.classLocation) : null;
 331                 if (moduleName != null) {
 332                     msym.module_info.classfile = null;
 333                     msym.flags_field |= Flags.AUTOMATIC_MODULE;
 334                 } else {
 335                     msym.kind = ERR;
 336                 }
 337             } else {
 338                 msym.module_info.classfile = fo;
 339                 msym.module_info.completer = new Symbol.Completer() {
 340                     @Override
 341                     public void complete(Symbol sym) throws CompletionFailure {
 342                         classFinder.fillIn(msym.module_info);
 343                     }
 344                     @Override
 345                     public String toString() {
 346                         return "ModuleInfoCompleter";
 347                     }
 348                 };
 349             }
 350         } catch (IOException e) {
 351             msym.kind = ERR;
 352         }
 353     }
 354 
 355     Fragment getDescription(StandardLocation l) {
 356         switch (l) {
 357             case MODULE_PATH: return Fragments.LocnModule_path;
 358             case MODULE_SOURCE_PATH: return Fragments.LocnModule_source_path;
 359             case SYSTEM_MODULES: return Fragments.LocnSystem_modules;
 360             case UPGRADE_MODULE_PATH: return Fragments.LocnUpgrade_module_path;
 361             default:
 362                 throw new AssertionError();
 363         }
 364     }
 365 
 366     public interface ModuleInfoSourceFileCompleter {
 367         public ModuleSymbol complete(JavaFileObject file);
 368     }
 369 
 370 }