Skip to content

Commit c592e56

Browse files
committed
Merge Jars - merge service provider files
Fixes #34
1 parent a974622 commit c592e56

File tree

3 files changed

+134
-36
lines changed

3 files changed

+134
-36
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Extra Java Module Info Gradle Plugin - Changelog
22

3+
## Version 0.15
4+
* [New] [#34](https://github.com/jjohannes/extra-java-module-info/issues/34) - Merge Jars - merge service provider files
5+
36
## Version 0.14
47
* [Fixed] [#33](https://github.com/jjohannes/extra-java-module-info/issues/33) - Map 'Jar File Path' to 'group:name' correctly for Jars cached in local .m2 repository (Thanks [Leon Linhart](https://github.com/TheMrMilchmann) for reporting!)
58

src/main/java/de/jjohannes/gradle/javamodules/ExtraModuleInfoTransform.java

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.io.OutputStream;
3030
import java.nio.charset.StandardCharsets;
3131
import java.nio.file.Files;
32+
import java.util.ArrayList;
3233
import java.util.Collection;
3334
import java.util.LinkedHashMap;
3435
import java.util.List;
@@ -39,6 +40,7 @@
3940
import java.util.jar.JarOutputStream;
4041
import java.util.jar.Manifest;
4142
import java.util.regex.Pattern;
43+
import java.util.stream.Collectors;
4244
import java.util.zip.ZipEntry;
4345
import java.util.zip.ZipException;
4446

@@ -178,8 +180,9 @@ private void addAutomaticModuleName(File originalJar, File moduleJar, AutomaticM
178180
}
179181
manifest.getMainAttributes().putValue("Automatic-Module-Name", automaticModule.getModuleName());
180182
try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(moduleJar.toPath()), manifest)) {
181-
copyAndExtractProviders(inputStream, outputStream);
182-
mergeJars(automaticModule, outputStream);
183+
Map<String, List<String>> providers = new LinkedHashMap<>();
184+
copyAndExtractProviders(inputStream, outputStream, !automaticModule.getMergedJars().isEmpty(), providers);
185+
mergeJars(automaticModule, outputStream, providers);
183186
}
184187
} catch (IOException e) {
185188
throw new RuntimeException(e);
@@ -189,8 +192,9 @@ private void addAutomaticModuleName(File originalJar, File moduleJar, AutomaticM
189192
private void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo moduleInfo) {
190193
try (JarInputStream inputStream = new JarInputStream(Files.newInputStream(originalJar.toPath()))) {
191194
try (JarOutputStream outputStream = newJarOutputStream(Files.newOutputStream(moduleJar.toPath()), inputStream.getManifest())) {
192-
Map<String, String[]> providers = copyAndExtractProviders(inputStream, outputStream);
193-
mergeJars(moduleInfo, outputStream);
195+
Map<String, List<String>> providers = new LinkedHashMap<>();
196+
copyAndExtractProviders(inputStream, outputStream, !moduleInfo.getMergedJars().isEmpty(), providers);
197+
mergeJars(moduleInfo, outputStream, providers);
194198
outputStream.putNextEntry(new JarEntry("module-info.class"));
195199
outputStream.write(addModuleInfo(moduleInfo, providers, versionFromFilePath(originalJar.toPath())));
196200
outputStream.closeEntry();
@@ -200,42 +204,53 @@ private void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo mo
200204
}
201205
}
202206

203-
private static JarOutputStream newJarOutputStream(OutputStream out, @Nullable Manifest manifest) throws IOException {
207+
private JarOutputStream newJarOutputStream(OutputStream out, @Nullable Manifest manifest) throws IOException {
204208
return manifest == null ? new JarOutputStream(out) : new JarOutputStream(out, manifest);
205209
}
206210

207-
private static Map<String, String[]> copyAndExtractProviders(JarInputStream inputStream, JarOutputStream outputStream) throws IOException {
211+
private void copyAndExtractProviders(JarInputStream inputStream, JarOutputStream outputStream, boolean willMergeJars, Map<String, List<String>> providers) throws IOException {
208212
JarEntry jarEntry = inputStream.getNextJarEntry();
209-
Map<String, String[]> providers = new LinkedHashMap<>();
210213
while (jarEntry != null) {
211214
byte[] content = readAllBytes(inputStream);
212215
String entryName = jarEntry.getName();
213-
if (entryName.startsWith(SERVICES_PREFIX) && !entryName.equals(SERVICES_PREFIX)) {
214-
providers.put(entryName.substring(SERVICES_PREFIX.length()), extractImplementations(content));
216+
boolean isServiceProviderFile = entryName.startsWith(SERVICES_PREFIX) && !entryName.equals(SERVICES_PREFIX);
217+
if (isServiceProviderFile) {
218+
String key = entryName.substring(SERVICES_PREFIX.length());
219+
if (!providers.containsKey(key)) {
220+
providers.put(key, new ArrayList<>());
221+
}
222+
providers.get(key).addAll(extractImplementations(content));
215223
}
224+
216225
if (!JAR_SIGNATURE_PATH.matcher(jarEntry.getName()).matches() && !"META-INF/MANIFEST.MF".equals(jarEntry.getName())) {
217-
jarEntry.setCompressedSize(-1);
218-
outputStream.putNextEntry(jarEntry);
219-
outputStream.write(content);
220-
outputStream.closeEntry();
226+
if (!willMergeJars || !isServiceProviderFile) { // service provider files will be merged later
227+
jarEntry.setCompressedSize(-1);
228+
try {
229+
outputStream.putNextEntry(jarEntry);
230+
outputStream.write(content);
231+
outputStream.closeEntry();
232+
} catch (ZipException e) {
233+
if (!e.getMessage().startsWith("duplicate entry:")) {
234+
throw new RuntimeException(e);
235+
}
236+
}
237+
}
221238
}
222239
jarEntry = inputStream.getNextJarEntry();
223240
}
224-
return providers;
225241
}
226242

227-
private static String[] extractImplementations(byte[] content) {
243+
private List<String> extractImplementations(byte[] content) {
228244
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(content), StandardCharsets.UTF_8))
229245
.lines()
230246
.map(String::trim)
231247
.filter(line -> !line.isEmpty())
232248
.filter(line -> !line.startsWith("#"))
233-
.map(line -> line.replace('.','/'))
234249
.distinct()
235-
.toArray(String[]::new);
250+
.collect(Collectors.toList());
236251
}
237252

238-
private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, String[]> providers, @Nullable String version) {
253+
private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, List<String>> providers, @Nullable String version) {
239254
ClassWriter classWriter = new ClassWriter(0);
240255
classWriter.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null);
241256
String moduleVersion = moduleInfo.getModuleVersion() == null ? version : moduleInfo.getModuleVersion();
@@ -253,19 +268,24 @@ private byte[] addModuleInfo(ModuleInfo moduleInfo, Map<String, String[]> provid
253268
for (String requireName : moduleInfo.requiresStatic) {
254269
moduleVisitor.visitRequire(requireName, Opcodes.ACC_STATIC_PHASE, null);
255270
}
256-
for (Map.Entry<String, String[]> entry : providers.entrySet()) {
271+
for (Map.Entry<String, List<String>> entry : providers.entrySet()) {
257272
String name = entry.getKey();
258-
String[] implementations = entry.getValue();
273+
List<String> implementations = entry.getValue();
259274
if (!moduleInfo.ignoreServiceProviders.contains(name)) {
260-
moduleVisitor.visitProvide(name.replace('.', '/'), implementations);
275+
moduleVisitor.visitProvide(name.replace('.', '/'),
276+
implementations.stream().map(impl -> impl.replace('.','/')).toArray(String[]::new));
261277
}
262278
}
263279
moduleVisitor.visitEnd();
264280
classWriter.visitEnd();
265281
return classWriter.toByteArray();
266282
}
267283

268-
private void mergeJars(ModuleSpec moduleSpec, JarOutputStream outputStream) throws IOException {
284+
private void mergeJars(ModuleSpec moduleSpec, JarOutputStream outputStream, Map<String, List<String>> providers) throws IOException {
285+
if (moduleSpec.getMergedJars().isEmpty()) {
286+
return;
287+
}
288+
269289
RegularFile mergeJarFile = null;
270290
for (String identifier : moduleSpec.getMergedJars()) {
271291
List<String> ids = getParameters().getMergeJarIds().get();
@@ -285,31 +305,29 @@ private void mergeJars(ModuleSpec moduleSpec, JarOutputStream outputStream) thro
285305

286306
if (mergeJarFile != null) {
287307
try (JarInputStream toMergeInputStream = new JarInputStream(Files.newInputStream(mergeJarFile.getAsFile().toPath()))) {
288-
copyEntries(toMergeInputStream, outputStream);
308+
copyAndExtractProviders(toMergeInputStream, outputStream, true, providers);
289309
}
290310
} else {
291311
throw new RuntimeException("Jar not found: " + identifier);
292312
}
293313
}
314+
315+
mergeServiceProviderFiles(outputStream, providers);
294316
}
295317

296-
private static void copyEntries(JarInputStream inputStream, JarOutputStream outputStream) throws IOException {
297-
JarEntry jarEntry = inputStream.getNextJarEntry();
298-
while (jarEntry != null) {
299-
try {
300-
outputStream.putNextEntry(jarEntry);
301-
outputStream.write(readAllBytes(inputStream));
302-
outputStream.closeEntry();
303-
} catch (ZipException e) {
304-
if (!e.getMessage().startsWith("duplicate entry:")) {
305-
throw new RuntimeException(e);
306-
}
318+
private void mergeServiceProviderFiles(JarOutputStream outputStream, Map<String, List<String>> providers) throws IOException {
319+
for (Map.Entry<String, List<String>> provider : providers.entrySet()) {
320+
JarEntry jarEntry = new JarEntry(SERVICES_PREFIX + provider.getKey());
321+
outputStream.putNextEntry(jarEntry);
322+
for (String implementation : provider.getValue()) {
323+
outputStream.write(implementation.getBytes());
324+
outputStream.write("\n".getBytes());
307325
}
308-
jarEntry = inputStream.getNextJarEntry();
326+
outputStream.closeEntry();
309327
}
310328
}
311329

312-
public static byte[] readAllBytes(InputStream inputStream) throws IOException {
330+
private byte[] readAllBytes(InputStream inputStream) throws IOException {
313331
final int bufLen = 4 * 0x400;
314332
byte[] buf = new byte[bufLen];
315333
int readLen;

src/test/groovy/de/jjohannes/gradle/javamodules/test/AbstractFunctionalTest.groovy

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,81 @@ abstract class AbstractFunctionalTest extends Specification {
453453
!result.output.contains('slf4j-api-1.7.32.jar')
454454
!result.output.contains('slf4j-ext-1.7.32.jar')
455455
}
456+
457+
def "merges service provider files of several jars"() {
458+
given:
459+
file("src/main/java/org/gradle/sample/app/Main.java") << """
460+
package org.gradle.sample.app;
461+
462+
public class Main {
463+
public static void main(String[] args) {
464+
}
465+
}
466+
"""
467+
file("src/main/java/module-info.java") << """
468+
module org.gradle.sample.app {
469+
requires org.apache.qpid.broker;
470+
}
471+
"""
472+
buildFile << """
473+
dependencies {
474+
implementation(platform("org.apache.qpid:qpid-broker-parent:8.0.6"))
475+
javaModulesMergeJars(platform("org.apache.qpid:qpid-broker-parent:8.0.6"))
476+
477+
implementation("org.apache.qpid:qpid-broker-core")
478+
implementation("org.apache.qpid:qpid-broker-plugins-amqp-1-0-protocol")
479+
implementation("org.apache.qpid:qpid-broker-plugins-memory-store")
480+
implementation("org.apache.qpid:qpid-jms-client")
481+
implementation("org.apache.qpid:qpid-broker-plugins-management-http")
482+
implementation("org.apache.qpid:qpid-broker-plugins-websocket")
483+
}
484+
485+
extraJavaModuleInfo {
486+
failOnMissingModuleInfo.set(false)
487+
automaticModule("org.apache.qpid:qpid-broker-core", "org.apache.qpid.broker") {
488+
mergeJar("org.apache.qpid:qpid-broker-plugins-amqp-1-0-protocol")
489+
mergeJar("org.apache.qpid:qpid-broker-plugins-memory-store")
490+
mergeJar("org.apache.qpid:qpid-jms-client")
491+
mergeJar("org.apache.qpid:qpid-broker-plugins-management-http")
492+
mergeJar("org.apache.qpid:qpid-broker-plugins-websocket")
493+
}
494+
}
495+
496+
tasks.named("run") {
497+
doLast {
498+
println(
499+
zipTree(configurations.runtimeClasspath.get().files.find {
500+
it.name == "qpid-broker-core-8.0.6-module.jar"
501+
}).find {
502+
it.path.endsWith("/META-INF/services/org.apache.qpid.server.plugin.ConfiguredObjectRegistration")
503+
}!!.readText()
504+
)
505+
}
506+
}
507+
"""
508+
509+
when:
510+
def result = run()
511+
512+
then:
513+
result.output.contains('''
514+
org.apache.qpid.server.security.auth.manager.ConfiguredObjectRegistrationImpl
515+
org.apache.qpid.server.model.adapter.ConfiguredObjectRegistrationImpl
516+
org.apache.qpid.server.model.ConfiguredObjectRegistrationImpl
517+
org.apache.qpid.server.virtualhost.ConfiguredObjectRegistrationImpl
518+
org.apache.qpid.server.security.group.ConfiguredObjectRegistrationImpl
519+
org.apache.qpid.server.security.group.cloudfoundry.ConfiguredObjectRegistrationImpl
520+
org.apache.qpid.server.virtualhostnode.ConfiguredObjectRegistrationImpl
521+
org.apache.qpid.server.security.auth.manager.oauth2.ConfiguredObjectRegistrationImpl
522+
org.apache.qpid.server.exchange.ConfiguredObjectRegistrationImpl
523+
org.apache.qpid.server.model.port.ConfiguredObjectRegistrationImpl
524+
org.apache.qpid.server.queue.ConfiguredObjectRegistrationImpl
525+
org.apache.qpid.server.security.ConfiguredObjectRegistrationImpl
526+
org.apache.qpid.server.protocol.v1_0.ConfiguredObjectRegistrationImpl
527+
org.apache.qpid.server.virtualhostnode.memory.ConfiguredObjectRegistrationImpl
528+
org.apache.qpid.server.store.ConfiguredObjectRegistrationImpl
529+
org.apache.qpid.server.virtualhost.memory.ConfiguredObjectRegistrationImpl
530+
org.apache.qpid.server.management.plugin.ConfiguredObjectRegistrationImpl
531+
'''.stripIndent())
532+
}
456533
}

0 commit comments

Comments
 (0)