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