1 /* 2 * Copyright (c) 2018, 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 jdk.internal.module; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.lang.module.FindException; 31 import java.lang.module.ModuleDescriptor; 32 import java.lang.module.ModuleFinder; 33 import java.lang.module.ModuleReference; 34 import java.net.URI; 35 import java.nio.file.DirectoryStream; 36 import java.nio.file.Files; 37 import java.nio.file.NoSuchFileException; 38 import java.nio.file.Path; 39 import java.nio.file.Paths; 40 import java.nio.file.attribute.BasicFileAttributes; 41 import java.util.Comparator; 42 import java.util.HashMap; 43 import java.util.Map; 44 import java.util.Optional; 45 46 /** 47 * A validator to check for errors and conflicts between modules. 48 */ 49 50 class ModulePathValidator { 51 private static final String MODULE_INFO = "module-info.class"; 52 private static final String INDENT = " "; 53 54 private final Map<String, ModuleReference> nameToModule; 55 private final Map<String, ModuleReference> packageToModule; 56 private final PrintStream out; 57 58 private int errorCount; 59 60 private ModulePathValidator(PrintStream out) { 61 this.nameToModule = new HashMap<>(); 62 this.packageToModule = new HashMap<>(); 63 this.out = out; 64 } 65 66 /** 67 * Scans and the validates all modules on the module path. The module path 68 * comprises the upgrade module path, system modules, and the application 69 * module path. 70 * 71 * @param out the print stream for output messages 72 * @return the number of errors found 73 */ 74 static int scanAllModules(PrintStream out) { 75 ModulePathValidator validator = new ModulePathValidator(out); 76 77 // upgrade module path 78 String value = System.getProperty("jdk.module.upgrade.path"); 79 if (value != null) { 80 Paths.pathToStrings(value) 81 .stream() 82 .map(Path::of) 83 .forEach(validator::scan); 84 } 85 86 // system modules 87 ModuleFinder.ofSystem().findAll().stream() 88 .sorted(Comparator.comparing(ModuleReference::descriptor)) 89 .forEach(validator::process); 90 91 // application module path 92 value = System.getProperty("jdk.module.path"); 93 if (value != null) { 94 Paths.pathToStrings(value) 95 .stream() 96 .map(Path::of) 97 .forEach(validator::scan); 98 } 99 100 return validator.errorCount; 101 } 102 103 /** 104 * Prints the module location and name. 105 */ 106 private void printModule(ModuleReference mref) { 107 mref.location() 108 .filter(uri -> !isJrt(uri)) 109 .ifPresent(uri -> out.print(uri + " ")); 110 ModuleDescriptor descriptor = mref.descriptor(); 111 out.print(descriptor.name()); 112 if (descriptor.isAutomatic()) 113 out.print(" automatic"); 114 out.println(); 115 } 116 117 /** 118 * Prints the module location and name, checks if the module is 119 * shadowed by a previously seen module, and finally checks for 120 * package conflicts with previously seen modules. 121 */ 122 private void process(ModuleReference mref) { 123 String name = mref.descriptor().name(); 124 ModuleReference previous = nameToModule.putIfAbsent(name, mref); 125 if (previous != null) { 126 printModule(mref); 127 out.print(INDENT + "shadowed by "); 128 printModule(previous); 129 } else { 130 boolean first = true; 131 132 // check for package conflicts when not shadowed 133 for (String pkg : mref.descriptor().packages()) { 134 previous = packageToModule.putIfAbsent(pkg, mref); 135 if (previous != null) { 136 if (first) { 137 printModule(mref); 138 first = false; 139 errorCount++; 140 } 141 String mn = previous.descriptor().name(); 142 out.println(INDENT + "contains " + pkg 143 + " conflicts with module " + mn); 144 } 145 } 146 } 147 } 148 149 /** 150 * Scan an element on a module path. The element is a directory 151 * of modules, an exploded module, or a JAR file. 152 */ 153 private void scan(Path entry) { 154 BasicFileAttributes attrs; 155 try { 156 attrs = Files.readAttributes(entry, BasicFileAttributes.class); 157 } catch (NoSuchFileException ignore) { 158 return; 159 } catch (IOException ioe) { 160 out.println(entry + " " + ioe); 161 errorCount++; 162 return; 163 } 164 165 String fn = entry.getFileName().toString(); 166 if (attrs.isRegularFile() && fn.endsWith(".jar")) { 167 // JAR file, explicit or automatic module 168 scanModule(entry).ifPresent(this::process); 169 } else if (attrs.isDirectory()) { 170 Path mi = entry.resolve(MODULE_INFO); 171 if (Files.exists(mi)) { 172 // exploded module 173 scanModule(entry).ifPresent(this::process); 174 } else { 175 // directory of modules 176 scanDirectory(entry); 177 } 178 } 179 } 180 181 /** 182 * Scan the JAR files and exploded modules in a directory. 183 */ 184 private void scanDirectory(Path dir) { 185 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 186 Map<String, Path> moduleToEntry = new HashMap<>(); 187 188 for (Path entry : stream) { 189 BasicFileAttributes attrs; 190 try { 191 attrs = Files.readAttributes(entry, BasicFileAttributes.class); 192 } catch (IOException ioe) { 193 out.println(entry + " " + ioe); 194 errorCount++; 195 continue; 196 } 197 198 ModuleReference mref = null; 199 200 String fn = entry.getFileName().toString(); 201 if (attrs.isRegularFile() && fn.endsWith(".jar")) { 202 mref = scanModule(entry).orElse(null); 203 } else if (attrs.isDirectory()) { 204 Path mi = entry.resolve(MODULE_INFO); 205 if (Files.exists(mi)) { 206 mref = scanModule(entry).orElse(null); 207 } 208 } 209 210 if (mref != null) { 211 String name = mref.descriptor().name(); 212 Path previous = moduleToEntry.putIfAbsent(name, entry); 213 if (previous != null) { 214 // same name as other module in the directory 215 printModule(mref); 216 out.println(INDENT + "contains same module as " 217 + previous.getFileName()); 218 errorCount++; 219 } else { 220 process(mref); 221 } 222 } 223 } 224 } catch (IOException ioe) { 225 out.println(dir + " " + ioe); 226 errorCount++; 227 } 228 } 229 230 /** 231 * Scan a JAR file or exploded module. 232 */ 233 private Optional<ModuleReference> scanModule(Path entry) { 234 ModuleFinder finder = ModuleFinder.of(entry); 235 try { 236 return finder.findAll().stream().findFirst(); 237 } catch (FindException e) { 238 out.println(entry); 239 out.println(INDENT + e.getMessage()); 240 Throwable cause = e.getCause(); 241 if (cause != null) { 242 out.println(INDENT + cause); 243 } 244 errorCount++; 245 return Optional.empty(); 246 } 247 } 248 249 /** 250 * Returns true if the given URI is a jrt URI 251 */ 252 private static boolean isJrt(URI uri) { 253 return (uri != null && uri.getScheme().equalsIgnoreCase("jrt")); 254 } 255 }