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.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStreamWriter;
27 import java.io.PrintWriter;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.Enumeration;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36 import java.util.SortedMap;
37 import java.util.StringTokenizer;
38 import java.util.TreeMap;
39
40 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
41 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
42 import org.apache.commons.compress.archivers.zip.ZipFile;
43 import org.apache.commons.compress.parallel.InputStreamSupplier;
44 import org.codehaus.plexus.archiver.ArchiverException;
45 import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
46 import org.codehaus.plexus.archiver.zip.ZipArchiver;
47
48 import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
49 import static org.codehaus.plexus.archiver.util.Streams.fileInputStream;
50 import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;
51
52
53
54
55 @Named("jar")
56 public class JarArchiver extends ZipArchiver {
57
58
59
60
61 private static final String META_INF_NAME = "META-INF";
62
63
64
65
66
67
68 @Deprecated
69 private static final String INDEX_NAME = "META-INF/INDEX.LIST";
70
71
72
73
74 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
75
76
77
78
79 private Manifest configuredManifest;
80
81
82
83
84 private Manifest savedConfiguredManifest;
85
86
87
88
89 private Manifest filesetManifest;
90
91
92
93
94
95 private Manifest originalManifest;
96
97
98
99
100
101 private FilesetManifestConfig filesetManifestConfig;
102
103
104
105
106
107 private boolean mergeManifestsMain = true;
108
109
110
111
112 private Manifest manifest;
113
114
115
116
117
118
119
120 private File manifestFile;
121
122
123
124
125
126
127 @Deprecated
128 private boolean index = false;
129
130
131
132
133
134 private boolean createEmpty = false;
135
136
137
138
139
140
141
142
143 private final List<String> rootEntries;
144
145
146
147
148
149
150 @Deprecated
151 private List<String> indexJars;
152
153
154
155
156 private boolean minimalDefaultManifest = false;
157
158
159
160
161 public JarArchiver() {
162 super();
163 archiveType = "jar";
164 setEncoding("UTF8");
165 rootEntries = new ArrayList<>();
166 }
167
168
169
170
171
172
173
174
175 @Deprecated
176 public void setIndex(boolean flag) {
177 index = flag;
178 }
179
180
181
182
183
184
185 public void setMinimalDefaultManifest(boolean minimalDefaultManifest) {
186 this.minimalDefaultManifest = minimalDefaultManifest;
187 }
188
189 @SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
190 @Deprecated
191 public void setManifestEncoding(String manifestEncoding) {}
192
193
194
195
196
197
198
199
200
201 public void addConfiguredManifest(Manifest newManifest) throws ManifestException {
202 if (configuredManifest == null) {
203 configuredManifest = newManifest;
204 } else {
205 JdkManifestFactory.merge(configuredManifest, newManifest, false);
206 }
207 savedConfiguredManifest = configuredManifest;
208 }
209
210
211
212
213
214
215
216
217
218 @SuppressWarnings({"UnusedDeclaration"})
219 public void setManifest(File manifestFile) throws ArchiverException {
220 if (!manifestFile.exists()) {
221 throw new ArchiverException("Manifest file: " + manifestFile + " does not exist.");
222 }
223
224 this.manifestFile = manifestFile;
225 }
226
227 private Manifest getManifest(File manifestFile) throws ArchiverException {
228 try (InputStream in = fileInputStream(manifestFile)) {
229 return getManifest(in);
230 } catch (IOException e) {
231 throw new ArchiverException(
232 "Unable to read manifest file: " + manifestFile + " (" + e.getMessage() + ")", e);
233 }
234 }
235
236 private Manifest getManifest(InputStream is) throws ArchiverException {
237 try {
238 return new Manifest(is);
239 } catch (IOException e) {
240 throw new ArchiverException("Unable to read manifest file" + " (" + e.getMessage() + ")", e);
241 }
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 @SuppressWarnings({"UnusedDeclaration"})
258 public void setFilesetmanifest(FilesetManifestConfig config) {
259 filesetManifestConfig = config;
260 mergeManifestsMain = FilesetManifestConfig.merge == config;
261
262 if ((filesetManifestConfig != null) && filesetManifestConfig != FilesetManifestConfig.skip) {
263
264 doubleFilePass = true;
265 }
266 }
267
268
269
270
271
272 @Deprecated
273 public void addConfiguredIndexJars(File indexJar) {
274 if (indexJars == null) {
275 indexJars = new ArrayList<>();
276 }
277 indexJars.add(indexJar.getAbsolutePath());
278 }
279
280 @Override
281 protected void initZipOutputStream(ConcurrentJarCreator zOut) throws ArchiverException, IOException {
282 if (!skipWriting) {
283 Manifest jarManifest = createManifest();
284 writeManifest(zOut, jarManifest);
285 }
286 }
287
288 @Override
289 protected boolean hasVirtualFiles() {
290 getLogger().debug("\n\n\nChecking for jar manifest virtual files...\n\n\n");
291 System.out.flush();
292
293 return (configuredManifest != null) || (manifest != null) || (manifestFile != null) || super.hasVirtualFiles();
294 }
295
296
297
298
299
300
301
302
303
304
305 protected Manifest createManifest() throws ArchiverException {
306 Manifest finalManifest = Manifest.getDefaultManifest(minimalDefaultManifest);
307
308 if ((manifest == null) && (manifestFile != null)) {
309
310
311 manifest = getManifest(manifestFile);
312 }
313
314
315
316
317
318
319
320
321 if (isInUpdateMode()) {
322 JdkManifestFactory.merge(finalManifest, originalManifest, false);
323 }
324 JdkManifestFactory.merge(finalManifest, filesetManifest, false);
325 JdkManifestFactory.merge(finalManifest, configuredManifest, false);
326 JdkManifestFactory.merge(finalManifest, manifest, !mergeManifestsMain);
327
328 return finalManifest;
329 }
330
331 private void writeManifest(ConcurrentJarCreator zOut, Manifest manifest) throws IOException, ArchiverException {
332 for (Enumeration<String> e = manifest.getWarnings(); e.hasMoreElements(); ) {
333 getLogger().warn("Manifest warning: " + e.nextElement());
334 }
335
336 zipDir(null, zOut, "META-INF/", DEFAULT_DIR_MODE, getEncoding());
337
338
339 ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
340 manifest.write(baos);
341 InputStreamSupplier in = () -> new ByteArrayInputStream(baos.toByteArray());
342
343 super.zipFile(in, zOut, MANIFEST_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null, false);
344 super.initZipOutputStream(zOut);
345 }
346
347 @Override
348 protected void finalizeZipOutputStream(ConcurrentJarCreator zOut) throws IOException, ArchiverException {
349 if (index) {
350 createIndexList(zOut);
351 }
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 @Deprecated
368 private void createIndexList(ConcurrentJarCreator zOut) throws IOException, ArchiverException {
369 ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
370
371 PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8));
372
373
374 writer.println("JarIndex-Version: 1.0");
375 writer.println();
376
377
378 writer.println(getDestFile().getName());
379
380
381
382 Set<String> filteredDirs = addedDirs.allAddedDirs();
383
384 if (filteredDirs.contains(META_INF_NAME + '/')) {
385 boolean add = false;
386 for (String entry : entries.keySet()) {
387 if (entry.startsWith(META_INF_NAME + '/')
388 && !entry.equals(INDEX_NAME)
389 && !entry.equals(MANIFEST_NAME)) {
390 add = true;
391 break;
392 }
393 }
394 if (!add) {
395 filteredDirs.remove(META_INF_NAME + '/');
396 }
397 }
398 writeIndexLikeList(new ArrayList<>(filteredDirs), rootEntries, writer);
399 writer.println();
400
401 if (indexJars != null) {
402 java.util.jar.Manifest mf = createManifest();
403 String classpath = mf.getMainAttributes().getValue(ManifestConstants.ATTRIBUTE_CLASSPATH);
404 String[] cpEntries = null;
405 if (classpath != null) {
406 StringTokenizer tok = new StringTokenizer(classpath, " ");
407 cpEntries = new String[tok.countTokens()];
408 int c = 0;
409 while (tok.hasMoreTokens()) {
410 cpEntries[c++] = tok.nextToken();
411 }
412 }
413
414 for (String indexJar : indexJars) {
415 String name = findJarName(indexJar, cpEntries);
416 if (name != null) {
417 List<String> dirs = new ArrayList<>();
418 List<String> files = new ArrayList<>();
419 grabFilesAndDirs(indexJar, dirs, files);
420 if (dirs.size() + files.size() > 0) {
421 writer.println(name);
422 writeIndexLikeList(dirs, files, writer);
423 writer.println();
424 }
425 }
426 }
427 }
428
429 writer.flush();
430
431 InputStreamSupplier in = () -> new ByteArrayInputStream(baos.toByteArray());
432
433 super.zipFile(in, zOut, INDEX_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null, true);
434 }
435
436
437
438
439 @Override
440 protected void zipFile(
441 InputStreamSupplier is,
442 ConcurrentJarCreator zOut,
443 String vPath,
444 long lastModified,
445 File fromArchive,
446 int mode,
447 String symlinkDestination,
448 boolean addInParallel)
449 throws IOException, ArchiverException {
450 if (MANIFEST_NAME.equalsIgnoreCase(vPath)) {
451 if (!doubleFilePass || skipWriting) {
452 try (InputStream manifestInputStream = is.get()) {
453 filesetManifest(fromArchive, manifestInputStream);
454 }
455 }
456 } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) {
457 getLogger()
458 .warn("Warning: selected " + archiveType + " files include a META-INF/INDEX.LIST which will"
459 + " be replaced by a newly generated one.");
460 } else {
461 if (index && !vPath.contains("/")) {
462 rootEntries.add(vPath);
463 }
464 super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode, symlinkDestination, addInParallel);
465 }
466 }
467
468 private void filesetManifest(File file, InputStream is) throws ArchiverException {
469 if ((manifestFile != null) && manifestFile.equals(file)) {
470
471
472 getLogger().debug("Found manifest " + file);
473 if (is != null) {
474 manifest = getManifest(is);
475 } else {
476 manifest = getManifest(file);
477 }
478 } else if ((filesetManifestConfig != null) && filesetManifestConfig != FilesetManifestConfig.skip) {
479
480 getLogger().debug("Found manifest to merge in file " + file);
481
482 Manifest newManifest;
483 if (is != null) {
484 newManifest = getManifest(is);
485 } else {
486 newManifest = getManifest(file);
487 }
488
489 if (filesetManifest == null) {
490 filesetManifest = newManifest;
491 } else {
492 JdkManifestFactory.merge(filesetManifest, newManifest, false);
493 }
494 }
495 }
496
497 @Override
498 protected boolean createEmptyZip(File zipFile) throws ArchiverException {
499 if (!createEmpty) {
500 return true;
501 }
502
503 try {
504 getLogger().debug("Building MANIFEST-only jar: " + getDestFile().getAbsolutePath());
505 zipArchiveOutputStream =
506 new ZipArchiveOutputStream(bufferedOutputStream(fileOutputStream(getDestFile(), "jar")));
507
508 zipArchiveOutputStream.setEncoding(getEncoding());
509 if (isCompress()) {
510 zipArchiveOutputStream.setMethod(ZipArchiveOutputStream.DEFLATED);
511 } else {
512 zipArchiveOutputStream.setMethod(ZipArchiveOutputStream.STORED);
513 }
514 ConcurrentJarCreator ps = new ConcurrentJarCreator(
515 isRecompressAddedZips(), Runtime.getRuntime().availableProcessors());
516 initZipOutputStream(ps);
517 finalizeZipOutputStream(ps);
518 } catch (IOException ioe) {
519 throw new ArchiverException("Could not create almost empty JAR archive (" + ioe.getMessage() + ")", ioe);
520 } finally {
521
522
523 createEmpty = false;
524 }
525 return true;
526 }
527
528
529
530
531
532
533
534 @Override
535 protected void cleanUp() throws IOException {
536 super.cleanUp();
537
538
539 if (!doubleFilePass || !skipWriting) {
540 manifest = null;
541 configuredManifest = savedConfiguredManifest;
542 filesetManifest = null;
543 originalManifest = null;
544 }
545 rootEntries.clear();
546 }
547
548
549
550
551
552
553 @Override
554 public void reset() {
555 super.reset();
556 configuredManifest = null;
557 filesetManifestConfig = null;
558 mergeManifestsMain = false;
559 manifestFile = null;
560 index = false;
561 }
562
563 public enum FilesetManifestConfig {
564 skip,
565 merge,
566 mergewithoutmain
567 }
568
569
570
571
572
573
574
575
576
577
578 @Deprecated
579 protected final void writeIndexLikeList(List<String> dirs, List<String> files, PrintWriter writer) {
580
581
582
583 Collections.sort(dirs);
584 Collections.sort(files);
585 for (String dir : dirs) {
586
587 dir = dir.replace('\\', '/');
588 if (dir.startsWith("./")) {
589 dir = dir.substring(2);
590 }
591 while (dir.startsWith("/")) {
592 dir = dir.substring(1);
593 }
594 int pos = dir.lastIndexOf('/');
595 if (pos != -1) {
596 dir = dir.substring(0, pos);
597 }
598
599
600 writer.println(dir);
601 }
602
603 for (String file : files) {
604 writer.println(file);
605 }
606 }
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628 protected static String findJarName(String fileName, String[] classpath) {
629 if (classpath == null) {
630 return new File(fileName).getName();
631 }
632 fileName = fileName.replace(File.separatorChar, '/');
633
634
635 SortedMap<String, String> matches =
636 new TreeMap<>(Comparator.comparingInt(String::length).reversed());
637
638 for (String aClasspath : classpath) {
639 if (fileName.endsWith(aClasspath)) {
640 matches.put(aClasspath, aClasspath);
641 } else {
642 int slash = aClasspath.indexOf("/");
643 String candidate = aClasspath;
644 while (slash > -1) {
645 candidate = candidate.substring(slash + 1);
646 if (fileName.endsWith(candidate)) {
647 matches.put(candidate, aClasspath);
648 break;
649 }
650 slash = candidate.indexOf("/");
651 }
652 }
653 }
654
655 return matches.size() == 0 ? null : matches.get(matches.firstKey());
656 }
657
658
659
660
661
662
663
664
665
666
667
668 private void grabFilesAndDirs(String file, List<String> dirs, List<String> files) throws IOException {
669 File zipFile = new File(file);
670 if (!zipFile.exists()) {
671 getLogger().error("JarArchive skipping non-existing file: " + zipFile.getAbsolutePath());
672 } else if (zipFile.isDirectory()) {
673 getLogger().info("JarArchiver skipping indexJar " + zipFile + " because it is not a jar");
674 } else {
675 try (ZipFile zf = new ZipFile(file, "utf-8")) {
676 Enumeration<ZipArchiveEntry> entries = zf.getEntries();
677 HashSet<String> dirSet = new HashSet<>();
678 while (entries.hasMoreElements()) {
679 ZipArchiveEntry ze = entries.nextElement();
680 String name = ze.getName();
681
682 if (!name.equals(META_INF_NAME)
683 && !name.equals(META_INF_NAME + '/')
684 && !name.equals(INDEX_NAME)
685 && !name.equals(MANIFEST_NAME)) {
686 if (ze.isDirectory()) {
687 dirSet.add(name);
688 } else if (!name.contains("/")) {
689 files.add(name);
690 } else {
691
692
693
694
695 dirSet.add(name.substring(0, name.lastIndexOf("/") + 1));
696 }
697 }
698 }
699 dirs.addAll(dirSet);
700 }
701 }
702 }
703
704
705
706
707
708
709
710 @Override
711 protected void setZipEntryTime(ZipArchiveEntry zipEntry, long lastModifiedTime) {
712 if (getLastModifiedTime() != null) {
713 lastModifiedTime = getLastModifiedTime().toMillis();
714 }
715
716
717 zipEntry.setTime(lastModifiedTime);
718 }
719 }