53 import java.util.regex.Pattern; |
53 import java.util.regex.Pattern; |
54 import java.util.stream.Collectors; |
54 import java.util.stream.Collectors; |
55 import java.util.zip.ZipEntry; |
55 import java.util.zip.ZipEntry; |
56 import java.util.zip.ZipFile; |
56 import java.util.zip.ZipFile; |
57 |
57 |
58 import jdk.internal.module.Checks; |
|
59 import jdk.internal.module.ConfigurableModuleFinder; |
58 import jdk.internal.module.ConfigurableModuleFinder; |
60 import jdk.internal.perf.PerfCounter; |
59 import jdk.internal.perf.PerfCounter; |
61 |
60 |
62 |
61 |
63 /** |
62 /** |
341 int index = cf.lastIndexOf("/") + 1; |
340 int index = cf.lastIndexOf("/") + 1; |
342 if (index < cf.length()) { |
341 if (index < cf.length()) { |
343 String prefix = cf.substring(0, index); |
342 String prefix = cf.substring(0, index); |
344 if (prefix.equals(SERVICES_PREFIX)) { |
343 if (prefix.equals(SERVICES_PREFIX)) { |
345 String sn = cf.substring(index); |
344 String sn = cf.substring(index); |
346 if (Checks.isJavaIdentifier(sn)) |
345 return Optional.of(sn); |
347 return Optional.of(sn); |
|
348 } |
346 } |
349 } |
347 } |
350 return Optional.empty(); |
348 return Optional.empty(); |
351 } |
349 } |
352 |
350 |
376 * 3. It has no module-private/concealed packages |
374 * 3. It has no module-private/concealed packages |
377 * 4. The contents of any META-INF/services configuration files are mapped |
375 * 4. The contents of any META-INF/services configuration files are mapped |
378 * to "provides" declarations |
376 * to "provides" declarations |
379 * 5. The Main-Class attribute in the main attributes of the JAR manifest |
377 * 5. The Main-Class attribute in the main attributes of the JAR manifest |
380 * is mapped to the module descriptor mainClass |
378 * is mapped to the module descriptor mainClass |
381 * |
|
382 * @apiNote This needs to move to somewhere where it can be used by tools, |
|
383 * maybe even a standard API if automatic modules are a Java SE feature. |
|
384 */ |
379 */ |
385 private ModuleDescriptor deriveModuleDescriptor(JarFile jf) |
380 private ModuleDescriptor deriveModuleDescriptor(JarFile jf) |
386 throws IOException |
381 throws IOException |
387 { |
382 { |
388 // Derive module name and version from JAR file name |
383 // Derive module name and version from JAR file name |
395 // drop .jar |
390 // drop .jar |
396 String mn = fn.substring(0, fn.length()-4); |
391 String mn = fn.substring(0, fn.length()-4); |
397 String vs = null; |
392 String vs = null; |
398 |
393 |
399 // find first occurrence of -${NUMBER}. or -${NUMBER}$ |
394 // find first occurrence of -${NUMBER}. or -${NUMBER}$ |
400 Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn); |
395 Matcher matcher = Patterns.DASH_VERSION.matcher(mn); |
401 if (matcher.find()) { |
396 if (matcher.find()) { |
402 int start = matcher.start(); |
397 int start = matcher.start(); |
403 |
398 |
404 // attempt to parse the tail as a version string |
399 // attempt to parse the tail as a version string |
405 try { |
400 try { |
410 |
405 |
411 mn = mn.substring(0, start); |
406 mn = mn.substring(0, start); |
412 } |
407 } |
413 |
408 |
414 // finally clean up the module name |
409 // finally clean up the module name |
415 mn = mn.replaceAll("[^A-Za-z0-9]", ".") // replace non-alphanumeric |
410 mn = cleanModuleName(mn); |
416 .replaceAll("(\\.)(\\1)+", ".") // collapse repeating dots |
|
417 .replaceAll("^\\.", "") // drop leading dots |
|
418 .replaceAll("\\.$", ""); // drop trailing dots |
|
419 |
|
420 |
411 |
421 // Builder throws IAE if module name is empty or invalid |
412 // Builder throws IAE if module name is empty or invalid |
422 ModuleDescriptor.Builder builder |
413 ModuleDescriptor.Builder builder |
423 = new ModuleDescriptor.Builder(mn, true) |
414 = new ModuleDescriptor.Builder(mn) |
424 .requires(Requires.Modifier.MANDATED, "java.base"); |
415 .automatic() |
|
416 .requires(Set.of(Requires.Modifier.MANDATED), "java.base"); |
425 if (vs != null) |
417 if (vs != null) |
426 builder.version(vs); |
418 builder.version(vs); |
427 |
419 |
428 // scan the entries in the JAR file to locate the .class and service |
420 // scan the entries in the JAR file to locate the .class and service |
429 // configuration file |
421 // configuration file |
455 try (InputStream in = jf.getInputStream(entry)) { |
447 try (InputStream in = jf.getInputStream(entry)) { |
456 BufferedReader reader |
448 BufferedReader reader |
457 = new BufferedReader(new InputStreamReader(in, "UTF-8")); |
449 = new BufferedReader(new InputStreamReader(in, "UTF-8")); |
458 String cn; |
450 String cn; |
459 while ((cn = nextLine(reader)) != null) { |
451 while ((cn = nextLine(reader)) != null) { |
460 if (Checks.isJavaIdentifier(cn)) { |
452 if (cn.length() > 0) { |
461 providerClasses.add(cn); |
453 providerClasses.add(cn); |
462 } |
454 } |
463 } |
455 } |
464 } |
456 } |
465 if (!providerClasses.isEmpty()) |
457 if (!providerClasses.isEmpty()) |
474 if (mainClass != null) |
466 if (mainClass != null) |
475 builder.mainClass(mainClass); |
467 builder.mainClass(mainClass); |
476 } |
468 } |
477 |
469 |
478 return builder.build(); |
470 return builder.build(); |
|
471 } |
|
472 |
|
473 /** |
|
474 * Patterns used to derive the module name from a JAR file name. |
|
475 */ |
|
476 private static class Patterns { |
|
477 static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))"); |
|
478 static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]"); |
|
479 static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+"); |
|
480 static final Pattern LEADING_DOTS = Pattern.compile("^\\."); |
|
481 static final Pattern TRAILING_DOTS = Pattern.compile("\\.$"); |
|
482 } |
|
483 |
|
484 /** |
|
485 * Clean up candidate module name derived from a JAR file name. |
|
486 */ |
|
487 private static String cleanModuleName(String mn) { |
|
488 // replace non-alphanumeric |
|
489 mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll("."); |
|
490 |
|
491 // collapse repeating dots |
|
492 mn = Patterns.REPEATING_DOTS.matcher(mn).replaceAll("."); |
|
493 |
|
494 // drop leading dots |
|
495 if (mn.length() > 0 && mn.charAt(0) == '.') |
|
496 mn = Patterns.LEADING_DOTS.matcher(mn).replaceAll(""); |
|
497 |
|
498 // drop trailing dots |
|
499 int len = mn.length(); |
|
500 if (len > 0 && mn.charAt(len-1) == '.') |
|
501 mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll(""); |
|
502 |
|
503 return mn; |
479 } |
504 } |
480 |
505 |
481 private Set<String> jarPackages(JarFile jf) { |
506 private Set<String> jarPackages(JarFile jf) { |
482 return jf.stream() |
507 return jf.stream() |
483 .filter(e -> e.getName().endsWith(".class")) |
508 .filter(e -> e.getName().endsWith(".class")) |