/* |
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package jdk.internal.module; |
|
import java.io.File; |
|
import java.io.IOException; |
|
import java.io.PrintStream; |
|
import java.lang.module.FindException; |
|
import java.lang.module.ModuleDescriptor; |
|
import java.lang.module.ModuleFinder; |
|
import java.lang.module.ModuleReference; |
|
import java.net.URI; |
|
import java.nio.file.DirectoryStream; |
|
import java.nio.file.Files; |
|
import java.nio.file.NoSuchFileException; |
|
import java.nio.file.Path; |
|
import java.nio.file.attribute.BasicFileAttributes; |
|
import java.util.Comparator; |
|
import java.util.HashMap; |
|
import java.util.Map; |
|
import java.util.Optional; |
|
import java.util.stream.Stream; |
|
/** |
|
* A validator to check for errors and conflicts between modules. |
|
*/ |
|
class ModulePathValidator { |
|
private static final String MODULE_INFO = "module-info.class"; |
|
private static final String INDENT = " "; |
|
private final Map<String, ModuleReference> nameToModule; |
|
private final Map<String, ModuleReference> packageToModule; |
|
private final PrintStream out; |
|
private int errorCount; |
|
private ModulePathValidator(PrintStream out) { |
|
this.nameToModule = new HashMap<>(); |
|
this.packageToModule = new HashMap<>(); |
|
this.out = out; |
|
} |
|
/** |
|
* Scans and the validates all modules on the module path. The module path |
|
* comprises the upgrade module path, system modules, and the application |
|
* module path. |
|
* |
|
* @param out the print stream for output messages |
|
* @return the number of errors found |
|
*/ |
|
static int scanAllModules(PrintStream out) { |
|
ModulePathValidator validator = new ModulePathValidator(out); |
|
// upgrade module path |
|
String value = System.getProperty("jdk.module.upgrade.path"); |
|
if (value != null) { |
|
Stream.of(value.split(File.pathSeparator)) |
|
.map(Path::of) |
|
.forEach(validator::scan); |
|
} |
|
// system modules |
|
ModuleFinder.ofSystem().findAll().stream() |
|
.sorted(Comparator.comparing(ModuleReference::descriptor)) |
|
.forEach(validator::process); |
|
// application module path |
|
value = System.getProperty("jdk.module.path"); |
|
if (value != null) { |
|
Stream.of(value.split(File.pathSeparator)) |
|
.map(Path::of) |
|
.forEach(validator::scan); |
|
} |
|
return validator.errorCount; |
|
} |
|
/** |
|
* Prints the module location and name. |
|
*/ |
|
private void printModule(ModuleReference mref) { |
|
mref.location() |
|
.filter(uri -> !isJrt(uri)) |
|
.ifPresent(uri -> out.print(uri + " ")); |
|
ModuleDescriptor descriptor = mref.descriptor(); |
|
out.print(descriptor.name()); |
|
if (descriptor.isAutomatic()) |
|
out.print(" automatic"); |
|
out.println(); |
|
} |
|
/** |
|
* Prints the module location and name, checks if the module is |
|
* shadowed by a previously seen module, and finally checks for |
|
* package conflicts with previously seen modules. |
|
*/ |
|
private void process(ModuleReference mref) { |
|
String name = mref.descriptor().name(); |
|
ModuleReference previous = nameToModule.putIfAbsent(name, mref); |
|
if (previous != null) { |
|
printModule(mref); |
|
out.print(INDENT + "shadowed by "); |
|
printModule(previous); |
|
} else { |
|
boolean first = true; |
|
// check for package conflicts when not shadowed |
|
for (String pkg : mref.descriptor().packages()) { |
|
previous = packageToModule.putIfAbsent(pkg, mref); |
|
if (previous != null) { |
|
if (first) { |
|
printModule(mref); |
|
first = false; |
|
errorCount++; |
|
} |
|
String mn = previous.descriptor().name(); |
|
out.println(INDENT + "contains " + pkg |
|
+ " conflicts with module " + mn); |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Scan an element on a module path. The element is a directory |
|
* of modules, an exploded module, or a JAR file. |
|
*/ |
|
private void scan(Path entry) { |
|
BasicFileAttributes attrs; |
|
try { |
|
attrs = Files.readAttributes(entry, BasicFileAttributes.class); |
|
} catch (NoSuchFileException ignore) { |
|
return; |
|
} catch (IOException ioe) { |
|
out.println(entry + " " + ioe); |
|
errorCount++; |
|
return; |
|
} |
|
String fn = entry.getFileName().toString(); |
|
if (attrs.isRegularFile() && fn.endsWith(".jar")) { |
|
// JAR file, explicit or automatic module |
|
scanModule(entry).ifPresent(this::process); |
|
} else if (attrs.isDirectory()) { |
|
Path mi = entry.resolve(MODULE_INFO); |
|
if (Files.exists(mi)) { |
|
// exploded module |
|
scanModule(entry).ifPresent(this::process); |
|
} else { |
|
// directory of modules |
|
scanDirectory(entry); |
|
} |
|
} |
|
} |
|
/** |
|
* Scan the JAR files and exploded modules in a directory. |
|
*/ |
|
private void scanDirectory(Path dir) { |
|
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { |
|
Map<String, Path> moduleToEntry = new HashMap<>(); |
|
for (Path entry : stream) { |
|
BasicFileAttributes attrs; |
|
try { |
|
attrs = Files.readAttributes(entry, BasicFileAttributes.class); |
|
} catch (IOException ioe) { |
|
out.println(entry + " " + ioe); |
|
errorCount++; |
|
continue; |
|
} |
|
ModuleReference mref = null; |
|
String fn = entry.getFileName().toString(); |
|
if (attrs.isRegularFile() && fn.endsWith(".jar")) { |
|
mref = scanModule(entry).orElse(null); |
|
} else if (attrs.isDirectory()) { |
|
Path mi = entry.resolve(MODULE_INFO); |
|
if (Files.exists(mi)) { |
|
mref = scanModule(entry).orElse(null); |
|
} |
|
} |
|
if (mref != null) { |
|
String name = mref.descriptor().name(); |
|
Path previous = moduleToEntry.putIfAbsent(name, entry); |
|
if (previous != null) { |
|
// same name as other module in the directory |
|
printModule(mref); |
|
out.println(INDENT + "contains same module as " |
|
+ previous.getFileName()); |
|
errorCount++; |
|
} else { |
|
process(mref); |
|
} |
|
} |
|
} |
|
} catch (IOException ioe) { |
|
out.println(dir + " " + ioe); |
|
errorCount++; |
|
} |
|
} |
|
/** |
|
* Scan a JAR file or exploded module. |
|
*/ |
|
private Optional<ModuleReference> scanModule(Path entry) { |
|
ModuleFinder finder = ModuleFinder.of(entry); |
|
try { |
|
return finder.findAll().stream().findFirst(); |
|
} catch (FindException e) { |
|
out.println(entry); |
|
out.println(INDENT + e.getMessage()); |
|
Throwable cause = e.getCause(); |
|
if (cause != null) { |
|
out.println(INDENT + cause); |
|
} |
|
errorCount++; |
|
return Optional.empty(); |
|
} |
|
} |
|
/** |
|
* Returns true if the given URI is a jrt URI |
|
*/ |
|
private static boolean isJrt(URI uri) { |
|
return (uri != null && uri.getScheme().equalsIgnoreCase("jrt")); |
|
} |
|
} |