1 /*
   2  * Copyright (c) 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 
  26 package com.sun.tools.jdeps;
  27 
  28 import java.io.IOException;
  29 import java.io.PrintWriter;
  30 import java.lang.module.ModuleDescriptor;
  31 import java.util.Comparator;
  32 import java.util.HashMap;
  33 import java.util.HashSet;
  34 import java.util.Map;
  35 import java.util.Set;
  36 import java.util.TreeSet;
  37 import java.util.stream.Collectors;
  38 import java.util.stream.Stream;
  39 
  40 /**
  41  * Analyze module dependences and any reference to JDK internal APIs.
  42  * It can apply transition reduction on the resulting module graph.
  43  *
  44  * The result prints one line per module it depends on
  45  * one line per JDK internal API package it references:
  46  *     $MODULE[/$PACKAGE]
  47  *
  48  */
  49 public class ModuleExportsAnalyzer extends DepsAnalyzer {
  50     // source archive to its dependences and JDK internal APIs it references
  51     private final Map<Archive, Map<Archive,Set<String>>> deps = new HashMap<>();
  52     private final Map<String, Set<String>> missingDeps = new HashMap<>();
  53     private final boolean showInternals;
  54     private final boolean reduced;
  55     private final PrintWriter writer;
  56     private final String separator;
  57     public ModuleExportsAnalyzer(JdepsConfiguration config,
  58                                  JdepsFilter filter,
  59                                  boolean showInternals,
  60                                  boolean reduced,
  61                                  PrintWriter writer,
  62                                  String separator) {
  63         super(config, filter, null,
  64               Analyzer.Type.PACKAGE,
  65               false /* all classes */);
  66         this.showInternals = showInternals;
  67         this.reduced = reduced;
  68         this.writer = writer;
  69         this.separator = separator;
  70     }
  71 
  72     public boolean run(int maxDepth, boolean ignoreMissingDeps) throws IOException {
  73         // use compile time view so that the entire archive on classpath is analyzed
  74         boolean rc = super.run(true, maxDepth);
  75 
  76         // A visitor to record the module-level dependences as well as
  77         // use of internal APIs
  78         Analyzer.Visitor visitor = (origin, originArchive, target, targetArchive) -> {
  79             Set<String> internals =
  80                 deps.computeIfAbsent(originArchive, _k -> new HashMap<>())
  81                     .computeIfAbsent(targetArchive, _k -> new HashSet<>());
  82 
  83             Module module = targetArchive.getModule();
  84             if (showInternals && originArchive.getModule() != module &&
  85                     module.isNamed() && !module.isExported(target, module.name())) {
  86                 // use of internal APIs
  87                 internals.add(target);
  88             }
  89             if (!ignoreMissingDeps && Analyzer.notFound(targetArchive)) {
  90                 Set<String> notFound =
  91                     missingDeps.computeIfAbsent(origin, _k -> new HashSet<>());
  92                 notFound.add(target);
  93             }
  94         };
  95 
  96         // visit the dependences
  97         archives.stream()
  98             .filter(analyzer::hasDependences)
  99             .sorted(Comparator.comparing(Archive::getName))
 100             .forEach(archive -> analyzer.visitDependences(archive, visitor));
 101 
 102         // error if any missing dependence
 103         if (!rc || !missingDeps.isEmpty()) {
 104             return false;
 105         }
 106 
 107         Map<Module, Set<String>> internalPkgs = internalPackages();
 108         Set<Module> modules = modules();
 109         if (showInternals) {
 110             // print modules and JDK internal API dependences
 111             Stream.concat(modules.stream(), internalPkgs.keySet().stream())
 112                     .sorted(Comparator.comparing(Module::name))
 113                     .distinct()
 114                     .forEach(m -> {
 115                         if (internalPkgs.containsKey(m)) {
 116                             internalPkgs.get(m).stream()
 117                                 .forEach(pn -> writer.format("   %s/%s%s", m, pn, separator));
 118                         } else {
 119                             writer.format("   %s%s", m, separator);
 120                         }
 121                     });
 122         } else {
 123             // print module dependences
 124             writer.println(modules.stream().map(Module::name).sorted()
 125                                   .collect(Collectors.joining(separator)));
 126         }
 127         return rc;
 128     }
 129 
 130     /*
 131      * Prints missing dependences
 132      */
 133     void visitMissingDeps(Analyzer.Visitor visitor) {
 134         archives.stream()
 135             .filter(analyzer::hasDependences)
 136             .sorted(Comparator.comparing(Archive::getName))
 137             .filter(m -> analyzer.requires(m).anyMatch(Analyzer::notFound))
 138             .forEach(m -> {
 139                 analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
 140             });
 141     }
 142 
 143     private Set<Module> modules() {
 144         // build module graph
 145         ModuleGraphBuilder builder = new ModuleGraphBuilder(configuration);
 146         Module root = new RootModule();
 147         builder.addModule(root);
 148         // find named module dependences
 149         dependenceStream()
 150             .flatMap(map -> map.keySet().stream())
 151             .filter(m -> m.getModule().isNamed() && !configuration.rootModules().contains(m))
 152             .map(Archive::getModule)
 153             .forEach(m -> builder.addEdge(root, m));
 154 
 155         // build module dependence graph
 156         // if reduced is set, apply transition reduction
 157         Graph<Module> g = reduced ? builder.reduced() : builder.build();
 158         return g.adjacentNodes(root);
 159     }
 160 
 161     private Map<Module, Set<String>> internalPackages() {
 162         Map<Module, Set<String>> internalPkgs = new HashMap<>();
 163         dependenceStream()
 164             .flatMap(map -> map.entrySet().stream())
 165             .filter(e -> e.getValue().size() > 0)
 166             .forEach(e -> internalPkgs.computeIfAbsent(e.getKey().getModule(),
 167                                                              _k -> new TreeSet<>())
 168                                       .addAll(e.getValue()));
 169         return internalPkgs;
 170     }
 171 
 172     /*
 173      * Returns a stream of dependence map from an Archive to the set of JDK
 174      * internal APIs being used.
 175      */
 176     private Stream<Map<Archive, Set<String>>> dependenceStream() {
 177         return deps.keySet().stream()
 178                    .filter(source -> !source.getModule().isNamed()
 179                             || configuration.rootModules().contains(source))
 180                    .map(deps::get);
 181     }
 182 
 183     /*
 184      * RootModule serves as the root node for building the module graph
 185      */
 186     private static class RootModule extends Module {
 187         static final String NAME = "root";
 188         RootModule() {
 189             super(NAME, ModuleDescriptor.newModule(NAME).build(), false);
 190         }
 191     }
 192 
 193 }