16
16
*/
17
17
package org .codehaus .plexus .archiver .jar ;
18
18
19
- import org .apache .commons .compress .parallel .InputStreamSupplier ;
20
- import org .codehaus .plexus .archiver .ArchiverException ;
21
- import org .codehaus .plexus .archiver .zip .ConcurrentJarCreator ;
22
-
23
19
import java .io .File ;
24
20
import java .io .IOException ;
25
21
import java .io .PrintStream ;
22
+ import java .lang .reflect .Method ;
26
23
import java .nio .file .Files ;
24
+ import java .nio .file .Path ;
25
+ import java .nio .file .StandardCopyOption ;
26
+ import java .nio .file .attribute .FileTime ;
27
27
import java .util .ArrayList ;
28
+ import java .util .Calendar ;
29
+ import java .util .Enumeration ;
28
30
import java .util .List ;
31
+ import java .util .Locale ;
32
+ import java .util .TimeZone ;
29
33
import 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 ;
30
43
31
44
/**
32
45
* A {@link ModularJarArchiver} implementation that uses
@@ -58,6 +71,8 @@ public class JarToolModularJarArchiver
58
71
59
72
private boolean moduleDescriptorFound ;
60
73
74
+ private boolean hasJarDateOption ;
75
+
61
76
public JarToolModularJarArchiver ()
62
77
{
63
78
try
@@ -111,18 +126,30 @@ protected void postCreateArchive()
111
126
getLogger ().debug ( "Using the jar tool to " +
112
127
"update the archive to modular JAR." );
113
128
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 () );
120
139
121
140
if ( result != null && result != 0 )
122
141
{
123
142
throw new ArchiverException ( "Could not create modular JAR file. " +
124
143
"The JDK jar tool exited with " + result );
125
144
}
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
+ }
126
153
}
127
154
catch ( IOException | ReflectiveOperationException | SecurityException e )
128
155
{
@@ -131,6 +158,36 @@ protected void postCreateArchive()
131
158
}
132
159
}
133
160
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
+
134
191
/**
135
192
* Returns {@code true} if {@code path}
136
193
* is a module descriptor.
@@ -201,11 +258,51 @@ private String[] getJarToolArguments()
201
258
args .add ( "--no-compress" );
202
259
}
203
260
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
+
204
269
args .add ( "-C" );
205
270
args .add ( tempEmptyDir .getAbsolutePath () );
206
271
args .add ( "." );
207
272
208
273
return args .toArray ( new String [0 ] );
209
274
}
210
275
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
+
211
308
}
0 commit comments