Skip to content

Commit eee3f70

Browse files
iherasymenkojjohannes
authored andcommitted
Allow granular exports and opens declarations
1 parent 0c5abca commit eee3f70

File tree

5 files changed

+206
-18
lines changed

5 files changed

+206
-18
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ extraJavaModuleInfo {
7070
// failOnMissingModuleInfo.set(false)
7171
module("commons-beanutils:commons-beanutils", "org.apache.commons.beanutils") {
7272
exports("org.apache.commons.beanutils")
73+
// or granuarly allowing access to a package by specific modules
74+
// exports("org.apache.commons.beanutils", "org.mycompany.server", "org.mycompany.client")
7375
// exportAllPackages()
7476
7577
requiresTransitive("org.apache.commons.logging")
@@ -78,6 +80,8 @@ extraJavaModuleInfo {
7880
7981
// closeModule()
8082
// opens("org.apache.commons.beanutils")
83+
// or granuarly allowing runtime-only access to a package by specific modules
84+
// opens("org.apache.commons.beanutils", "org.mycompany.server", "org.mycompany.client")
8185
8286
// requiresTransitive(...)
8387
// requiresStatic(...)

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,16 @@ private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, List<String>> pr
355355
for (String packageName : autoExportedPackages) {
356356
moduleVisitor.visitExport(packageName, 0);
357357
}
358-
for (String packageName : moduleInfo.exports) {
359-
moduleVisitor.visitExport(packageName.replace('.', '/'), 0);
358+
for (Map.Entry<String, Set<String>> entry : moduleInfo.exports.entrySet()) {
359+
String packageName = entry.getKey();
360+
Set<String> modules = entry.getValue();
361+
moduleVisitor.visitExport(packageName.replace('.', '/'), 0, modules.toArray(new String[0]));
360362
}
361363

362-
for (String packageName : moduleInfo.opens) {
363-
moduleVisitor.visitOpen(packageName.replace('.', '/'), 0);
364+
for (Map.Entry<String, Set<String>> entry : moduleInfo.opens.entrySet()) {
365+
String packageName = entry.getKey();
366+
Set<String> modules = entry.getValue();
367+
moduleVisitor.visitOpen(packageName.replace('.', '/'), 0, modules.toArray(new String[0]));
364368
}
365369

366370
moduleVisitor.visitRequire("java.base", 0, null);

src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818

1919
import org.gradle.api.model.ObjectFactory;
2020

21-
import java.util.LinkedHashSet;
22-
import java.util.Set;
21+
import java.util.*;
2322

2423
/**
2524
* Data class to hold the information that should be added as module-info.class to an existing Jar file.
@@ -30,8 +29,8 @@ public class ModuleInfo extends ModuleSpec {
3029
private final String moduleVersion;
3130

3231
boolean openModule = true;
33-
final Set<String> exports = new LinkedHashSet<>();
34-
final Set<String> opens = new LinkedHashSet<>();
32+
final Map<String, Set<String>> exports = new LinkedHashMap<>();
33+
final Map<String, Set<String>> opens = new LinkedHashMap<>();
3534
final Set<String> requires = new LinkedHashSet<>();
3635
final Set<String> requiresTransitive = new LinkedHashSet<>();
3736
final Set<String> requiresStatic = new LinkedHashSet<>();
@@ -58,17 +57,19 @@ public void closeModule() {
5857
* Calling this method at least once automatically makes this a "closed" module: 'module' instead of 'open module'.
5958
*
6059
* @param opens corresponds to the directive in a 'module-info.java' file
60+
* @param to modules this package should be opened to.
6161
*/
62-
public void opens(String opens) {
62+
public void opens(String opens, String... to) {
6363
closeModule();
64-
addOrThrow(this.opens, opens);
64+
addOrThrow(this.opens, opens, to);
6565
}
6666

6767
/**
6868
* @param exports corresponds to the directive in a 'module-info.java' file
69+
* @param to modules this package should be exported to.
6970
*/
70-
public void exports(String exports) {
71-
addOrThrow(this.exports, exports);
71+
public void exports(String exports, String... to) {
72+
addOrThrow(this.exports, exports, to);
7273
}
7374

7475
/**
@@ -113,12 +114,6 @@ public String getModuleVersion() {
113114
return moduleVersion;
114115
}
115116

116-
private static void addOrThrow(Set<String> target, String element) {
117-
if (!target.add(element)) {
118-
throw new IllegalArgumentException("The element '" + element + "' is already specified");
119-
}
120-
}
121-
122117
/**
123118
* Automatically export all packages of the Jar. Can be used instead of individual 'exports()' statements.
124119
*/
@@ -140,4 +135,16 @@ public void patchRealModule() {
140135
this.patchRealModule = true;
141136
}
142137

138+
private static void addOrThrow(Set<String> target, String element) {
139+
if (!target.add(element)) {
140+
throw new IllegalArgumentException("The element '" + element + "' is already specified");
141+
}
142+
}
143+
144+
private static void addOrThrow(Map<String, Set<String>> target, String key, String... elements) {
145+
if (target.put(key, new LinkedHashSet<>(Arrays.asList(elements))) != null) {
146+
throw new IllegalArgumentException("The element '" + key + "' is already specified");
147+
}
148+
}
149+
143150
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package org.gradlex.javamodule.moduleinfo.test
2+
3+
import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild
4+
import org.gradlex.javamodule.moduleinfo.test.fixture.LegacyLibraries
5+
import spock.lang.Specification
6+
7+
class ExportsFunctionalTest extends Specification {
8+
9+
@Delegate
10+
GradleBuild build = new GradleBuild()
11+
12+
LegacyLibraries libs = new LegacyLibraries(false)
13+
14+
def setup() {
15+
settingsFile << 'rootProject.name = "test-project"'
16+
buildFile << '''
17+
plugins {
18+
id("application")
19+
id("org.gradlex.extra-java-module-info")
20+
}
21+
application {
22+
mainModule.set("org.gradle.sample.app")
23+
mainClass.set("org.gradle.sample.app.Main")
24+
}
25+
'''
26+
file("src/main/java/module-info.java") << """
27+
module org.gradle.sample.app {
28+
requires apache.commons.collections;
29+
}
30+
"""
31+
file("src/main/java/org/gradle/sample/app/Main.java") << """
32+
package org.gradle.sample.app;
33+
34+
public class Main {
35+
public static void main(String[] args) {
36+
new org.apache.commons.collections.bag.HashBag();
37+
}
38+
}
39+
"""
40+
}
41+
42+
def "a package can be exported globally"() {
43+
given:
44+
buildFile << """
45+
dependencies {
46+
implementation("commons-collections:commons-collections:3.2.2")
47+
}
48+
extraJavaModuleInfo {
49+
module(${libs.commonsCollections}, "apache.commons.collections") {
50+
exports("org.apache.commons.collections.bag")
51+
}
52+
}
53+
"""
54+
55+
expect:
56+
run()
57+
}
58+
59+
def "a package can be exported to a specific module and only to this module"() {
60+
given:
61+
62+
buildFile << """
63+
dependencies {
64+
implementation("commons-collections:commons-collections:3.2.2")
65+
}
66+
extraJavaModuleInfo {
67+
module(${libs.commonsCollections}, "apache.commons.collections") {
68+
exports("org.apache.commons.collections.bag", "org.gradle.sample.lib")
69+
}
70+
}
71+
"""
72+
73+
expect:
74+
def out = failRun()
75+
out.output.contains('package org.apache.commons.collections.bag is declared in module apache.commons.collections, which does not export it to module org.gradle.sample.app')
76+
}
77+
78+
def "a package can be exported to a specific module"() {
79+
given:
80+
81+
buildFile << """
82+
dependencies {
83+
implementation("commons-collections:commons-collections:3.2.2")
84+
}
85+
extraJavaModuleInfo {
86+
module(${libs.commonsCollections}, "apache.commons.collections") {
87+
exports("org.apache.commons.collections.bag", "org.gradle.sample.app")
88+
}
89+
}
90+
"""
91+
92+
expect:
93+
run()
94+
}
95+
96+
def "a package can be exported to multiple modules"() {
97+
given:
98+
99+
buildFile << """
100+
dependencies {
101+
implementation("commons-collections:commons-collections:3.2.2")
102+
}
103+
extraJavaModuleInfo {
104+
module(${libs.commonsCollections}, "apache.commons.collections") {
105+
exports("org.apache.commons.collections.bag", "org.gradle.sample.lib", "org.gradle.sample.app")
106+
}
107+
}
108+
"""
109+
110+
expect:
111+
run()
112+
}
113+
114+
}

src/test/groovy/org/gradlex/javamodule/moduleinfo/test/OpensFunctionalTest.groovy

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,63 @@ class OpensFunctionalTest extends Specification {
9191
def out = failRun()
9292
out.output.contains('module apache.commons.collections does not "opens org.apache.commons.collections.bag" to module org.gradle.sample.app')
9393
}
94+
95+
def "a package can be opened to a specific module"() {
96+
given:
97+
98+
buildFile << """
99+
dependencies {
100+
implementation("commons-collections:commons-collections:3.2.2")
101+
}
102+
extraJavaModuleInfo {
103+
module(${libs.commonsCollections}, "apache.commons.collections") {
104+
opens("org.apache.commons.collections.buffer", "org.gradle.sample.app")
105+
opens("org.apache.commons.collections.bag", "org.gradle.sample.app")
106+
}
107+
}
108+
"""
109+
110+
expect:
111+
run()
112+
}
113+
114+
def "a package can be opened to a specific module and only to this module"() {
115+
given:
116+
117+
buildFile << """
118+
dependencies {
119+
implementation("commons-collections:commons-collections:3.2.2")
120+
}
121+
extraJavaModuleInfo {
122+
module(${libs.commonsCollections}, "apache.commons.collections") {
123+
opens("org.apache.commons.collections.buffer", "org.gradle.sample.lib")
124+
opens("org.apache.commons.collections.bag", "org.gradle.sample.lib")
125+
}
126+
}
127+
"""
128+
129+
expect:
130+
def out = failRun()
131+
out.output.contains('module apache.commons.collections does not "exports org.apache.commons.collections.buffer" to module org.gradle.sample.app')
132+
}
133+
134+
def "a package can be opened to multiple modules"() {
135+
given:
136+
137+
buildFile << """
138+
dependencies {
139+
implementation("commons-collections:commons-collections:3.2.2")
140+
}
141+
extraJavaModuleInfo {
142+
module(${libs.commonsCollections}, "apache.commons.collections") {
143+
opens("org.apache.commons.collections.buffer", "org.gradle.sample.lib", "org.gradle.sample.app")
144+
opens("org.apache.commons.collections.bag", "org.gradle.sample.lib", "org.gradle.sample.app")
145+
}
146+
}
147+
"""
148+
149+
expect:
150+
run()
151+
}
152+
94153
}

0 commit comments

Comments
 (0)