2929import java .io .OutputStream ;
3030import java .nio .charset .StandardCharsets ;
3131import java .nio .file .Files ;
32+ import java .util .ArrayList ;
3233import java .util .Collection ;
3334import java .util .LinkedHashMap ;
3435import java .util .List ;
3940import java .util .jar .JarOutputStream ;
4041import java .util .jar .Manifest ;
4142import java .util .regex .Pattern ;
43+ import java .util .stream .Collectors ;
4244import java .util .zip .ZipEntry ;
4345import 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 ;
0 commit comments