Skip to content

Commit 1d2eb2f

Browse files
Alan BatemanAlex Buckley
Alan Bateman
and
Alex Buckley
committedJan 25, 2025
8299504: Resolve uses and provides at run time if the service is optional and missing
Co-authored-by: Alan Bateman <alanb@openjdk.org> Co-authored-by: Alex Buckley <abuckley@openjdk.org> Reviewed-by: sundar
1 parent f446cef commit 1d2eb2f

File tree

4 files changed

+530
-302
lines changed

4 files changed

+530
-302
lines changed
 

‎src/java.base/share/classes/java/lang/module/Configuration.java

+67-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -75,6 +75,61 @@
7575
* ModuleLayer.boot().configuration()}. The configuration for the boot layer
7676
* will often be the parent when creating new configurations. </p>
7777
*
78+
* <h2><a id="optional-services">Optional Services</a></h2>
79+
*
80+
* Resolution requires that if a module {@code M} '{@code uses}' a service or
81+
* '{@code provides}' an implementation of a service, then the service must be available
82+
* to {@code M} at run time, either because {@code M} itself contains the service's
83+
* package or because {@code M} reads another module that exports the service's package.
84+
* However, it is sometimes desirable for the service's package to come from a module
85+
* that is optional at run time, as indicated by the use of 'requires static' in this
86+
* example:
87+
*
88+
* {@snippet :
89+
* module M {
90+
* requires static Y;
91+
* uses p.S;
92+
* }
93+
*
94+
* module Y {
95+
* exports p;
96+
* }
97+
* }
98+
*
99+
* Resolution is resilient when a service's package comes from a module that is optional
100+
* at run time. That is, if a module {@code M} has an optional dependency on some module
101+
* {@code Y}, but {@code Y} is not needed at run time ({@code Y} might be observable but
102+
* no-one reads it), then resolution at run time <i>assumes</i> that {@code Y} exported
103+
* the service's package at compile time. Resolution at run time does not attempt to
104+
* check whether {@code Y} is observable or (if it is observable) whether {@code Y}
105+
* exports the service's package.
106+
*
107+
* <p> The module that '{@code uses}' the service, or '{@code provides}' an implementation
108+
* of it, may depend directly on the optional module, as {@code M} does above, or may
109+
* depend indirectly on the optional module, as shown here:
110+
*
111+
* {@snippet :
112+
* module M {
113+
* requires X;
114+
* uses p.S;
115+
* }
116+
*
117+
* module X {
118+
* requires static transitive Y;
119+
* }
120+
*
121+
* module Y {
122+
* exports p;
123+
* }
124+
* }
125+
*
126+
* In effect, the service that {@code M} '{@code uses}', or '{@code provides}' an
127+
* implementation of, is optional if it comes from an optional dependency. In this case,
128+
* code in {@code M} must be prepared to deal with the class or interface that denotes
129+
* the service being unavailable at run time. This is distinct from the more regular
130+
* case where the service is available but no implementations of the service are
131+
* available.
132+
*
78133
* <h2> Example </h2>
79134
*
80135
* <p> The following example uses the {@link
@@ -153,7 +208,6 @@ private Configuration(List<Configuration> parents, Resolver resolver) {
153208
this.graph = g;
154209
this.modules = Set.of(moduleArray);
155210
this.nameToModule = Map.ofEntries(nameEntries);
156-
157211
this.targetPlatform = resolver.targetPlatform();
158212
}
159213

@@ -358,10 +412,17 @@ static Configuration resolveAndBind(ModuleFinder finder,
358412
* module {@code M} containing package {@code p} reads another module
359413
* that exports {@code p} to {@code M}. </p></li>
360414
*
361-
* <li><p> A module {@code M} declares that it "{@code uses p.S}" or
362-
* "{@code provides p.S with ...}" but package {@code p} is neither in
363-
* module {@code M} nor exported to {@code M} by any module that
364-
* {@code M} reads. </p></li>
415+
* <li><p> A module {@code M} declares that it '{@code uses p.S}' or
416+
* '{@code provides p.S with ...}', but the package {@code p} is neither in
417+
* module {@code M} nor exported to {@code M} by any module that {@code M}
418+
* reads. Additionally, neither of the following is {@code true}:
419+
* <ul>
420+
* <li> {@code M} declares '{@code requires static}' for at least one
421+
* module that is not in the readability graph. </li>
422+
* <li> {@code M} reads another module that declares
423+
* '{@code requires transitive static}' for at least one module that is
424+
* not in the readability graph. </li>
425+
* </ul> </li>
365426
*
366427
* </ul>
367428
*

‎src/java.base/share/classes/java/lang/module/Resolver.java

+38-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -689,7 +689,6 @@ private ResolvedModule computeIfAbsent(Map<String, ResolvedModule> map,
689689
* </ol>
690690
*/
691691
private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph) {
692-
693692
for (Map.Entry<ResolvedModule, Set<ResolvedModule>> e : graph.entrySet()) {
694693
ModuleDescriptor descriptor1 = e.getKey().descriptor();
695694
String name1 = descriptor1.name();
@@ -754,7 +753,6 @@ private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph
754753
failTwoSuppliers(descriptor1, source, descriptor2, supplier);
755754
}
756755
}
757-
758756
}
759757
}
760758

