Skip to content

Commit 3c2dc6a

Browse files
committed
Add Archiver that creates modular JAR files using the JDK jar tool.
1 parent 54fe62b commit 3c2dc6a

File tree

6 files changed

+575
-1
lines changed

6 files changed

+575
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
/**
2+
*
3+
* Copyright 2018 The Apache Software Foundation
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.codehaus.plexus.archiver.jar;
18+
19+
import org.apache.commons.compress.parallel.InputStreamSupplier;
20+
import org.codehaus.plexus.archiver.ArchiverException;
21+
import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
22+
import org.codehaus.plexus.archiver.util.ResourceUtils;
23+
import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
24+
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
25+
import org.codehaus.plexus.util.FileUtils;
26+
27+
import java.io.File;
28+
import java.io.IOException;
29+
import java.io.PrintStream;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.regex.Pattern;
35+
36+
/**
37+
* A {@link ModularJarArchiver} implementation that uses
38+
* the {@code jar} tool provided by
39+
* {@code java.util.spi.ToolProvider} to create
40+
* modular JAR files.
41+
*
42+
* <p>
43+
* The basic JAR archive is created by {@link JarArchiver}
44+
* and the {@code jar} tool is used to upgrade it to modular JAR.
45+
*
46+
* <p>
47+
* If the JAR file does not contain module descriptor
48+
* or the JDK does not provide the {@code jar} tool
49+
* (for example JDK prior to Java 9), then the
50+
* archive created by {@link JarArchiver}
51+
* is left unchanged.
52+
*/
53+
public class JarToolModularJarArchiver
54+
extends ModularJarArchiver
55+
{
56+
private static final String MODULE_DESCRIPTOR_FILE_NAME
57+
= "module-info.class";
58+
59+
private static final Pattern MRJAR_VERSION_AREA
60+
= Pattern.compile( "META-INF/versions/\\d+/" );
61+
62+
private Object jarTool;
63+
64+
private boolean moduleDescriptorFound;
65+
66+
private Path tempDir;
67+
68+
public JarToolModularJarArchiver()
69+
{
70+
try
71+
{
72+
Class<?> toolProviderClass =
73+
Class.forName( "java.util.spi.ToolProvider" );
74+
Object jarToolOptional = toolProviderClass
75+
.getMethod( "findFirst", String.class )
76+
.invoke( null, "jar" );
77+
78+
jarTool = jarToolOptional.getClass().getMethod( "get" )
79+
.invoke( jarToolOptional );
80+
}
81+
catch ( ReflectiveOperationException | SecurityException e )
82+
{
83+
// Ignore. It is expected that the jar tool
84+
// may not be available.
85+
}
86+
}
87+
88+
@Override
89+
protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut,
90+
String vPath, long lastModified, File fromArchive,
91+
int mode, String symlinkDestination,
92+
boolean addInParallel )
93+
throws IOException, ArchiverException
94+
{
95+
// We store the module descriptors in temporary location
96+
// and then add it to the JAR file using the JDK jar tool.
97+
// It may look strange at first, but to update a JAR file
98+
// you need to add new files[1] and the only files
99+
// we're sure that exists in modular JAR file
100+
// are the module descriptors.
101+
//
102+
// [1] There are some exceptions but we need at least one file to
103+
// ensure it will work in all cases.
104+
if ( jarTool != null && isModuleDescriptor( vPath ) )
105+
{
106+
getLogger().debug( "Module descriptor found: " + vPath );
107+
108+
moduleDescriptorFound = true;
109+
110+
// Copy the module descriptor to temporary directory
111+
// so later then can be added to the JAR archive
112+
// by the jar tool.
113+
114+
if ( tempDir == null )
115+
{
116+
tempDir = Files
117+
.createTempDirectory( "plexus-archiver-modular_jar-" );
118+
tempDir.toFile().deleteOnExit();
119+
}
120+
121+
File destFile = tempDir.resolve( vPath ).toFile();
122+
destFile.getParentFile().mkdirs();
123+
destFile.deleteOnExit();
124+
125+
ResourceUtils.copyFile( is.get(), destFile );
126+
ArchiveEntryUtils.chmod( destFile, mode );
127+
destFile.setLastModified( lastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
128+
? System.currentTimeMillis()
129+
: lastModified );
130+
}
131+
else
132+
{
133+
super.zipFile( is, zOut, vPath, lastModified,
134+
fromArchive, mode, symlinkDestination, addInParallel );
135+
}
136+
}
137+
138+
@Override
139+
protected void postCreateArchive()
140+
throws ArchiverException
141+
{
142+
if ( !moduleDescriptorFound )
143+
{
144+
// no need to update the JAR archive
145+
return;
146+
}
147+
148+
try
149+
{
150+
getLogger().debug( "Using the jar tool to " +
151+
"update the archive to modular JAR." );
152+
153+
Integer result = (Integer) jarTool.getClass()
154+
.getMethod( "run",
155+
PrintStream.class, PrintStream.class, String[].class )
156+
.invoke( jarTool,
157+
System.out, System.err,
158+
getJarToolArguments() );
159+
160+
if ( result != null && result != 0 )
161+
{
162+
throw new ArchiverException( "Could not create modular JAR file. " +
163+
"The JDK jar tool exited with " + result );
164+
}
165+
}
166+
catch ( ReflectiveOperationException | SecurityException e )
167+
{
168+
throw new ArchiverException( "Exception occurred " +
169+
"while creating modular JAR file", e );
170+
}
171+
finally
172+
{
173+
clearTempDirectory();
174+
}
175+
}
176+
177+
/**
178+
* Returns {@code true} if {@code path}
179+
* is a module descriptor.
180+
*/
181+
private boolean isModuleDescriptor( String path )
182+
{
183+
if ( path.endsWith( MODULE_DESCRIPTOR_FILE_NAME ) )
184+
{
185+
String prefix = path.substring( 0,
186+
path.lastIndexOf( MODULE_DESCRIPTOR_FILE_NAME ) );
187+
188+
// the path is a module descriptor if it located
189+
// into the root of the archive or into the
190+
// version are of a multi-release JAR file
191+
return prefix.isEmpty() ||
192+
MRJAR_VERSION_AREA.matcher( prefix ).matches();
193+
}
194+
else
195+
{
196+
return false;
197+
}
198+
}
199+
200+
/**
201+
* Prepares the arguments for the jar tool.
202+
* It takes into account the module version,
203+
* main class, etc.
204+
*/
205+
private String[] getJarToolArguments()
206+
{
207+
List<String> args = new ArrayList<>();
208+
209+
args.add( "--update" );
210+
args.add( "--file" );
211+
args.add( getDestFile().getAbsolutePath() );
212+
213+
if ( getModuleMainClass() != null )
214+
{
215+
args.add( "--main-class" );
216+
args.add( getModuleMainClass() );
217+
}
218+
219+
if ( getModuleVersion() != null )
220+
{
221+
args.add( "--module-version" );
222+
args.add( getModuleVersion() );
223+
}
224+
225+
if ( !isCompress() )
226+
{
227+
args.add( "--no-compress" );
228+
}
229+
230+
args.add( "-C" );
231+
args.add( tempDir.toFile().getAbsolutePath() );
232+
args.add( "." );
233+
234+
return args.toArray( new String[]{} );
235+
}
236+
237+
/**
238+
* Makes best effort the clean up
239+
* the temporary directory used.
240+
*/
241+
private void clearTempDirectory()
242+
{
243+
try
244+
{
245+
if ( tempDir != null )
246+
{
247+
FileUtils.deleteDirectory( tempDir.toFile() );
248+
}
249+
}
250+
catch ( IOException e )
251+
{
252+
// Ignore. It is just best effort.
253+
}
254+
}
255+
256+
}

src/main/resources/META-INF/plexus/components.xml

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
<implementation>org.codehaus.plexus.archiver.jar.JarArchiver</implementation>
4242
<instantiation-strategy>per-lookup</instantiation-strategy>
4343
</component>
44+
<component>
45+
<role>org.codehaus.plexus.archiver.Archiver</role>
46+
<role-hint>mjar</role-hint>
47+
<implementation>org.codehaus.plexus.archiver.jar.JarToolModularJarArchiver</implementation>
48+
<instantiation-strategy>per-lookup</instantiation-strategy>
49+
</component>
4450

4551
<component>
4652
<role>org.codehaus.plexus.archiver.Archiver</role>

0 commit comments

Comments
 (0)