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