@@ -764,18 +762,21 @@ private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph
764762
// uses S
765763
for (String service : descriptor1.uses()) {
766764
String pn = packageName(service);
767-
if (!packageToExporter.containsKey(pn)) {
768-
resolveFail("Module %s does not read a module that exports %s",
769-
descriptor1.name(), pn);
765+
if (!packageToExporter.containsKey(pn)
766+
&& !requiresStaticMissingModule(descriptor1, reads)) {
767+
resolveFail("Module %s uses %s but does not read a module that exports %s to %s",
768+
descriptor1.name(), service, pn, descriptor1.name());
769+
770770
}
771771
}
772772

773773
// provides S
774774
for (ModuleDescriptor.Provides provides : descriptor1.provides()) {
775775
String pn = packageName(provides.service());
776-
if (!packageToExporter.containsKey(pn)) {
777-
resolveFail("Module %s does not read a module that exports %s",
778-
descriptor1.name(), pn);
776+
if (!packageToExporter.containsKey(pn)
777+
&& !requiresStaticMissingModule(descriptor1, reads)) {
778+
resolveFail("Module %s provides %s but does not read a module that exports %s to %s",
779+
descriptor1.name(), provides.service(), pn, descriptor1.name());
779780
}
780781
}
781782

@@ -785,6 +786,34 @@ private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph
785786

786787
}
787788

789+
/**
790+
* Returns true if a module 'requires static' a module that is not in the
791+
* readability graph, or reads a module that 'requires static transitive'
792+
* a module that is not in the readability graph.
793+
*/
794+
private boolean requiresStaticMissingModule(ModuleDescriptor descriptor,
795+
Set<ResolvedModule> reads) {
796+
Set<String> moduleNames = reads.stream()
797+
.map(ResolvedModule::name)
798+
.collect(Collectors.toSet());
799+
for (ModuleDescriptor.Requires r : descriptor.requires()) {
800+
if (r.modifiers().contains(Modifier.STATIC)
801+
&& !moduleNames.contains(r.name())) {
802+
return true;
803+
}
804+
}
805+
for (ResolvedModule rm : reads) {
806+
for (ModuleDescriptor.Requires r : rm.descriptor().requires()) {
807+
if (r.modifiers().contains(Modifier.STATIC)
808+
&& r.modifiers().contains(Modifier.TRANSITIVE)
809+
&& !moduleNames.contains(r.name())) {
810+
return true;
811+
}
812+
}
813+
}
814+
return false;
815+
}
816+
788817
/**
789818
* Fail because a module in the configuration exports the same package to
790819
* a module that reads both. This includes the case where a module M

‎src/java.base/share/classes/java/lang/module/package-info.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -191,6 +191,10 @@
191191
* However, if M is recursively enumerated at step 1 then all modules that are
192192
* enumerated and `requires static M` will read M. </p>
193193
*
194+
* <p> The {@linkplain java.lang.module.Configuration##optional-services Optional
195+
* Services} section of {@link java.lang.module.Configuration} shows how resolution
196+
* can be resilient when a service comes from a module that is optional at run time.
197+
*
194198
* <h3> Completeness </h3>
195199
*
196200
* <p> Resolution may be partial at compile-time in that the complete transitive

0 commit comments

Comments
 (0)