1 /*
2 * Copyright (c) 2014, 2022, 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.BufferedInputStream;
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.UncheckedIOException;
35 import java.lang.module.FindException;
36 import java.lang.module.InvalidModuleDescriptorException;
37 import java.lang.module.ModuleDescriptor;
38 import java.lang.module.ModuleDescriptor.Builder;
39 import java.lang.module.ModuleFinder;
40 import java.lang.module.ModuleReference;
41 import java.net.URI;
42 import java.nio.file.DirectoryStream;
43 import java.nio.file.Files;
44 import java.nio.file.NoSuchFileException;
45 import java.nio.file.Path;
46 import java.nio.file.attribute.BasicFileAttributes;
47 import java.util.ArrayList;
48 import java.util.HashMap;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.Optional;
53 import java.util.Set;
54 import java.util.jar.Attributes;
55 import java.util.jar.JarEntry;
56 import java.util.jar.JarFile;
57 import java.util.jar.Manifest;
58 import java.util.regex.Matcher;
59 import java.util.regex.Pattern;
60 import java.util.stream.Collectors;
61 import java.util.stream.Stream;
62 import java.util.zip.ZipException;
63 import java.util.zip.ZipFile;
64
65 import sun.nio.cs.UTF_8;
66
67 import jdk.internal.jmod.JmodFile;
68 import jdk.internal.jmod.JmodFile.Section;
69 import jdk.internal.perf.PerfCounter;
70
71 /**
72 * A {@code ModuleFinder} that locates modules on the file system by searching
73 * a sequence of directories or packaged modules. The ModuleFinder can be
74 * created to work in either the run-time or link-time phases. In both cases it
75 * locates modular JAR and exploded modules. When created for link-time then it
76 * additionally locates modules in JMOD files. The ModuleFinder can also
77 * optionally patch any modules that it locates with a ModulePatcher.
78 */
79
80 public class ModulePath implements ModuleFinder {
81 private static final String MODULE_INFO = "module-info.class";
82
83 // the version to use for multi-release modular JARs
84 private final Runtime.Version releaseVersion;
85
86 // true for the link phase (supports modules packaged in JMOD format)
87 private final boolean isLinkPhase;
88
89 // for patching modules, can be null
90 private final ModulePatcher patcher;
91
92 // the entries on this module path
93 private final Path[] entries;
94 private int next;
95
96 // map of module name to module reference map for modules already located
97 private final Map<String, ModuleReference> cachedModules = new HashMap<>();
98
99
100 private ModulePath(Runtime.Version version,
101 boolean isLinkPhase,
102 ModulePatcher patcher,
103 Path... entries) {
104 this.releaseVersion = version;
105 this.isLinkPhase = isLinkPhase;
106 this.patcher = patcher;
107 this.entries = entries.clone();
108 for (Path entry : this.entries) {
109 Objects.requireNonNull(entry);
110 }
111 }
112
113 /**
114 * Returns a ModuleFinder that locates modules on the file system by
115 * searching a sequence of directories and/or packaged modules. The modules
116 * may be patched by the given ModulePatcher.
117 */
118 public static ModuleFinder of(ModulePatcher patcher, Path... entries) {
119 return new ModulePath(JarFile.runtimeVersion(), false, patcher, entries);
120 }
121
122 /**
123 * Returns a ModuleFinder that locates modules on the file system by
124 * searching a sequence of directories and/or packaged modules.
125 */
126 public static ModuleFinder of(Path... entries) {
127 return of((ModulePatcher)null, entries);
128 }
129
130 /**
131 * Returns a ModuleFinder that locates modules on the file system by
132 * searching a sequence of directories and/or packaged modules.
133 *
134 * @param version The release version to use for multi-release JAR files
135 * @param isLinkPhase {@code true} if the link phase to locate JMOD files
136 */
137 public static ModuleFinder of(Runtime.Version version,
138 boolean isLinkPhase,
139 Path... entries) {
140 return new ModulePath(version, isLinkPhase, null, entries);
141 }
142
143
144 @Override
145 public Optional<ModuleReference> find(String name) {
146 Objects.requireNonNull(name);
147
148 // try cached modules
149 ModuleReference m = cachedModules.get(name);
150 if (m != null)
151 return Optional.of(m);
152
153 // the module may not have been encountered yet
154 while (hasNextEntry()) {
155 scanNextEntry();
156 m = cachedModules.get(name);
157 if (m != null)
158 return Optional.of(m);
159 }
160 return Optional.empty();
161 }
162
163 @Override
164 public Set<ModuleReference> findAll() {
165 // need to ensure that all entries have been scanned
166 while (hasNextEntry()) {
167 scanNextEntry();
168 }
169 return cachedModules.values().stream().collect(Collectors.toSet());
170 }
171
172 /**
173 * Returns {@code true} if there are additional entries to scan
174 */
175 private boolean hasNextEntry() {
176 return next < entries.length;
177 }
178
179 /**
180 * Scans the next entry on the module path. A no-op if all entries have
181 * already been scanned.
182 *
183 * @throws FindException if an error occurs scanning the next entry
184 */
185 private void scanNextEntry() {
186 if (hasNextEntry()) {
187
188 long t0 = System.nanoTime();
189
190 Path entry = entries[next];
191 Map<String, ModuleReference> modules = scan(entry);
192 next++;
193
194 // update cache, ignoring duplicates
195 int initialSize = cachedModules.size();
196 for (Map.Entry<String, ModuleReference> e : modules.entrySet()) {
197 cachedModules.putIfAbsent(e.getKey(), e.getValue());
198 }
199
200 // update counters
201 int added = cachedModules.size() - initialSize;
202 moduleCount.add(added);
203
204 scanTime.addElapsedTimeFrom(t0);
205 }
206 }
207
208
209 /**
210 * Scan the given module path entry. If the entry is a directory then it is
211 * a directory of modules or an exploded module. If the entry is a regular
212 * file then it is assumed to be a packaged module.
213 *
214 * @throws FindException if an error occurs scanning the entry
215 */
216 private Map<String, ModuleReference> scan(Path entry) {
217
218 BasicFileAttributes attrs;
219 try {
220 attrs = Files.readAttributes(entry, BasicFileAttributes.class);
221 } catch (NoSuchFileException e) {
222 return Map.of();
223 } catch (IOException ioe) {
224 throw new FindException(ioe);
225 }
226
227 try {
228
229 if (attrs.isDirectory()) {
230 Path mi = entry.resolve(MODULE_INFO);
231 if (!Files.exists(mi)) {
232 // assume a directory of modules
233 return scanDirectory(entry);
234 }
235 }
236
237 // packaged or exploded module
238 ModuleReference mref = readModule(entry, attrs);
239 if (mref != null) {
240 String name = mref.descriptor().name();
241 return Map.of(name, mref);
242 }
243
244 // not recognized
245 String msg;
246 if (!isLinkPhase && entry.toString().endsWith(".jmod")) {
247 msg = "JMOD format not supported at execution time";
248 } else {
249 msg = "Module format not recognized";
250 }
251 throw new FindException(msg + ": " + entry);
252
253 } catch (IOException ioe) {
254 throw new FindException(ioe);
255 }
256 }
257
258
259 /**
260 * Scans the given directory for packaged or exploded modules.
261 *
262 * @return a map of module name to ModuleReference for the modules found
263 * in the directory
264 *
265 * @throws IOException if an I/O error occurs
266 * @throws FindException if an error occurs scanning the entry or the
267 * directory contains two or more modules with the same name
268 */
269 private Map<String, ModuleReference> scanDirectory(Path dir)
270 throws IOException
271 {
272 // The map of name -> mref of modules found in this directory.
273 Map<String, ModuleReference> nameToReference = new HashMap<>();
274
275 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
276 for (Path entry : stream) {
277 BasicFileAttributes attrs;
278 try {
279 attrs = Files.readAttributes(entry, BasicFileAttributes.class);
280 } catch (NoSuchFileException ignore) {
281 // file has been removed or moved, ignore for now
282 continue;
283 }
284
285 ModuleReference mref = readModule(entry, attrs);
286
287 // module found
288 if (mref != null) {
289 // can have at most one version of a module in the directory
290 String name = mref.descriptor().name();
291 ModuleReference previous = nameToReference.put(name, mref);
292 if (previous != null) {
293 String fn1 = fileName(mref);
294 String fn2 = fileName(previous);
295 throw new FindException("Two versions of module "
296 + name + " found in " + dir
297 + " (" + fn1 + " and " + fn2 + ")");
298 }
299 }
300 }
301 }
302
303 return nameToReference;
304 }
305
306
307 /**
308 * Reads a packaged or exploded module, returning a {@code ModuleReference}
309 * to the module. Returns {@code null} if the entry is not recognized.
310 *
311 * @throws IOException if an I/O error occurs
312 * @throws FindException if an error occurs parsing its module descriptor
313 */
314 private ModuleReference readModule(Path entry, BasicFileAttributes attrs)
315 throws IOException
316 {
317 try {
318
319 // exploded module
320 if (attrs.isDirectory()) {
321 return readExplodedModule(entry); // may return null
322 }
323
324 // JAR or JMOD file
325 if (attrs.isRegularFile()) {
326 String fn = entry.getFileName().toString();
327 boolean isDefaultFileSystem = isDefaultFileSystem(entry);
328
329 // JAR file
330 if (fn.endsWith(".jar")) {
331 if (isDefaultFileSystem) {
332 return readJar(entry);
333 } else {
334 // the JAR file is in a custom file system so
335 // need to copy it to the local file system
336 Path tmpdir = Files.createTempDirectory("mlib");
337 Path target = Files.copy(entry, tmpdir.resolve(fn));
338 return readJar(target);
339 }
340 }
341
342 // JMOD file
343 if (isDefaultFileSystem && isLinkPhase && fn.endsWith(".jmod")) {
344 return readJMod(entry);
345 }
346 }
347
348 return null;
349
350 } catch (InvalidModuleDescriptorException e) {
351 throw new FindException("Error reading module: " + entry, e);
352 }
353 }
354
355 /**
356 * Returns a string with the file name of the module if possible.
357 * If the module location is not a file URI then return the URI
358 * as a string.
359 */
360 private String fileName(ModuleReference mref) {
361 URI uri = mref.location().orElse(null);
362 if (uri != null) {
363 if (uri.getScheme().equalsIgnoreCase("file")) {
364 Path file = Path.of(uri);
365 return file.getFileName().toString();
366 } else {
367 return uri.toString();
368 }
369 } else {
370 return "<unknown>";
371 }
372 }
373
374 // -- JMOD files --
375
376 private Set<String> jmodPackages(JmodFile jf) {
377 return jf.stream()
378 .filter(e -> e.section() == Section.CLASSES)
379 .map(JmodFile.Entry::name)
380 .map(this::toPackageName)
381 .flatMap(Optional::stream)
382 .collect(Collectors.toSet());
383 }
384
385 /**
386 * Returns a {@code ModuleReference} to a module in JMOD file on the
387 * file system.
388 *
389 * @throws IOException
390 * @throws InvalidModuleDescriptorException
391 */
392 private ModuleReference readJMod(Path file) throws IOException {
393 try (JmodFile jf = new JmodFile(file)) {
394 ModuleInfo.Attributes attrs;
395 try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
396 attrs = ModuleInfo.read(in, () -> jmodPackages(jf));
397 }
398 return ModuleReferences.newJModModule(attrs, file);
399 }
400 }
401
402
403 // -- JAR files --
404
405 private static final String SERVICES_PREFIX = "META-INF/services/";
406
407 private static final Attributes.Name AUTOMATIC_MODULE_NAME
408 = new Attributes.Name("Automatic-Module-Name");
409
410 /**
411 * Returns the service type corresponding to the name of a services
412 * configuration file if it is a legal type name.
413 *
414 * For example, if called with "META-INF/services/p.S" then this method
415 * returns a container with the value "p.S".
416 */
417 private Optional<String> toServiceName(String cf) {
418 assert cf.startsWith(SERVICES_PREFIX);
419 int index = cf.lastIndexOf("/") + 1;
420 if (index < cf.length()) {
421 String prefix = cf.substring(0, index);
422 if (prefix.equals(SERVICES_PREFIX)) {
423 String sn = cf.substring(index);
424 if (Checks.isClassName(sn))
425 return Optional.of(sn);
426 }
427 }
428 return Optional.empty();
429 }
430
431 /**
432 * Reads the next line from the given reader and trims it of comments and
433 * leading/trailing white space.
434 *
435 * Returns null if the reader is at EOF.
436 */
437 private String nextLine(BufferedReader reader) throws IOException {
438 String ln = reader.readLine();
439 if (ln != null) {
440 int ci = ln.indexOf('#');
441 if (ci >= 0)
442 ln = ln.substring(0, ci);
443 ln = ln.trim();
444 }
445 return ln;
446 }
447
448 /**
449 * Treat the given JAR file as a module as follows:
450 *
451 * 1. The value of the Automatic-Module-Name attribute is the module name
452 * 2. The version, and the module name when the Automatic-Module-Name
453 * attribute is not present, is derived from the file ame of the JAR file
454 * 3. All packages are derived from the .class files in the JAR file
455 * 4. The contents of any META-INF/services configuration files are mapped
456 * to "provides" declarations
457 * 5. The Main-Class attribute in the main attributes of the JAR manifest
458 * is mapped to the module descriptor mainClass if possible
459 */
460 private ModuleDescriptor deriveModuleDescriptor(JarFile jf)
461 throws IOException
462 {
463 // Read Automatic-Module-Name attribute if present
464 Manifest man = jf.getManifest();
465 Attributes attrs = null;
466 String moduleName = null;
467 if (man != null) {
468 attrs = man.getMainAttributes();
469 if (attrs != null) {
470 moduleName = attrs.getValue(AUTOMATIC_MODULE_NAME);
471 }
472 }
473
474 // Derive the version, and the module name if needed, from JAR file name
475 String fn = jf.getName();
476 int i = fn.lastIndexOf(File.separator);
477 if (i != -1)
478 fn = fn.substring(i + 1);
479
480 // drop ".jar"
481 String name = fn.substring(0, fn.length() - 4);
482 String vs = null;
483
484 // find first occurrence of -${NUMBER}. or -${NUMBER}$
485 Matcher matcher = Patterns.DASH_VERSION.matcher(name);
486 if (matcher.find()) {
487 int start = matcher.start();
488
489 // attempt to parse the tail as a version string
490 try {
491 String tail = name.substring(start + 1);
492 ModuleDescriptor.Version.parse(tail);
493 vs = tail;
494 } catch (IllegalArgumentException ignore) { }
495
496 name = name.substring(0, start);
497 }
498
499 // Create builder, using the name derived from file name when
500 // Automatic-Module-Name not present
501 Builder builder;
502 if (moduleName != null) {
503 try {
504 builder = ModuleDescriptor.newAutomaticModule(moduleName);
505 } catch (IllegalArgumentException e) {
506 throw new FindException(AUTOMATIC_MODULE_NAME + ": " + e.getMessage());
507 }
508 } else {
509 builder = ModuleDescriptor.newAutomaticModule(cleanModuleName(name));
510 }
511
512 // module version if present
513 if (vs != null)
514 builder.version(vs);
515
516 // scan the names of the entries in the JAR file
517 Map<Boolean, Set<String>> map = jf.versionedStream()
518 .filter(e -> !e.isDirectory())
519 .map(JarEntry::getName)
520 .filter(e -> (e.endsWith(".class") ^ e.startsWith(SERVICES_PREFIX)))
521 .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
522 Collectors.toSet()));
523
524 Set<String> classFiles = map.get(Boolean.FALSE);
525 Set<String> configFiles = map.get(Boolean.TRUE);
526
527 // the packages containing class files
528 Set<String> packages = classFiles.stream()
529 .map(this::toPackageName)
530 .flatMap(Optional::stream)
531 .collect(Collectors.toSet());
532
533 // all packages are exported and open
534 builder.packages(packages);
535
536 // map names of service configuration files to service names
537 Set<String> serviceNames = configFiles.stream()
538 .map(this::toServiceName)
539 .flatMap(Optional::stream)
540 .collect(Collectors.toSet());
541
542 // parse each service configuration file
543 for (String sn : serviceNames) {
544 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
545 List<String> providerClasses = new ArrayList<>();
546 try (InputStream in = jf.getInputStream(entry)) {
547 BufferedReader reader
548 = new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE));
549 String cn;
550 while ((cn = nextLine(reader)) != null) {
551 if (!cn.isEmpty()) {
552 String pn = packageName(cn);
553 if (!packages.contains(pn)) {
554 String msg = "Provider class " + cn + " not in JAR file " + fn;
555 throw new InvalidModuleDescriptorException(msg);
556 }
557 providerClasses.add(cn);
558 }
559 }
560 }
561 if (!providerClasses.isEmpty())
562 builder.provides(sn, providerClasses);
563 }
564
565 // Main-Class attribute if it exists
566 if (attrs != null) {
567 String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
568 if (mainClass != null) {
569 mainClass = mainClass.replace('/', '.');
570 if (Checks.isClassName(mainClass)) {
571 String pn = packageName(mainClass);
572 if (packages.contains(pn)) {
573 builder.mainClass(mainClass);
574 }
575 }
576 }
577 }
578
579 return builder.build();
580 }
581
582 /**
583 * Patterns used to derive the module name from a JAR file name.
584 */
585 private static class Patterns {
586 static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
587 static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
588 static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
589 static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
590 static final Pattern TRAILING_DOTS = Pattern.compile("\\.$");
591 }
592
593 /**
594 * Clean up candidate module name derived from a JAR file name.
595 */
596 private static String cleanModuleName(String mn) {
597 // replace non-alphanumeric
598 mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll(".");
599
600 // collapse repeating dots
601 mn = Patterns.REPEATING_DOTS.matcher(mn).replaceAll(".");
602
603 // drop leading dots
604 if (!mn.isEmpty() && mn.charAt(0) == '.')
605 mn = Patterns.LEADING_DOTS.matcher(mn).replaceAll("");
606
607 // drop trailing dots
608 int len = mn.length();
609 if (len > 0 && mn.charAt(len-1) == '.')
610 mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll("");
611
612 return mn;
613 }
614
615 private Set<String> jarPackages(JarFile jf) {
616 return jf.versionedStream()
617 .filter(e -> !e.isDirectory())
618 .map(JarEntry::getName)
619 .map(this::toPackageName)
620 .flatMap(Optional::stream)
621 .collect(Collectors.toSet());
622 }
623
624 /**
625 * Returns a {@code ModuleReference} to a module in modular JAR file on
626 * the file system.
627 *
628 * @throws IOException
629 * @throws FindException
630 * @throws InvalidModuleDescriptorException
631 */
632 private ModuleReference readJar(Path file) throws IOException {
633 try (JarFile jf = new JarFile(file.toFile(),
634 true, // verify
635 ZipFile.OPEN_READ,
636 releaseVersion))
637 {
638 ModuleInfo.Attributes attrs;
639 JarEntry entry = jf.getJarEntry(MODULE_INFO);
640 if (entry == null) {
641
642 // no module-info.class so treat it as automatic module
643 try {
644 ModuleDescriptor md = deriveModuleDescriptor(jf);
645 attrs = new ModuleInfo.Attributes(md, null, null, null);
646 } catch (RuntimeException e) {
647 throw new FindException("Unable to derive module descriptor for "
648 + jf.getName(), e);
649 }
650
651 } else {
652 attrs = ModuleInfo.read(jf.getInputStream(entry),
653 () -> jarPackages(jf));
654 }
655
656 return ModuleReferences.newJarModule(attrs, patcher, file);
657 } catch (ZipException e) {
658 throw new FindException("Error reading " + file, e);
659 }
660 }
661
662
663 // -- exploded directories --
664
665 private Set<String> explodedPackages(Path dir) {
666 String separator = dir.getFileSystem().getSeparator();
667 try (Stream<Path> stream = Files.find(dir, Integer.MAX_VALUE,
668 (path, attrs) -> attrs.isRegularFile() && !isHidden(path))) {
669 return stream.map(dir::relativize)
670 .map(path -> toPackageName(path, separator))
671 .flatMap(Optional::stream)
672 .collect(Collectors.toSet());
673 } catch (IOException x) {
674 throw new UncheckedIOException(x);
675 }
676 }
677
678 /**
679 * Returns a {@code ModuleReference} to an exploded module on the file
680 * system or {@code null} if {@code module-info.class} not found.
681 *
682 * @throws IOException
683 * @throws InvalidModuleDescriptorException
684 */
685 private ModuleReference readExplodedModule(Path dir) throws IOException {
686 Path mi = dir.resolve(MODULE_INFO);
687 ModuleInfo.Attributes attrs;
688 try (InputStream in = Files.newInputStream(mi)) {
689 attrs = ModuleInfo.read(new BufferedInputStream(in),
690 () -> explodedPackages(dir));
691 } catch (NoSuchFileException e) {
692 // for now
693 return null;
694 }
695 return ModuleReferences.newExplodedModule(attrs, patcher, dir);
696 }
697
698 /**
699 * Maps a type name to its package name.
700 */
701 private static String packageName(String cn) {
702 int index = cn.lastIndexOf('.');
703 return (index == -1) ? "" : cn.substring(0, index);
704 }
705
706 /**
707 * Maps the name of an entry in a JAR or ZIP file to a package name.
708 *
709 * @throws InvalidModuleDescriptorException if the name is a class file in
710 * the top-level directory of the JAR/ZIP file (and it's not
711 * module-info.class)
712 */
713 private Optional<String> toPackageName(String name) {
714 assert !name.endsWith("/");
715 int index = name.lastIndexOf("/");
716 if (index == -1) {
717 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
718 String msg = name + " found in top-level directory"
719 + " (unnamed package not allowed in module)";
720 throw new InvalidModuleDescriptorException(msg);
721 }
722 return Optional.empty();
723 }
724
725 String pn = name.substring(0, index).replace('/', '.');
726 if (Checks.isPackageName(pn)) {
727 return Optional.of(pn);
728 } else {
729 // not a valid package name
730 return Optional.empty();
731 }
732 }
733
734 /**
735 * Maps the relative path of an entry in an exploded module to a package
736 * name.
737 *
738 * @throws InvalidModuleDescriptorException if the name is a class file in
739 * the top-level directory (and it's not module-info.class)
740 */
741 private Optional<String> toPackageName(Path file, String separator) {
742 assert file.getRoot() == null;
743
744 Path parent = file.getParent();
745 if (parent == null) {
746 String name = file.toString();
747 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
748 String msg = name + " found in top-level directory"
749 + " (unnamed package not allowed in module)";
750 throw new InvalidModuleDescriptorException(msg);
751 }
752 return Optional.empty();
753 }
754
755 String pn = parent.toString().replace(separator, ".");
756 if (Checks.isPackageName(pn)) {
757 return Optional.of(pn);
758 } else {
759 // not a valid package name
760 return Optional.empty();
761 }
762 }
763
764 /**
765 * Returns true if the given file exists and is a hidden file
766 */
767 private boolean isHidden(Path file) {
768 try {
769 return Files.isHidden(file);
770 } catch (IOException ioe) {
771 return false;
772 }
773 }
774
775
776 /**
777 * Return true if a path locates a path in the default file system
778 */
779 private boolean isDefaultFileSystem(Path path) {
780 return path.getFileSystem().provider()
781 .getScheme().equalsIgnoreCase("file");
782 }
783
784
785 private static final PerfCounter scanTime
786 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime");
787 private static final PerfCounter moduleCount
788 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules");
789 }