1 /*
   2  * Copyright (c) 2015, 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.jdeps;
  26 
  27 import static com.sun.tools.jdeps.JdepsTask.*;
  28 import static com.sun.tools.jdeps.Analyzer.*;
  29 import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
  30 
  31 import java.io.IOException;
  32 import java.io.PrintWriter;
  33 import java.io.UncheckedIOException;
  34 import java.lang.module.ModuleDescriptor;
  35 import java.lang.module.ModuleDescriptor.Exports;
  36 import java.lang.module.ModuleDescriptor.Provides;
  37 import java.lang.module.ModuleDescriptor.Requires;
  38 import java.lang.module.ModuleFinder;
  39 import java.nio.file.Files;
  40 import java.nio.file.Path;
  41 import java.nio.file.Paths;
  42 import java.util.Collections;
  43 import java.util.Comparator;
  44 import java.util.HashMap;
  45 import java.util.List;
  46 import java.util.Locale;
  47 import java.util.Map;
  48 import java.util.Optional;
  49 import java.util.Set;
  50 import java.util.function.Function;
  51 import java.util.stream.Collectors;
  52 import java.util.stream.Stream;
  53 import static java.util.stream.Collectors.*;
  54 
  55 
  56 public class ModuleInfoBuilder {
  57     final JdepsConfiguration configuration;
  58     final Path outputdir;
  59     final boolean open;
  60 
  61     final DependencyFinder dependencyFinder;
  62     final Analyzer analyzer;
  63 
  64     // an input JAR file (loaded as an automatic module for analysis)
  65     // maps to a normal module to generate module-info.java
  66     final Map<Module, Module> automaticToNormalModule;
  67     public ModuleInfoBuilder(JdepsConfiguration configuration,
  68                              List<String> args,
  69                              Path outputdir,
  70                              boolean open) {
  71         this.configuration = configuration;
  72         this.outputdir = outputdir;
  73         this.open = open;
  74 
  75         this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);
  76         this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER);
  77 
  78         // add targets to modulepath if it has module-info.class
  79         List<Path> paths = args.stream()
  80             .map(fn -> Paths.get(fn))
  81             .collect(toList());
  82 
  83         // automatic module to convert to normal module
  84         this.automaticToNormalModule = ModuleFinder.of(paths.toArray(new Path[0]))
  85                 .findAll().stream()
  86                 .map(configuration::toModule)
  87                 .collect(toMap(Function.identity(), Function.identity()));
  88 
  89         Optional<Module> om = automaticToNormalModule.keySet().stream()
  90                                     .filter(m -> !m.descriptor().isAutomatic())
  91                                     .findAny();
  92         if (om.isPresent()) {
  93             throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile",
  94                                                    om.get().getPathName()));
  95         }
  96         if (automaticToNormalModule.isEmpty()) {
  97             throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args));
  98         }
  99     }
 100 
 101     public boolean run() throws IOException {
 102         try {
 103             // pass 1: find API dependencies
 104             Map<Archive, Set<Archive>> requiresTransitive = computeRequiresTransitive();
 105 
 106             // pass 2: analyze all class dependences
 107             dependencyFinder.parse(automaticModules().stream());
 108 
 109             analyzer.run(automaticModules(), dependencyFinder.locationToArchive());
 110 
 111             boolean missingDeps = false;
 112             for (Module m : automaticModules()) {
 113                 Set<Archive> apiDeps = requiresTransitive.containsKey(m)
 114                                             ? requiresTransitive.get(m)
 115                                             : Collections.emptySet();
 116 
 117                 Path file = outputdir.resolve(m.name()).resolve("module-info.java");
 118 
 119                 // computes requires and requires transitive
 120                 Module normalModule = toNormalModule(m, apiDeps);
 121                 if (normalModule != null) {
 122                     automaticToNormalModule.put(m, normalModule);
 123 
 124                     // generate module-info.java
 125                     System.out.format("writing to %s%n", file);
 126                     writeModuleInfo(file,  normalModule.descriptor());
 127                 } else {
 128                     // find missing dependences
 129                     System.out.format("Missing dependence: %s not generated%n", file);
 130                     missingDeps = true;
 131                 }
 132             }
 133 
 134             return !missingDeps;
 135         } finally {
 136             dependencyFinder.shutdown();
 137         }
 138     }
 139 
 140     boolean notFound(Archive m) {
 141         return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS;
 142     }
 143 
 144     private Module toNormalModule(Module module, Set<Archive> requiresTransitive)
 145         throws IOException
 146     {
 147         // done analysis
 148         module.close();
 149 
 150         if (analyzer.requires(module).anyMatch(this::notFound)) {
 151             // missing dependencies
 152             return null;
 153         }
 154 
 155         Map<String, Boolean> requires = new HashMap<>();
 156         requiresTransitive.stream()
 157             .map(Archive::getModule)
 158             .forEach(m -> requires.put(m.name(), Boolean.TRUE));
 159 
 160         analyzer.requires(module)
 161             .map(Archive::getModule)
 162             .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE));
 163 
 164         return module.toNormalModule(requires);
 165     }
 166 
 167     /**
 168      * Returns the stream of resulting modules
 169      */
 170     Stream<Module> modules() {
 171         return automaticToNormalModule.values().stream();
 172     }
 173 
 174     /**
 175      * Returns the stream of resulting ModuleDescriptors
 176      */
 177     public Stream<ModuleDescriptor> descriptors() {
 178         return automaticToNormalModule.entrySet().stream()
 179                     .map(Map.Entry::getValue)
 180                     .map(Module::descriptor);
 181     }
 182 
 183     void visitMissingDeps(Analyzer.Visitor visitor) {
 184         automaticModules().stream()
 185             .filter(m -> analyzer.requires(m).anyMatch(this::notFound))
 186             .forEach(m -> {
 187                 analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
 188             });
 189     }
 190 
 191     void writeModuleInfo(Path file, ModuleDescriptor md) {
 192         try {
 193             Files.createDirectories(file.getParent());
 194             try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) {
 195                 printModuleInfo(pw, md);
 196             }
 197         } catch (IOException e) {
 198             throw new UncheckedIOException(e);
 199         }
 200     }
 201 
 202     private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) {
 203         writer.format("%smodule %s {%n", open ? "open " : "", md.name());
 204 
 205         Map<String, Module> modules = configuration.getModules();
 206         // first print the JDK modules
 207         md.requires().stream()
 208           .filter(req -> !req.name().equals("java.base"))   // implicit requires
 209           .sorted(Comparator.comparing(Requires::name))
 210           .forEach(req -> writer.format("    requires %s;%n",
 211                                         toString(req.modifiers(), req.name())));
 212 
 213         if (!open) {
 214             md.exports().stream()
 215               .peek(exp -> {
 216                   if (exp.isQualified())
 217                       throw new InternalError(md.name() + " qualified exports: " + exp);
 218               })
 219               .sorted(Comparator.comparing(Exports::source))
 220               .forEach(exp -> writer.format("    exports %s;%n", exp.source()));
 221         }
 222 
 223         md.provides().stream()
 224           .sorted(Comparator.comparing(Provides::service))
 225           .map(p -> p.providers().stream()
 226                      .map(impl -> "        " + impl.replace('$', '.'))
 227                      .collect(joining(",\n",
 228                                       String.format("    provides %s with%n",
 229                                                     p.service().replace('$', '.')),
 230                                       ";")))
 231           .forEach(writer::println);
 232 
 233         writer.println("}");
 234     }
 235 
 236     private Set<Module> automaticModules() {
 237         return automaticToNormalModule.keySet();
 238     }
 239 
 240     /**
 241      * Returns a string containing the given set of modifiers and label.
 242      */
 243     private static <M> String toString(Set<M> mods, String what) {
 244         return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase(Locale.US)),
 245                               Stream.of(what)))
 246                       .collect(Collectors.joining(" "));
 247     }
 248 
 249     /**
 250      * Compute 'requires transitive' dependences by analyzing API dependencies
 251      */
 252     private Map<Archive, Set<Archive>> computeRequiresTransitive()
 253         throws IOException
 254     {
 255         // parse the input modules
 256         dependencyFinder.parseExportedAPIs(automaticModules().stream());
 257 
 258         return dependencyFinder.dependences();
 259     }
 260 }