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