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 
ModulePathValidator(PrintStream out)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      */
scanAllModules(PrintStream out)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      */
printModule(ModuleReference mref)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      */
process(ModuleReference mref)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      */
scan(Path entry)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      */
scanDirectory(Path dir)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      */
scanModule(Path entry)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      */
isJrt(URI uri)251     private static boolean isJrt(URI uri) {
252         return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));
253     }
254 }
255