1 /* 2 * Copyright (c) 2013, 2016, 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 java.lang.module; 27 28 import java.io.PrintStream; 29 import java.lang.module.ModuleDescriptor.Provides; 30 import java.lang.module.ModuleDescriptor.Requires.Modifier; 31 import java.lang.reflect.Layer; 32 import java.util.ArrayDeque; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.Deque; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.LinkedHashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Optional; 44 import java.util.Set; 45 import java.util.StringJoiner; 46 import java.util.stream.Collectors; 47 48 import jdk.internal.module.ModuleHashes; 49 import jdk.internal.module.ModuleReferenceImpl; 50 51 /** 52 * The resolver used by {@link Configuration#resolveRequires} and 53 * {@link Configuration#resolveRequiresAndUses}. 54 * 55 * @implNote The resolver is used at VM startup and so deliberately avoids 56 * using lambda and stream usages in code paths used during startup. 57 */ 58 59 final class Resolver { 60 61 private final ModuleFinder beforeFinder; 62 private final List<Configuration> parents; 63 private final ModuleFinder afterFinder; 64 private final PrintStream traceOutput; 65 66 // maps module name to module reference 67 private final Map<String, ModuleReference> nameToReference = new HashMap<>(); 68 69 70 Resolver(ModuleFinder beforeFinder, 71 List<Configuration> parents, 72 ModuleFinder afterFinder, 73 PrintStream traceOutput) { 74 this.beforeFinder = beforeFinder; 75 this.parents = parents; 76 this.afterFinder = afterFinder; 77 this.traceOutput = traceOutput; 78 } 79 80 81 /** 82 * Resolves the given named modules. 83 * 84 * @throws ResolutionException 85 */ 86 Resolver resolveRequires(Collection<String> roots) { 87 88 // create the visit stack to get us started 89 Deque<ModuleDescriptor> q = new ArrayDeque<>(); 90 for (String root : roots) { 91 92 // find root module 93 ModuleReference mref = findWithBeforeFinder(root); 94 if (mref == null) { 95 96 if (findInParent(root) != null) { 97 // in parent, nothing to do 98 continue; 99 } 100 101 mref = findWithAfterFinder(root); 102 if (mref == null) { 103 fail("Module %s not found", root); 104 } 105 } 106 107 if (isTracing()) { 108 trace("Root module %s located", root); 109 mref.location().ifPresent(uri -> trace(" (%s)", uri)); 110 } 111 112 assert mref.descriptor().name().equals(root); 113 nameToReference.put(root, mref); 114 q.push(mref.descriptor()); 115 } 116 117 resolve(q); 118 119 return this; 120 } 121 122 /** 123 * Resolve all modules in the given queue. On completion the queue will be 124 * empty and any resolved modules will be added to {@code nameToReference}. 125 * 126 * @return The set of module resolved by this invocation of resolve 127 */ 128 private Set<ModuleDescriptor> resolve(Deque<ModuleDescriptor> q) { 129 Set<ModuleDescriptor> resolved = new HashSet<>(); 130 131 while (!q.isEmpty()) { 132 ModuleDescriptor descriptor = q.poll(); 133 assert nameToReference.containsKey(descriptor.name()); 134 135 // process dependences 136 for (ModuleDescriptor.Requires requires : descriptor.requires()) { 137 138 // only required at compile-time 139 if (requires.modifiers().contains(Modifier.STATIC)) 140 continue; 141 142 String dn = requires.name(); 143 144 // find dependence 145 ModuleReference mref = findWithBeforeFinder(dn); 146 if (mref == null) { 147 148 if (findInParent(dn) != null) { 149 // dependence is in parent 150 continue; 151 } 152 153 mref = findWithAfterFinder(dn); 154 if (mref == null) { 155 fail("Module %s not found, required by %s", 156 dn, descriptor.name()); 157 } 158 } 159 160 if (!nameToReference.containsKey(dn)) { 161 nameToReference.put(dn, mref); 162 q.offer(mref.descriptor()); 163 resolved.add(mref.descriptor()); 164 165 if (isTracing()) { 166 trace("Module %s located, required by %s", 167 dn, descriptor.name()); 168 mref.location().ifPresent(uri -> trace(" (%s)", uri)); 169 } 170 } 171 172 } 173 174 resolved.add(descriptor); 175 } 176 177 return resolved; 178 } 179 180 /** 181 * Augments the set of resolved modules with modules induced by the 182 * service-use relation. 183 */ 184 Resolver resolveUses() { 185 186 // Scan the finders for all available service provider modules. As 187 // java.base uses services then then module finders will be scanned 188 // anyway. 189 Map<String, Set<ModuleReference>> availableProviders = new HashMap<>(); 190 for (ModuleReference mref : findAll()) { 191 ModuleDescriptor descriptor = mref.descriptor(); 192 if (!descriptor.provides().isEmpty()) { 193 194 for (Provides provides : descriptor.provides()) { 195 String sn = provides.service(); 196 197 // computeIfAbsent 198 Set<ModuleReference> providers = availableProviders.get(sn); 199 if (providers == null) { 200 providers = new HashSet<>(); 201 availableProviders.put(sn, providers); 202 } 203 providers.add(mref); 204 } 205 206 } 207 } 208 209 // create the visit stack 210 Deque<ModuleDescriptor> q = new ArrayDeque<>(); 211 212 // the initial set of modules that may use services 213 Set<ModuleDescriptor> initialConsumers; 214 if (Layer.boot() == null) { 215 initialConsumers = new HashSet<>(); 216 } else { 217 initialConsumers = parents.stream() 218 .flatMap(Configuration::configurations) 219 .distinct() 220 .flatMap(c -> c.descriptors().stream()) 221 .collect(Collectors.toSet()); 222 } 223 for (ModuleReference mref : nameToReference.values()) { 224 initialConsumers.add(mref.descriptor()); 225 } 226 227 // Where there is a consumer of a service then resolve all modules 228 // that provide an implementation of that service 229 Set<ModuleDescriptor> candidateConsumers = initialConsumers; 230 do { 231 for (ModuleDescriptor descriptor : candidateConsumers) { 232 if (!descriptor.uses().isEmpty()) { 233 for (String service : descriptor.uses()) { 234 Set<ModuleReference> mrefs = availableProviders.get(service); 235 if (mrefs != null) { 236 for (ModuleReference mref : mrefs) { 237 ModuleDescriptor provider = mref.descriptor(); 238 if (!provider.equals(descriptor)) { 239 240 trace("Module %s provides %s, used by %s", 241 provider.name(), service, descriptor.name()); 242 243 String pn = provider.name(); 244 if (!nameToReference.containsKey(pn)) { 245 if (isTracing()) { 246 mref.location() 247 .ifPresent(uri -> trace(" (%s)", uri)); 248 } 249 nameToReference.put(pn, mref); 250 q.push(provider); 251 } 252 } 253 } 254 } 255 } 256 } 257 } 258 259 candidateConsumers = resolve(q); 260 } while (!candidateConsumers.isEmpty()); 261 262 return this; 263 } 264 265 266 /** 267 * Execute post-resolution checks and returns the module graph of resolved 268 * modules as {@code Map}. The resolved modules will be in the given 269 * configuration. 270 * 271 * @param check {@true} to execute the post resolution checks 272 */ 273 Map<ResolvedModule, Set<ResolvedModule>> finish(Configuration cf, 274 boolean check) 275 { 276 if (isTracing()) { 277 trace("Result:"); 278 Set<String> names = nameToReference.keySet(); 279 names.stream().sorted().forEach(name -> trace(" %s", name)); 280 } 281 282 if (check) { 283 detectCycles(); 284 checkPlatformConstraints(); 285 checkHashes(); 286 } 287 288 Map<ResolvedModule, Set<ResolvedModule>> graph = makeGraph(cf); 289 290 if (check) { 291 checkExportSuppliers(graph); 292 } 293 294 return graph; 295 } 296 297 /** 298 * Checks the given module graph for cycles. 299 * 300 * For now the implementation is a simple depth first search on the 301 * dependency graph. We'll replace this later, maybe with Tarjan. 302 */ 303 private void detectCycles() { 304 visited = new HashSet<>(); 305 visitPath = new LinkedHashSet<>(); // preserve insertion order 306 for (ModuleReference mref : nameToReference.values()) { 307 visit(mref.descriptor()); 308 } 309 visited.clear(); 310 } 311 312 // the modules that were visited 313 private Set<ModuleDescriptor> visited; 314 315 // the modules in the current visit path 316 private Set<ModuleDescriptor> visitPath; 317 318 private void visit(ModuleDescriptor descriptor) { 319 if (!visited.contains(descriptor)) { 320 boolean added = visitPath.add(descriptor); 321 if (!added) { 322 throw new ResolutionException("Cycle detected: " + 323 cycleAsString(descriptor)); 324 } 325 for (ModuleDescriptor.Requires requires : descriptor.requires()) { 326 String dn = requires.name(); 327 328 ModuleReference mref = nameToReference.get(dn); 329 if (mref != null) { 330 ModuleDescriptor other = mref.descriptor(); 331 if (other != descriptor) { 332 // dependency is in this configuration 333 visit(other); 334 } 335 } 336 } 337 visitPath.remove(descriptor); 338 visited.add(descriptor); 339 } 340 } 341 342 /** 343 * Returns a String with a list of the modules in a detected cycle. 344 */ 345 private String cycleAsString(ModuleDescriptor descriptor) { 346 List<ModuleDescriptor> list = new ArrayList<>(visitPath); 347 list.add(descriptor); 348 int index = list.indexOf(descriptor); 349 return list.stream() 350 .skip(index) 351 .map(ModuleDescriptor::name) 352 .collect(Collectors.joining(" -> ")); 353 } 354 355 356 /** 357 * If there are platform specific modules then check that the OS name, 358 * architecture and version match. 359 * 360 * @apiNote This method does not currently check if the OS matches 361 * platform specific modules in parent configurations. 362 */ 363 private void checkPlatformConstraints() { 364 365 // first module encountered that is platform specific 366 String savedModuleName = null; 367 String savedOsName = null; 368 String savedOsArch = null; 369 String savedOsVersion = null; 370 371 for (ModuleReference mref : nameToReference.values()) { 372 ModuleDescriptor descriptor = mref.descriptor(); 373 374 String osName = descriptor.osName().orElse(null); 375 String osArch = descriptor.osArch().orElse(null); 376 String osVersion = descriptor.osVersion().orElse(null); 377 378 if (osName != null || osArch != null || osVersion != null) { 379 380 if (savedModuleName == null) { 381 382 savedModuleName = descriptor.name(); 383 savedOsName = osName; 384 savedOsArch = osArch; 385 savedOsVersion = osVersion; 386 387 } else { 388 389 boolean matches = platformMatches(osName, savedOsName) 390 && platformMatches(osArch, savedOsArch) 391 && platformMatches(osVersion, savedOsVersion); 392 393 if (!matches) { 394 String s1 = platformAsString(savedOsName, 395 savedOsArch, 396 savedOsVersion); 397 398 String s2 = platformAsString(osName, osArch, osVersion); 399 fail("Mismatching constraints on target platform: " 400 + savedModuleName + ": " + s1 401 + ", " + descriptor.name() + ": " + s2); 402 } 403 404 } 405 406 } 407 } 408 409 } 410 411 /** 412 * Returns true if the s1 and s2 are equal or one of them is null. 413 */ 414 private boolean platformMatches(String s1, String s2) { 415 if (s1 == null || s2 == null) 416 return true; 417 else 418 return Objects.equals(s1, s2); 419 } 420 421 /** 422 * Return a string that encodes the OS name/arch/version. 423 */ 424 private String platformAsString(String osName, 425 String osArch, 426 String osVersion) { 427 428 return new StringJoiner("-") 429 .add(Objects.toString(osName, "*")) 430 .add(Objects.toString(osArch, "*")) 431 .add(Objects.toString(osVersion, "*")) 432 .toString(); 433 434 } 435 436 /** 437 * Checks the hashes in the module descriptor to ensure that they match 438 * any recorded hashes. 439 */ 440 private void checkHashes() { 441 for (ModuleReference mref : nameToReference.values()) { 442 443 // get the recorded hashes, if any 444 if (!(mref instanceof ModuleReferenceImpl)) 445 continue; 446 ModuleHashes hashes = ((ModuleReferenceImpl)mref).recordedHashes(); 447 if (hashes == null) 448 continue; 449 450 ModuleDescriptor descriptor = mref.descriptor(); 451 String algorithm = hashes.algorithm(); 452 for (String dn : hashes.names()) { 453 ModuleReference mref2 = nameToReference.get(dn); 454 if (mref2 == null) { 455 ResolvedModule resolvedModule = findInParent(dn); 456 if (resolvedModule != null) 457 mref2 = resolvedModule.reference(); 458 } 459 if (mref2 == null) 460 continue; 461 462 if (!(mref2 instanceof ModuleReferenceImpl)) { 463 fail("Unable to compute the hash of module %s", dn); 464 } 465 466 // skip checking the hash if the module has been patched 467 ModuleReferenceImpl other = (ModuleReferenceImpl)mref2; 468 if (other != null && !other.isPatched()) { 469 byte[] recordedHash = hashes.hashFor(dn); 470 byte[] actualHash = other.computeHash(algorithm); 471 if (actualHash == null) 472 fail("Unable to compute the hash of module %s", dn); 473 if (!Arrays.equals(recordedHash, actualHash)) { 474 fail("Hash of %s (%s) differs to expected hash (%s)" + 475 " recorded in %s", dn, toHexString(actualHash), 476 toHexString(recordedHash), descriptor.name()); 477 } 478 } 479 } 480 481 } 482 } 483 484 private static String toHexString(byte[] ba) { 485 StringBuilder sb = new StringBuilder(ba.length * 2); 486 for (byte b: ba) { 487 sb.append(String.format("%02x", b & 0xff)); 488 } 489 return sb.toString(); 490 } 491 492 493 /** 494 * Computes the readability graph for the modules in the given Configuration. 495 * 496 * The readability graph is created by propagating "requires" through the 497 * "requires transitive" edges of the module dependence graph. So if the 498 * module dependence graph has m1 requires m2 && m2 requires transitive m3 499 * then the resulting readability graph will contain m1 reads m2, m1 reads m3, 500 * and m2 reads m3. 501 */ 502 private Map<ResolvedModule, Set<ResolvedModule>> makeGraph(Configuration cf) { 503 504 // initial capacity of maps to avoid resizing 505 int capacity = 1 + (4 * nameToReference.size())/ 3; 506 507 // the "reads" graph starts as a module dependence graph and 508 // is iteratively updated to be the readability graph 509 Map<ResolvedModule, Set<ResolvedModule>> g1 = new HashMap<>(capacity); 510 511 // the "requires transitive" graph, contains requires transitive edges only 512 Map<ResolvedModule, Set<ResolvedModule>> g2; 513 514 // need "requires transitive" from the modules in parent configurations 515 // as there may be selected modules that have a dependency on modules in 516 // the parent configuration. 517 if (Layer.boot() == null) { 518 g2 = new HashMap<>(capacity); 519 } else { 520 g2 = parents.stream() 521 .flatMap(Configuration::configurations) 522 .distinct() 523 .flatMap(c -> 524 c.modules().stream().flatMap(m1 -> 525 m1.descriptor().requires().stream() 526 .filter(r -> r.modifiers().contains(Modifier.TRANSITIVE)) 527 .flatMap(r -> { 528 Optional<ResolvedModule> m2 = c.findModule(r.name()); 529 assert m2.isPresent() 530 || r.modifiers().contains(Modifier.STATIC); 531 return m2.stream(); 532 }) 533 .map(m2 -> Map.entry(m1, m2)) 534 ) 535 ) 536 // stream of m1->m2 537 .collect(Collectors.groupingBy(Map.Entry::getKey, 538 HashMap::new, 539 Collectors.mapping(Map.Entry::getValue, Collectors.toSet()) 540 )); 541 } 542 543 // populate g1 and g2 with the dependences from the selected modules 544 545 Map<String, ResolvedModule> nameToResolved = new HashMap<>(capacity); 546 547 for (ModuleReference mref : nameToReference.values()) { 548 ModuleDescriptor descriptor = mref.descriptor(); 549 String name = descriptor.name(); 550 551 ResolvedModule m1 = computeIfAbsent(nameToResolved, name, cf, mref); 552 553 Set<ResolvedModule> reads = new HashSet<>(); 554 Set<ResolvedModule> requiresTransitive = new HashSet<>(); 555 556 for (ModuleDescriptor.Requires requires : descriptor.requires()) { 557 String dn = requires.name(); 558 559 ResolvedModule m2 = null; 560 ModuleReference mref2 = nameToReference.get(dn); 561 if (mref2 != null) { 562 // same configuration 563 m2 = computeIfAbsent(nameToResolved, dn, cf, mref2); 564 } else { 565 // parent configuration 566 m2 = findInParent(dn); 567 if (m2 == null) { 568 assert requires.modifiers().contains(Modifier.STATIC); 569 continue; 570 } 571 } 572 573 // m1 requires m2 => m1 reads m2 574 reads.add(m2); 575 576 // m1 requires transitive m2 577 if (requires.modifiers().contains(Modifier.TRANSITIVE)) { 578 requiresTransitive.add(m2); 579 } 580 581 } 582 583 // automatic modules read all selected modules and all modules 584 // in parent configurations 585 if (descriptor.isAutomatic()) { 586 587 // reads all selected modules 588 // `requires transitive` all selected automatic modules 589 for (ModuleReference mref2 : nameToReference.values()) { 590 ModuleDescriptor descriptor2 = mref2.descriptor(); 591 String name2 = descriptor2.name(); 592 593 if (!name.equals(name2)) { 594 ResolvedModule m2 595 = computeIfAbsent(nameToResolved, name2, cf, mref2); 596 reads.add(m2); 597 if (descriptor2.isAutomatic()) 598 requiresTransitive.add(m2); 599 } 600 } 601 602 // reads all modules in parent configurations 603 // `requires transitive` all automatic modules in parent 604 // configurations 605 for (Configuration parent : parents) { 606 parent.configurations() 607 .map(Configuration::modules) 608 .flatMap(Set::stream) 609 .forEach(m -> { 610 reads.add(m); 611 if (m.reference().descriptor().isAutomatic()) 612 requiresTransitive.add(m); 613 }); 614 } 615 } 616 617 g1.put(m1, reads); 618 g2.put(m1, requiresTransitive); 619 } 620 621 // Iteratively update g1 until there are no more requires transitive 622 // to propagate 623 boolean changed; 624 List<ResolvedModule> toAdd = new ArrayList<>(); 625 do { 626 changed = false; 627 for (Set<ResolvedModule> m1Reads : g1.values()) { 628 for (ResolvedModule m2 : m1Reads) { 629 Set<ResolvedModule> m2RequiresTransitive = g2.get(m2); 630 if (m2RequiresTransitive != null) { 631 for (ResolvedModule m3 : m2RequiresTransitive) { 632 if (!m1Reads.contains(m3)) { 633 // m1 reads m2, m2 requires transitive m3 634 // => need to add m1 reads m3 635 toAdd.add(m3); 636 } 637 } 638 } 639 } 640 if (!toAdd.isEmpty()) { 641 m1Reads.addAll(toAdd); 642 toAdd.clear(); 643 changed = true; 644 } 645 } 646 } while (changed); 647 648 return g1; 649 } 650 651 /** 652 * Equivalent to 653 * <pre>{@code 654 * map.computeIfAbsent(name, k -> new ResolvedModule(cf, mref)) 655 * </pre>} 656 */ 657 private ResolvedModule computeIfAbsent(Map<String, ResolvedModule> map, 658 String name, 659 Configuration cf, 660 ModuleReference mref) 661 { 662 ResolvedModule m = map.get(name); 663 if (m == null) { 664 m = new ResolvedModule(cf, mref); 665 map.put(name, m); 666 } 667 return m; 668 } 669 670 671 /** 672 * Checks the readability graph to ensure that no two modules export the 673 * same package to a module. This includes the case where module M has 674 * a local package P and M reads another module that exports P to M. 675 * Also checks the uses/provides of module M to ensure that it reads a 676 * module that exports the package of the service type to M. 677 */ 678 private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph) { 679 680 for (Map.Entry<ResolvedModule, Set<ResolvedModule>> e : graph.entrySet()) { 681 ModuleDescriptor descriptor1 = e.getKey().descriptor(); 682 683 // the map of packages that are local or exported to descriptor1 684 Map<String, ModuleDescriptor> packageToExporter = new HashMap<>(); 685 686 // local packages 687 Set<String> packages = descriptor1.packages(); 688 for (String pn : packages) { 689 packageToExporter.put(pn, descriptor1); 690 } 691 692 // descriptor1 reads descriptor2 693 Set<ResolvedModule> reads = e.getValue(); 694 for (ResolvedModule endpoint : reads) { 695 ModuleDescriptor descriptor2 = endpoint.descriptor(); 696 697 for (ModuleDescriptor.Exports export : descriptor2.exports()) { 698 699 if (export.isQualified()) { 700 if (!export.targets().contains(descriptor1.name())) 701 continue; 702 } 703 704 // source is exported to descriptor2 705 String source = export.source(); 706 ModuleDescriptor other 707 = packageToExporter.putIfAbsent(source, descriptor2); 708 709 if (other != null && other != descriptor2) { 710 // package might be local to descriptor1 711 if (other == descriptor1) { 712 fail("Module %s contains package %s" 713 + ", module %s exports package %s to %s", 714 descriptor1.name(), 715 source, 716 descriptor2.name(), 717 source, 718 descriptor1.name()); 719 } else { 720 fail("Modules %s and %s export package %s to module %s", 721 descriptor2.name(), 722 other.name(), 723 source, 724 descriptor1.name()); 725 } 726 727 } 728 } 729 } 730 731 // uses/provides checks not applicable to automatic modules 732 if (!descriptor1.isAutomatic()) { 733 734 // uses S 735 for (String service : descriptor1.uses()) { 736 String pn = packageName(service); 737 if (!packageToExporter.containsKey(pn)) { 738 fail("Module %s does not read a module that exports %s", 739 descriptor1.name(), pn); 740 } 741 } 742 743 // provides S 744 for (ModuleDescriptor.Provides provides : descriptor1.provides()) { 745 String pn = packageName(provides.service()); 746 if (!packageToExporter.containsKey(pn)) { 747 fail("Module %s does not read a module that exports %s", 748 descriptor1.name(), pn); 749 } 750 751 for (String provider : provides.providers()) { 752 if (!packages.contains(packageName(provider))) { 753 fail("Provider %s not in module %s", 754 provider, descriptor1.name()); 755 } 756 } 757 } 758 759 } 760 761 } 762 763 } 764 765 /** 766 * Find a module of the given name in the parent configurations 767 */ 768 private ResolvedModule findInParent(String mn) { 769 for (Configuration parent : parents) { 770 Optional<ResolvedModule> om = parent.findModule(mn); 771 if (om.isPresent()) 772 return om.get(); 773 } 774 return null; 775 } 776 777 778 /** 779 * Invokes the beforeFinder to find method to find the given module. 780 */ 781 private ModuleReference findWithBeforeFinder(String mn) { 782 try { 783 return beforeFinder.find(mn).orElse(null); 784 } catch (FindException e) { 785 // unwrap 786 throw new ResolutionException(e.getMessage(), e.getCause()); 787 } 788 } 789 790 /** 791 * Invokes the afterFinder to find method to find the given module. 792 */ 793 private ModuleReference findWithAfterFinder(String mn) { 794 try { 795 return afterFinder.find(mn).orElse(null); 796 } catch (FindException e) { 797 // unwrap 798 throw new ResolutionException(e.getMessage(), e.getCause()); 799 } 800 } 801 802 /** 803 * Returns the set of all modules that are observable with the before 804 * and after ModuleFinders. 805 */ 806 private Set<ModuleReference> findAll() { 807 try { 808 809 Set<ModuleReference> beforeModules = beforeFinder.findAll(); 810 Set<ModuleReference> afterModules = afterFinder.findAll(); 811 812 if (afterModules.isEmpty()) 813 return beforeModules; 814 815 if (beforeModules.isEmpty() 816 && parents.size() == 1 817 && parents.get(0) == Configuration.empty()) 818 return afterModules; 819 820 Set<ModuleReference> result = new HashSet<>(beforeModules); 821 for (ModuleReference mref : afterModules) { 822 String name = mref.descriptor().name(); 823 if (!beforeFinder.find(name).isPresent() 824 && findInParent(name) == null) { 825 result.add(mref); 826 } 827 } 828 829 return result; 830 831 } catch (FindException e) { 832 // unwrap 833 throw new ResolutionException(e.getMessage(), e.getCause()); 834 } 835 } 836 837 /** 838 * Returns the package name 839 */ 840 private static String packageName(String cn) { 841 int index = cn.lastIndexOf("."); 842 return (index == -1) ? "" : cn.substring(0, index); 843 } 844 845 /** 846 * Throw ResolutionException with the given format string and arguments 847 */ 848 private static void fail(String fmt, Object ... args) { 849 String msg = String.format(fmt, args); 850 throw new ResolutionException(msg); 851 } 852 853 /** 854 * Tracing support 855 */ 856 857 private boolean isTracing() { 858 return traceOutput != null; 859 } 860 861 private void trace(String fmt, Object ... args) { 862 if (traceOutput != null) { 863 traceOutput.format("[Resolver] " + fmt, args); 864 traceOutput.println(); 865 } 866 } 867 868 }