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