1616 */
1717package org .codehaus .plexus .archiver .jar ;
1818
19- import org .apache .commons .compress .parallel .InputStreamSupplier ;
20- import org .codehaus .plexus .archiver .ArchiverException ;
21- import org .codehaus .plexus .archiver .zip .ConcurrentJarCreator ;
22-
2319import java .io .File ;
2420import java .io .IOException ;
2521import java .io .PrintStream ;
22+ import java .lang .reflect .Method ;
2623import java .nio .file .Files ;
24+ import java .nio .file .Path ;
25+ import java .nio .file .StandardCopyOption ;
26+ import java .nio .file .attribute .FileTime ;
2727import java .util .ArrayList ;
28+ import java .util .Calendar ;
29+ import java .util .Enumeration ;
2830import java .util .List ;
31+ import java .util .Locale ;
32+ import java .util .TimeZone ;
2933import java .util .regex .Pattern ;
34+ import java .util .zip .ZipEntry ;
35+ import java .util .zip .ZipFile ;
36+ import java .util .zip .ZipOutputStream ;
37+
38+ import org .apache .commons .compress .parallel .InputStreamSupplier ;
39+ import org .apache .commons .io .output .NullOutputStream ;
40+ import org .codehaus .plexus .archiver .ArchiverException ;
41+ import org .codehaus .plexus .archiver .zip .ConcurrentJarCreator ;
42+ import org .codehaus .plexus .util .IOUtil ;
3043
3144/**
3245 * A {@link ModularJarArchiver} implementation that uses
@@ -58,6 +71,8 @@ public class JarToolModularJarArchiver
5871
5972 private boolean moduleDescriptorFound ;
6073
74+ private boolean hasJarDateOption ;
75+
6176 public JarToolModularJarArchiver ()
6277 {
6378 try
@@ -111,18 +126,30 @@ protected void postCreateArchive()
111126 getLogger ().debug ( "Using the jar tool to " +
112127 "update the archive to modular JAR." );
113128
114- Integer result = (Integer ) jarTool .getClass ()
115- .getMethod ( "run" ,
116- PrintStream .class , PrintStream .class , String [].class )
117- .invoke ( jarTool ,
118- System .out , System .err ,
119- getJarToolArguments () );
129+ final Method jarRun = jarTool .getClass ()
130+ .getMethod ( "run" , PrintStream .class , PrintStream .class , String [].class );
131+
132+ if ( getLastModifiedTime () != null )
133+ {
134+ hasJarDateOption = isJarDateOptionSupported ( jarRun );
135+ getLogger ().debug ( "jar tool --date option is supported: " + hasJarDateOption );
136+ }
137+
138+ Integer result = (Integer ) jarRun .invoke ( jarTool , System .out , System .err , getJarToolArguments () );
120139
121140 if ( result != null && result != 0 )
122141 {
123142 throw new ArchiverException ( "Could not create modular JAR file. " +
124143 "The JDK jar tool exited with " + result );
125144 }
145+
146+ if ( !hasJarDateOption && getLastModifiedTime () != null )
147+ {
148+ getLogger ().debug ( "Fix last modified time zip entries." );
149+ // --date option not supported, fallback to rewrite the JAR file
150+ // https://github.com/codehaus-plexus/plexus-archiver/issues/164
151+ fixLastModifiedTimeZipEntries ();
152+ }
126153 }
127154 catch ( IOException | ReflectiveOperationException | SecurityException e )
128155 {
@@ -131,6 +158,36 @@ protected void postCreateArchive()
131158 }
132159 }
133160
161+ /**
162+ * Fallback to rewrite the JAR file with the correct timestamp if the {@code --date} option is not available.
163+ */
164+ private void fixLastModifiedTimeZipEntries ()
165+ throws IOException
166+ {
167+ long timeMillis = getLastModifiedTime ().toMillis ();
168+ Path destFile = getDestFile ().toPath ();
169+ Path tmpZip = Files .createTempFile ( destFile .getParent (), null , null );
170+ try ( ZipFile zipFile = new ZipFile ( getDestFile () );
171+ ZipOutputStream out = new ZipOutputStream ( Files .newOutputStream ( tmpZip ) ) )
172+ {
173+ Enumeration <? extends ZipEntry > entries = zipFile .entries ();
174+ while ( entries .hasMoreElements () )
175+ {
176+ ZipEntry entry = entries .nextElement ();
177+ // Not using setLastModifiedTime(FileTime) as it sets the extended timestamp
178+ // which is not compatible with the jar tool output.
179+ entry .setTime ( timeMillis );
180+ out .putNextEntry ( entry );
181+ if ( !entry .isDirectory () )
182+ {
183+ IOUtil .copy ( zipFile .getInputStream ( entry ), out );
184+ }
185+ out .closeEntry ();
186+ }
187+ }
188+ Files .move ( tmpZip , destFile , StandardCopyOption .REPLACE_EXISTING );
189+ }
190+
134191 /**
135192 * Returns {@code true} if {@code path}
136193 * is a module descriptor.
@@ -201,11 +258,51 @@ private String[] getJarToolArguments()
201258 args .add ( "--no-compress" );
202259 }
203260
261+ if ( hasJarDateOption )
262+ {
263+ // The --date option already normalize the time, so revert to the local time
264+ FileTime localTime = revertToLocalTime ( getLastModifiedTime () );
265+ args .add ( "--date" );
266+ args .add ( localTime .toString () );
267+ }
268+
204269 args .add ( "-C" );
205270 args .add ( tempEmptyDir .getAbsolutePath () );
206271 args .add ( "." );
207272
208273 return args .toArray ( new String [0 ] );
209274 }
210275
276+ private static FileTime revertToLocalTime ( FileTime time )
277+ {
278+ long restoreToLocalTime = time .toMillis ();
279+ Calendar cal = Calendar .getInstance ( TimeZone .getDefault (), Locale .ROOT );
280+ cal .setTimeInMillis ( restoreToLocalTime );
281+ restoreToLocalTime = restoreToLocalTime + ( cal .get ( Calendar .ZONE_OFFSET ) + cal .get ( Calendar .DST_OFFSET ) );
282+ return FileTime .fromMillis ( restoreToLocalTime );
283+ }
284+
285+ /**
286+ * Check support for {@code --date} option introduced since Java 17.0.3 (JDK-8279925).
287+ *
288+ * @return true if the JAR tool supports the {@code --date} option
289+ */
290+ private boolean isJarDateOptionSupported ( Method runMethod )
291+ {
292+ try
293+ {
294+ // Test the output code validating the --date option.
295+ String [] args = { "--date" , "2099-12-31T23:59:59Z" , "--version" };
296+
297+ PrintStream nullPrintStream = new PrintStream ( NullOutputStream .NULL_OUTPUT_STREAM );
298+ Integer result = (Integer ) runMethod .invoke ( jarTool , nullPrintStream , nullPrintStream , args );
299+
300+ return result != null && result .intValue () == 0 ;
301+ }
302+ catch ( ReflectiveOperationException | SecurityException e )
303+ {
304+ return false ;
305+ }
306+ }
307+
211308}
0 commit comments