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