1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.codehaus.plexus.archiver.jar;
18
19 import javax.inject.Named;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintStream;
24 import java.lang.reflect.Method;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.StandardCopyOption;
28 import java.nio.file.attribute.FileTime;
29 import java.util.ArrayList;
30 import java.util.Calendar;
31 import java.util.Enumeration;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.TimeZone;
35 import java.util.regex.Pattern;
36 import java.util.zip.ZipEntry;
37 import java.util.zip.ZipFile;
38 import java.util.zip.ZipOutputStream;
39
40 import org.apache.commons.compress.parallel.InputStreamSupplier;
41 import org.apache.commons.io.output.NullPrintStream;
42 import org.codehaus.plexus.archiver.ArchiverException;
43 import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
44 import org.codehaus.plexus.util.IOUtil;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 @Named("mjar")
64 public class JarToolModularJarArchiver extends ModularJarArchiver {
65 private static final String MODULE_DESCRIPTOR_FILE_NAME = "module-info.class";
66
67 private static final Pattern MRJAR_VERSION_AREA = Pattern.compile("META-INF/versions/\\d+/");
68
69 private Object jarTool;
70
71 private boolean moduleDescriptorFound;
72
73 private boolean hasJarDateOption;
74
75 public JarToolModularJarArchiver() {
76 try {
77 Class<?> toolProviderClass = Class.forName("java.util.spi.ToolProvider");
78 Object jarToolOptional =
79 toolProviderClass.getMethod("findFirst", String.class).invoke(null, "jar");
80
81 jarTool = jarToolOptional.getClass().getMethod("get").invoke(jarToolOptional);
82 } catch (ReflectiveOperationException | SecurityException e) {
83
84
85 }
86 }
87
88 @Override
89 protected void zipFile(
90 InputStreamSupplier is,
91 ConcurrentJarCreator zOut,
92 String vPath,
93 long lastModified,
94 File fromArchive,
95 int mode,
96 String symlinkDestination,
97 boolean addInParallel)
98 throws IOException, ArchiverException {
99 if (jarTool != null && isModuleDescriptor(vPath)) {
100 getLogger().debug("Module descriptor found: " + vPath);
101
102 moduleDescriptorFound = true;
103 }
104
105 super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode, symlinkDestination, addInParallel);
106 }
107
108 @Override
109 protected void postCreateArchive() throws ArchiverException {
110 if (!moduleDescriptorFound) {
111
112 return;
113 }
114
115 try {
116 getLogger().debug("Using the jar tool to " + "update the archive to modular JAR.");
117
118 final Method jarRun =
119 jarTool.getClass().getMethod("run", PrintStream.class, PrintStream.class, String[].class);
120
121 if (getLastModifiedTime() != null) {
122 hasJarDateOption = isJarDateOptionSupported(jarRun);
123 getLogger().debug("jar tool --date option is supported: " + hasJarDateOption);
124 }
125
126 Integer result = (Integer) jarRun.invoke(jarTool, System.out, System.err, getJarToolArguments());
127
128 if (result != null && result != 0) {
129 throw new ArchiverException(
130 "Could not create modular JAR file. " + "The JDK jar tool exited with " + result);
131 }
132
133 if (!hasJarDateOption && getLastModifiedTime() != null) {
134 getLogger().debug("Fix last modified time zip entries.");
135
136
137 fixLastModifiedTimeZipEntries();
138 }
139 } catch (IOException | ReflectiveOperationException | SecurityException e) {
140 throw new ArchiverException("Exception occurred " + "while creating modular JAR file", e);
141 }
142 }
143
144
145
146
147 private void fixLastModifiedTimeZipEntries() throws IOException {
148 long timeMillis = getLastModifiedTime().toMillis();
149 Path destFile = getDestFile().toPath();
150 Path tmpZip = Files.createTempFile(destFile.getParent(), null, null);
151 try (ZipFile zipFile = new ZipFile(getDestFile());
152 ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(tmpZip))) {
153 Enumeration<? extends ZipEntry> entries = zipFile.entries();
154 while (entries.hasMoreElements()) {
155 ZipEntry entry = entries.nextElement();
156
157
158 entry.setTime(timeMillis);
159 out.putNextEntry(entry);
160 if (!entry.isDirectory()) {
161 IOUtil.copy(zipFile.getInputStream(entry), out);
162 }
163 out.closeEntry();
164 }
165 }
166 Files.move(tmpZip, destFile, StandardCopyOption.REPLACE_EXISTING);
167 }
168
169
170
171
172
173 private boolean isModuleDescriptor(String path) {
174 if (path.endsWith(MODULE_DESCRIPTOR_FILE_NAME)) {
175 String prefix = path.substring(0, path.lastIndexOf(MODULE_DESCRIPTOR_FILE_NAME));
176
177
178
179
180 return prefix.isEmpty() || MRJAR_VERSION_AREA.matcher(prefix).matches();
181 } else {
182 return false;
183 }
184 }
185
186
187
188
189
190
191 private String[] getJarToolArguments() throws IOException {
192
193
194
195
196
197
198
199
200
201 File tempEmptyDir = Files.createTempDirectory(null).toFile();
202 tempEmptyDir.deleteOnExit();
203
204 List<String> args = new ArrayList<>();
205
206 args.add("--update");
207 args.add("--file");
208 args.add(getDestFile().getAbsolutePath());
209
210 String mainClass = getModuleMainClass() != null ? getModuleMainClass() : getManifestMainClass();
211
212 if (mainClass != null) {
213 args.add("--main-class");
214 args.add(mainClass);
215 }
216
217 if (getModuleVersion() != null) {
218 args.add("--module-version");
219 args.add(getModuleVersion());
220 }
221
222 if (!isCompress()) {
223 args.add("--no-compress");
224 }
225
226 if (hasJarDateOption) {
227
228 FileTime localTime = revertToLocalTime(getLastModifiedTime());
229 args.add("--date");
230 args.add(localTime.toString());
231 }
232
233 args.add("-C");
234 args.add(tempEmptyDir.getAbsolutePath());
235 args.add(".");
236
237 return args.toArray(new String[0]);
238 }
239
240 private static FileTime revertToLocalTime(FileTime time) {
241 long restoreToLocalTime = time.toMillis();
242 Calendar cal = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT);
243 cal.setTimeInMillis(restoreToLocalTime);
244 restoreToLocalTime = restoreToLocalTime + (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
245 return FileTime.fromMillis(restoreToLocalTime);
246 }
247
248
249
250
251
252
253 private boolean isJarDateOptionSupported(Method runMethod) {
254 try {
255
256 String[] args = {"--date", "2099-12-31T23:59:59Z", "--version"};
257
258 PrintStream nullPrintStream = NullPrintStream.NULL_PRINT_STREAM;
259 Integer result = (Integer) runMethod.invoke(jarTool, nullPrintStream, nullPrintStream, args);
260
261 return result != null && result.intValue() == 0;
262 } catch (ReflectiveOperationException | SecurityException e) {
263 return false;
264 }
265 }
266 }