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 boolean showJdkInternals;
  53     private final boolean reduced;
  54     private final PrintWriter writer;
  55     private final String separator;
  56     public ModuleExportsAnalyzer(JdepsConfiguration config,
  57                                  JdepsFilter filter,
  58                                  boolean showJdkInternals,
  59                                  boolean reduced,
  60                                  PrintWriter writer,
  61                                  String separator) {
  62         super(config, filter, null,
  63               Analyzer.Type.PACKAGE,
  64               false /* all classes */);
  65         this.showJdkInternals = showJdkInternals;
  66         this.reduced = reduced;
  67         this.writer = writer;
  68         this.separator = separator;
  69     }
  70 
  71     @Override
  72     public boolean run() throws IOException {
  73         // analyze dependences
  74         boolean rc = super.run();
  75 
  76         // A visitor to record the module-level dependences as well as
  77         // use of JDK internal APIs
  78         Analyzer.Visitor visitor = (origin, originArchive, target, targetArchive) -> {
  79             Set<String> jdkInternals =
  80                 deps.computeIfAbsent(originArchive, _k -> new HashMap<>())
  81                     .computeIfAbsent(targetArchive, _k -> new HashSet<>());
  82 
  83             Module module = targetArchive.getModule();
  84             if (showJdkInternals && originArchive.getModule() != module &&
  85                     module.isJDK() && !module.isExported(target)) {
  86                 // use of JDK internal APIs
  87                 jdkInternals.add(target);
  88             }
  89         };
  90 
  91         // visit the dependences
  92         archives.stream()
  93             .filter(analyzer::hasDependences)
  94             .sorted(Comparator.comparing(Archive::getName))
  95             .forEach(archive -> analyzer.visitDependences(archive, visitor));
  96 
  97         Set<Module> modules = modules();
  98         if (showJdkInternals) {
  99             // print modules and JDK internal API dependences
 100             printDependences(modules);
 101         } else {
 102             // print module dependences
 103             writer.println(modules.stream().map(Module::name).sorted()
 104                                   .collect(Collectors.joining(separator)));
 105         }
 106         return rc;
 107     }
 108 
 109     private Set<Module> modules() {
 110         // build module graph
 111         ModuleGraphBuilder builder = new ModuleGraphBuilder(configuration);
 112         Module root = new RootModule("root");
 113         builder.addModule(root);
 114         // find named module dependences
 115         dependenceStream()
 116             .flatMap(map -> map.keySet().stream())
 117             .filter(m -> m.getModule().isNamed()
 118                 && !configuration.rootModules().contains(m))
 119             .map(Archive::getModule)
 120             .forEach(m -> builder.addEdge(root, m));
 121 
 122         // build module dependence graph
 123         // if reduced is set, apply transition reduction
 124         Graph<Module> g = reduced ? builder.reduced() : builder.build();
 125         return g.adjacentNodes(root);
 126     }
 127 
 128     private void printDependences(Set<Module> modules) {
 129         // find use of JDK internals
 130         Map<Module, Set<String>> jdkinternals = new HashMap<>();
 131         dependenceStream()
 132             .flatMap(map -> map.entrySet().stream())
 133             .filter(e -> e.getValue().size() > 0)
 134             .forEach(e -> jdkinternals.computeIfAbsent(e.getKey().getModule(),
 135                                                        _k -> new TreeSet<>())
 136                                       .addAll(e.getValue()));
 137 
 138         // print modules and JDK internal API dependences
 139         Stream.concat(modules.stream(), jdkinternals.keySet().stream())
 140               .sorted(Comparator.comparing(Module::name))
 141               .distinct()
 142               .forEach(m -> {
 143                   if (jdkinternals.containsKey(m)) {
 144                       jdkinternals.get(m).stream()
 145                           .forEach(pn -> writer.format("   %s/%s%s", m, pn, separator));
 146                   } else {
 147                       writer.format("   %s%s", m, separator);
 148                   }
 149               });
 150     }
 151 
 152     /*
 153      * Returns a stream of dependence map from an Archive to the set of JDK
 154      * internal APIs being used.
 155      */
 156     private Stream<Map<Archive, Set<String>>> dependenceStream() {
 157         return deps.keySet().stream()
 158                    .filter(source -> !source.getModule().isNamed()
 159                             || configuration.rootModules().contains(source))
 160                    .map(deps::get);
 161     }
 162 
 163     private class RootModule extends Module {
 164         final ModuleDescriptor descriptor;
 165         RootModule(String name) {
 166             super(name);
 167 
 168             ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(name);
 169             this.descriptor = builder.build();
 170         }
 171 
 172         @Override
 173         public ModuleDescriptor descriptor() {
 174             return descriptor;
 175         }
 176     }
 177 
 178 }