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 }