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