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