• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jreleaser / jreleaser / #486

23 May 2025 05:11PM UTC coverage: 48.584% (-0.09%) from 48.67%
#486

push

github

aalmiray
feat(core): Add a flag to skip non-configured sections. The yolo flag.

Closes #1840

160 of 217 new or added lines in 57 files covered. (73.73%)

438 existing lines in 34 files now uncovered.

25292 of 52058 relevant lines covered (48.58%)

0.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.92
/api/jreleaser-utils/src/main/java/org/jreleaser/util/FileUtils.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.util;
19

20
import com.github.luben.zstd.Zstd;
21
import org.apache.commons.compress.archivers.ArchiveEntry;
22
import org.apache.commons.compress.archivers.ArchiveException;
23
import org.apache.commons.compress.archivers.ArchiveInputStream;
24
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
25
import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
26
import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
27
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
28
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
29
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
30
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
31
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
32
import org.apache.commons.compress.archivers.zip.ZipFile;
33
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
34
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
35
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
36
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
37
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
38
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
39
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
40
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
41
import org.apache.commons.io.IOUtils;
42
import org.jreleaser.bundle.RB;
43
import org.jreleaser.logging.JReleaserLogger;
44

45
import java.io.BufferedInputStream;
46
import java.io.ByteArrayOutputStream;
47
import java.io.File;
48
import java.io.IOException;
49
import java.io.InputStream;
50
import java.io.OutputStream;
51
import java.nio.file.FileAlreadyExistsException;
52
import java.nio.file.FileSystemLoopException;
53
import java.nio.file.FileVisitResult;
54
import java.nio.file.FileVisitor;
55
import java.nio.file.Files;
56
import java.nio.file.Path;
57
import java.nio.file.Paths;
58
import java.nio.file.SimpleFileVisitor;
59
import java.nio.file.attribute.BasicFileAttributes;
60
import java.nio.file.attribute.FileAttribute;
61
import java.nio.file.attribute.FileTime;
62
import java.nio.file.attribute.PosixFileAttributeView;
63
import java.nio.file.attribute.PosixFilePermission;
64
import java.nio.file.attribute.PosixFilePermissions;
65
import java.time.ZonedDateTime;
66
import java.util.ArrayList;
67
import java.util.Comparator;
68
import java.util.EnumSet;
69
import java.util.Enumeration;
70
import java.util.Iterator;
71
import java.util.LinkedHashSet;
72
import java.util.List;
73
import java.util.Locale;
74
import java.util.Optional;
75
import java.util.Set;
76
import java.util.TreeSet;
77
import java.util.function.Consumer;
78
import java.util.function.Function;
79
import java.util.function.Predicate;
80
import java.util.stream.Stream;
81
import java.util.zip.ZipOutputStream;
82

83
import static java.nio.file.FileVisitResult.CONTINUE;
84
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
85
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
86
import static java.nio.file.StandardOpenOption.CREATE;
87
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
88
import static java.util.Collections.unmodifiableList;
89
import static java.util.Collections.unmodifiableSet;
90
import static org.jreleaser.util.FileType.TAR;
91
import static org.jreleaser.util.FileType.TAR_BZ2;
92
import static org.jreleaser.util.FileType.TAR_GZ;
93
import static org.jreleaser.util.FileType.TAR_XZ;
94
import static org.jreleaser.util.FileType.TAR_ZST;
95
import static org.jreleaser.util.FileType.TBZ2;
96
import static org.jreleaser.util.FileType.TGZ;
97
import static org.jreleaser.util.FileType.TXZ;
98
import static org.jreleaser.util.FileType.ZIP;
99
import static org.jreleaser.util.StringUtils.getFilename;
100
import static org.jreleaser.util.StringUtils.isBlank;
101
import static org.jreleaser.util.StringUtils.isNotBlank;
102

103
/**
104
 * @author Andres Almiray
105
 * @since 0.1.0
106
 */
107
public final class FileUtils {
108
    private static final String[] LICENSE_FILE_NAMES = {
1✔
109
        "LICENSE",
110
        "LICENSE.txt",
111
        "LICENSE.md",
112
        "LICENSE.adoc"
113
    };
114

115
    private static final String[] TAR_COMPRESSED_EXTENSIONS = {
1✔
116
        TAR_BZ2.extension(),
1✔
117
        TAR_GZ.extension(),
1✔
118
        TAR_XZ.extension(),
1✔
119
        TAR_ZST.extension(),
1✔
120
        TBZ2.extension(),
1✔
121
        TGZ.extension(),
1✔
122
        TXZ.extension()
1✔
123
    };
124

125
    private FileUtils() {
126
        //noop
127
    }
128

129
    public static void listFilesAndConsume(Path path, Consumer<Stream<Path>> consumer) throws IOException {
130
        if (!Files.exists(path)) return;
1✔
131
        try (Stream<Path> files = Files.list(path)) {
1✔
132
            consumer.accept(files);
1✔
133
        }
134
    }
1✔
135

136
    public static <T> Optional<T> listFilesAndProcess(Path path, Function<Stream<Path>, T> function) throws IOException {
137
        if (!Files.exists(path)) return Optional.empty();
1✔
138
        try (Stream<Path> files = Files.list(path)) {
1✔
139
            return Optional.ofNullable(function.apply(files));
1✔
140
        }
141
    }
142

143
    public static Optional<Path> findLicenseFile(Path basedir) {
144
        for (String licenseFilename : LICENSE_FILE_NAMES) {
1✔
145
            Path path = basedir.resolve(licenseFilename);
1✔
146
            if (Files.exists(path)) {
1✔
147
                return Optional.of(path);
1✔
148
            }
149
            path = basedir.resolve(licenseFilename.toLowerCase(Locale.ENGLISH));
×
150
            if (Files.exists(path)) {
×
151
                return Optional.of(path);
×
152
            }
153
        }
154

155
        return Optional.empty();
×
156
    }
157

158
    public static Path resolveOutputDirectory(Path basedir, Path outputdir, String baseOutput) {
159
        String od = Env.resolve("OUTPUT_DIRECTORY", "");
1✔
160
        if (isNotBlank(od)) {
1✔
161
            return basedir.resolve(od).resolve("jreleaser").normalize();
1✔
162
        }
163
        if (null != outputdir) {
×
164
            return basedir.resolve(outputdir).resolve("jreleaser").normalize();
×
165
        }
166
        return basedir.resolve(baseOutput).resolve("jreleaser").normalize();
×
167
    }
168

169
    public static void zip(Path src, Path dest) throws IOException {
170
        zip(src, dest, new ArchiveOptions());
1✔
171
    }
1✔
172

173
    public static void zip(Path src, Path dest, ArchiveOptions options) throws IOException {
174
        try (ZipArchiveOutputStream out = new ZipArchiveOutputStream(dest.toFile())) {
1✔
175
            out.setMethod(ZipOutputStream.DEFLATED);
1✔
176

177
            Set<Path> paths = !options.getIncludedPaths().isEmpty() ? options.getIncludedPaths() : collectPaths(src);
1✔
178
            FileTime fileTime = null != options.getTimestamp() ? FileTime.from(options.getTimestamp().toInstant()) : null;
1✔
179
            String rootEntryName = options.getRootEntryName();
1✔
180
            if (null == rootEntryName) {
1✔
181
                rootEntryName = "";
1✔
182
            } else if (!rootEntryName.endsWith("/")) {
×
183
                rootEntryName += "/";
×
184
            }
185

186
            Set<String> entryNames = new TreeSet<>();
1✔
187
            for (Path path : paths) {
1✔
188
                String entryName = rootEntryName + src.relativize(path);
1✔
189
                entryNames.add(entryName);
1✔
190

191
                if (options.isCreateIntermediateDirs()) {
1✔
192
                    Path parentPath = Paths.get(entryName).getParent();
×
193
                    if (null != parentPath) {
×
194
                        Iterator<Path> it = parentPath.iterator();
×
195
                        List<String> directories = new ArrayList<>();
×
196
                        while (it.hasNext()) {
×
197
                            Path directoryPath = it.next();
×
198
                            directories.add(directoryPath.getFileName().toString());
×
199
                            String directoryEntryName = String.join("/", directories) + "/";
×
200
                            if ("./".equals(directoryEntryName)) continue;
×
201
                            if (!entryNames.contains(directoryEntryName)) {
×
202
                                ZipArchiveEntry archiveEntry = new ZipArchiveEntry(directoryEntryName);
×
203
                                if (null != fileTime) archiveEntry.setTime(fileTime);
×
204
                                out.putArchiveEntry(archiveEntry);
×
205
                                out.closeArchiveEntry();
×
206
                                entryNames.add(directoryEntryName);
×
207
                            }
208
                        }
×
209
                    }
210
                }
211

212
                File inputFile = path.toFile();
1✔
213
                ZipArchiveEntry archiveEntry = new ZipArchiveEntry(inputFile, entryName);
1✔
214
                if (null != fileTime) archiveEntry.setTime(fileTime);
1✔
215

216
                archiveEntry.setMethod(ZipOutputStream.DEFLATED);
1✔
217
                if (inputFile.isFile() && Files.isExecutable(path)) {
1✔
218
                    archiveEntry.setUnixMode(0100755);
1✔
219
                }
220

221
                out.putArchiveEntry(archiveEntry);
1✔
222

223
                if (inputFile.isFile()) {
1✔
224
                    out.write(Files.readAllBytes(path));
1✔
225
                }
226
                out.closeArchiveEntry();
1✔
227
            }
1✔
228
        }
229
    }
1✔
230

231
    public static void ar(Path src, Path dest) throws IOException {
232
        ar(src, dest, new ArchiveOptions());
×
233
    }
×
234

235
    public static void ar(Path src, Path dest, ArchiveOptions options) throws IOException {
236
        try (ArArchiveOutputStream out = new ArArchiveOutputStream(
1✔
237
            Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING))) {
1✔
238
            ar(src, out, options);
1✔
239
        }
240
    }
1✔
241

242
    public static void tar(Path src, Path dest) throws IOException {
243
        tar(src, dest, new ArchiveOptions());
×
244
    }
×
245

246
    public static void tar(Path src, Path dest, ArchiveOptions options) throws IOException {
247
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
248
            Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING))) {
1✔
249
            tar(src, out, options);
1✔
250
        }
251
    }
1✔
252

253
    public static void tgz(Path src, Path dest) throws IOException {
254
        tgz(src, dest, new ArchiveOptions());
×
255
    }
×
256

257
    public static void tgz(Path src, Path dest, ArchiveOptions options) throws IOException {
258
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
259
            new GzipCompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING)))) {
1✔
260
            tar(src, out, options);
1✔
261
        }
262
    }
1✔
263

264
    public static void bz2(Path src, Path dest) throws IOException {
265
        bz2(src, dest, new ArchiveOptions());
×
266
    }
×
267

268
    public static void bz2(Path src, Path dest, ArchiveOptions options) throws IOException {
269
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
270
            new BZip2CompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING)))) {
1✔
271
            tar(src, out, options);
1✔
272
        }
273
    }
1✔
274

275
    public static void xz(Path src, Path dest) throws IOException {
276
        xz(src, dest, new ArchiveOptions());
×
277
    }
×
278

279
    public static void xz(Path src, Path dest, ArchiveOptions options) throws IOException {
280
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
281
            new XZCompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING)))) {
1✔
282
            tar(src, out, options);
1✔
283
        }
284
    }
1✔
285

286
    public static void zst(Path src, Path dest) throws IOException {
287
        zst(src, dest, new ArchiveOptions());
×
288
    }
×
289

290
    public static void zst(Path src, Path dest, ArchiveOptions options) throws IOException {
291
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
292
            new ZstdCompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING),
1✔
293
                Zstd.defaultCompressionLevel(), true))) {
1✔
294
            tar(src, out, options);
1✔
295
        }
296
    }
1✔
297

298
    private static void tar(Path src, TarArchiveOutputStream out, ArchiveOptions options) throws IOException {
299
        Set<Path> paths = !options.getIncludedPaths().isEmpty() ? options.getIncludedPaths() : collectPaths(src);
1✔
300

301
        out.setLongFileMode(options.getLongFileMode().toLongFileMode());
1✔
302
        out.setBigNumberMode(options.getBigNumberMode().toBigNumberMode());
1✔
303

304
        FileTime fileTime = null != options.getTimestamp() ? FileTime.from(options.getTimestamp().toInstant()) : null;
1✔
305
        String rootEntryName = options.getRootEntryName();
1✔
306
        if (null == rootEntryName) {
1✔
307
            rootEntryName = "";
1✔
308
        } else if (!rootEntryName.endsWith("/")) {
1✔
309
            rootEntryName += "/";
1✔
310
        }
311

312
        Set<String> entryNames = new TreeSet<>();
1✔
313
        for (Path path : paths) {
1✔
314
            String entryName = rootEntryName + src.relativize(path);
1✔
315
            entryNames.add(entryName);
1✔
316

317
            if (options.isCreateIntermediateDirs()) {
1✔
318
                Path parentPath = Paths.get(entryName).getParent();
1✔
319
                if (null != parentPath) {
1✔
320
                    Iterator<Path> it = parentPath.iterator();
1✔
321
                    List<String> directories = new ArrayList<>();
1✔
322
                    while (it.hasNext()) {
1✔
323
                        Path directoryPath = it.next();
1✔
324
                        directories.add(directoryPath.getFileName().toString());
1✔
325
                        String directoryEntryName = String.join("/", directories) + "/";
1✔
326
                        if ("./".equals(directoryEntryName)) continue;
1✔
327
                        if (!entryNames.contains(directoryEntryName)) {
1✔
328
                            TarArchiveEntry archiveEntry = new TarArchiveEntry(directoryEntryName);
1✔
329
                            if (null != fileTime) archiveEntry.setModTime(fileTime);
1✔
330
                            out.putArchiveEntry(archiveEntry);
1✔
331
                            out.closeArchiveEntry();
1✔
332
                            entryNames.add(directoryEntryName);
1✔
333
                        }
334
                    }
1✔
335
                }
336
            }
337

338
            File inputFile = path.toFile();
1✔
339
            TarArchiveEntry archiveEntry = out.createArchiveEntry(inputFile, entryName);
1✔
340
            if (null != fileTime) archiveEntry.setModTime(fileTime);
1✔
341

342
            if (inputFile.isFile() && Files.isExecutable(path)) {
1✔
343
                archiveEntry.setMode(0100755);
1✔
344
            }
345

346
            out.putArchiveEntry(archiveEntry);
1✔
347

348
            if (inputFile.isFile()) {
1✔
349
                out.write(Files.readAllBytes(path));
1✔
350
            }
351

352
            out.closeArchiveEntry();
1✔
353
        }
1✔
354
    }
1✔
355

356
    private static void ar(Path src, ArArchiveOutputStream out, ArchiveOptions options) throws IOException {
357
        Set<Path> paths = !options.getIncludedPaths().isEmpty() ? options.getIncludedPaths() : collectPaths(src);
1✔
358

359
        out.setLongFileMode(options.getLongFileMode().toLongFileMode());
1✔
360
        String rootEntryName = options.getRootEntryName();
1✔
361
        if (null == rootEntryName) {
1✔
362
            rootEntryName = "";
1✔
363
        } else if (!rootEntryName.endsWith("/")) {
×
364
            rootEntryName += "/";
×
365
        }
366

367
        Set<String> entryNames = new TreeSet<>();
1✔
368
        for (Path path : paths) {
1✔
369
            String entryName = rootEntryName + src.relativize(path);
1✔
370

371
            entryNames.add(entryName);
1✔
372

373
            if (options.isCreateIntermediateDirs()) {
1✔
374
                Path parentPath = Paths.get(entryName).getParent();
×
375
                if (null != parentPath) {
×
376
                    Iterator<Path> it = parentPath.iterator();
×
377
                    List<String> directories = new ArrayList<>();
×
378
                    while (it.hasNext()) {
×
379
                        Path directoryPath = it.next();
×
380
                        directories.add(directoryPath.getFileName().toString());
×
381
                        String directoryEntryName = String.join("/", directories) + "/";
×
382
                        if ("./".equals(directoryEntryName)) continue;
×
383
                        if (!entryNames.contains(directoryEntryName)) {
×
384
                            ArArchiveEntry archiveEntry = new ArArchiveEntry(directoryEntryName, directoryEntryName.length());
×
385
                            out.putArchiveEntry(archiveEntry);
×
386
                            out.closeArchiveEntry();
×
387
                            entryNames.add(directoryEntryName);
×
388
                        }
389
                    }
×
390
                }
391
            }
392

393
            File inputFile = path.toFile();
1✔
394
            ArArchiveEntry archiveEntry = out.createArchiveEntry(inputFile, entryName);
1✔
395

396
            out.putArchiveEntry(archiveEntry);
1✔
397

398
            if (inputFile.isFile()) {
1✔
399
                out.write(Files.readAllBytes(path));
1✔
400
            }
401

402
            out.closeArchiveEntry();
1✔
403
        }
1✔
404
    }
1✔
405

406
    private static TreeSet<Path> collectPaths(Path src) throws IOException {
407
        TreeSet<Path> paths = new TreeSet<>();
1✔
408
        Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
1✔
409
            @Override
410
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
411
                paths.add(file);
1✔
412
                return super.visitFile(file, attrs);
1✔
413
            }
414
        });
415
        return paths;
1✔
416
    }
417

418
    public static class ArchiveOptions {
1✔
419
        private final Set<Path> includedPaths = new LinkedHashSet<>();
1✔
420
        private String rootEntryName;
421
        private ZonedDateTime timestamp;
422
        private TarMode longFileMode = TarMode.ERROR;
1✔
423
        private TarMode bigNumberMode = TarMode.ERROR;
1✔
424
        private boolean createIntermediateDirs;
425

426
        public boolean isCreateIntermediateDirs() {
427
            return createIntermediateDirs;
1✔
428
        }
429

430
        public String getRootEntryName() {
431
            return rootEntryName;
1✔
432
        }
433

434
        public ZonedDateTime getTimestamp() {
435
            return timestamp;
1✔
436
        }
437

438
        public TarMode getLongFileMode() {
439
            return longFileMode;
1✔
440
        }
441

442
        public TarMode getBigNumberMode() {
443
            return bigNumberMode;
1✔
444
        }
445

446
        public Set<Path> getIncludedPaths() {
447
            return includedPaths;
1✔
448
        }
449

450
        public ArchiveOptions withCreateIntermediateDirs(boolean createIntermediateDirs) {
451
            this.createIntermediateDirs = createIntermediateDirs;
1✔
452
            return this;
1✔
453
        }
454

455
        public ArchiveOptions withIncludedPath(Path path) {
456
            this.includedPaths.add(path);
1✔
457
            return this;
1✔
458
        }
459

460
        public ArchiveOptions withRootEntryName(String rootEntryName) {
461
            this.rootEntryName = rootEntryName;
1✔
462
            return this;
1✔
463
        }
464

465
        public ArchiveOptions withTimestamp(ZonedDateTime timestamp) {
466
            this.timestamp = timestamp;
1✔
467
            return this;
1✔
468
        }
469

470
        public ArchiveOptions withLongFileMode(TarMode longFileMode) {
471
            if (null != longFileMode) this.longFileMode = longFileMode;
1✔
472
            return this;
1✔
473
        }
474

475
        public ArchiveOptions withBigNumberMode(TarMode bigNumberMode) {
476
            if (null != bigNumberMode) this.bigNumberMode = bigNumberMode;
1✔
477
            return this;
1✔
478
        }
479

480
        public enum TarMode {
1✔
481
            GNU,
1✔
482
            POSIX,
1✔
483
            ERROR,
1✔
484
            TRUNCATE;
1✔
485

486
            public String formatted() {
487
                return name().toLowerCase(Locale.ENGLISH);
×
488
            }
489

490
            public static TarMode of(String str) {
491
                if (isBlank(str)) return null;
1✔
492
                return valueOf(str.toUpperCase(Locale.ENGLISH).trim());
1✔
493
            }
494

495
            public int toLongFileMode() {
496
                switch (this) {
1✔
497
                    case GNU:
498
                        return TarArchiveOutputStream.LONGFILE_GNU;
×
499
                    case POSIX:
500
                        return TarArchiveOutputStream.LONGFILE_POSIX;
1✔
501
                    case TRUNCATE:
502
                        return TarArchiveOutputStream.LONGFILE_TRUNCATE;
×
503
                    case ERROR:
504
                    default:
505
                        return TarArchiveOutputStream.LONGFILE_ERROR;
1✔
506
                }
507
            }
508

509
            public int toBigNumberMode() {
510
                switch (this) {
1✔
511
                    case GNU:
512
                        return TarArchiveOutputStream.BIGNUMBER_STAR;
×
513
                    case POSIX:
514
                        return TarArchiveOutputStream.BIGNUMBER_POSIX;
×
515
                    case ERROR:
516
                    default:
517
                        return TarArchiveOutputStream.BIGNUMBER_ERROR;
1✔
518
                }
519
            }
520
        }
521
    }
522

523
    public static void packArchive(Path src, Path dest) throws IOException {
524
        packArchive(src, dest, new ArchiveOptions());
×
525
    }
×
526

527
    public static void packArchive(Path src, Path dest, ArchiveOptions options) throws IOException {
528
        String filename = dest.getFileName().toString();
1✔
529
        if (filename.endsWith(ZIP.extension())) {
1✔
530
            zip(src, dest, options);
1✔
531
        } else if (filename.endsWith(TAR_BZ2.extension()) || filename.endsWith(TBZ2.extension())) {
1✔
532
            bz2(src, dest, options);
1✔
533
        } else if (filename.endsWith(TAR_GZ.extension()) || filename.endsWith(TGZ.extension())) {
1✔
534
            tgz(src, dest, options);
1✔
535
        } else if (filename.endsWith(TAR_XZ.extension()) || filename.endsWith(TXZ.extension())) {
1✔
536
            xz(src, dest, options);
1✔
537
        } else if (filename.endsWith(TAR_ZST.extension())) {
1✔
538
            zst(src, dest, options);
1✔
539
        } else if (filename.endsWith(TAR.extension())) {
1✔
540
            tar(src, dest, options);
1✔
541
        }
542
    }
1✔
543

544
    public static void unpackArchive(Path src, Path dest) throws IOException {
545
        unpackArchive(src, dest, true);
1✔
546
    }
1✔
547

548
    public static void unpackArchive(Path src, Path dest, boolean removeRootEntry) throws IOException {
549
        unpackArchive(src, dest, removeRootEntry, true);
1✔
550
    }
1✔
551

552
    public static void unpackArchive(Path src, Path dest, boolean removeRootEntry, boolean cleanDirectory) throws IOException {
553
        String filename = src.getFileName().toString();
1✔
554

555
        for (String extension : TAR_COMPRESSED_EXTENSIONS) {
1✔
556
            if (filename.endsWith(extension)) {
1✔
557
                unpackArchiveCompressed(src, dest, removeRootEntry);
1✔
558
                return;
1✔
559
            }
560
        }
561

562
        if (cleanDirectory) deleteFiles(dest, true);
1✔
563
        File destinationDir = dest.toFile();
1✔
564

565
        String rootEntryName = resolveRootEntryName(src);
1✔
566
        if (filename.endsWith(ZIP.extension())) {
1✔
567
            try (ZipFile zipFile = ZipFile.builder().setFile(src.toFile()).get()) {
1✔
568
                unpackArchive(removeRootEntry ? rootEntryName + "/" : "", destinationDir, zipFile);
1✔
569
            }
570
            return;
1✔
571
        }
572

573
        try (InputStream fi = Files.newInputStream(src);
1✔
574
             InputStream bi = new BufferedInputStream(fi);
1✔
575
             ArchiveInputStream<?> in = new ArchiveStreamFactory().createArchiveInputStream(bi)) {
1✔
576

577
            unpackArchive(removeRootEntry ? rootEntryName + "/" : "", destinationDir, in);
1✔
578
        } catch (ArchiveException e) {
×
579
            throw new IOException(e.getMessage(), e);
×
580
        }
1✔
581
    }
1✔
582

583
    public static void unpackArchiveCompressed(Path src, Path dest) throws IOException {
584
        unpackArchiveCompressed(src, dest, true);
×
585
    }
×
586

587
    public static void unpackArchiveCompressed(Path src, Path dest, boolean removeRootEntry) throws IOException {
588
        unpackArchiveCompressed(src, dest, removeRootEntry, true);
1✔
589
    }
1✔
590

591
    public static void unpackArchiveCompressed(Path src, Path dest, boolean removeRootEntry, boolean cleanDirectory) throws IOException {
592
        if (cleanDirectory) deleteFiles(dest, true);
1✔
593
        File destinationDir = dest.toFile();
1✔
594

595
        String filename = src.getFileName().toString();
1✔
596
        String artifactFileName = getFilename(filename, FileType.getSupportedExtensions());
1✔
597
        String rootEntryName = resolveRootEntryName(src);
1✔
598
        String artifactExtension = filename.substring(artifactFileName.length());
1✔
599
        String artifactFileFormat = artifactExtension.substring(1);
1✔
600
        FileType fileType = FileType.of(artifactFileFormat);
1✔
601

602
        try (InputStream fi = Files.newInputStream(src);
1✔
603
             InputStream bi = new BufferedInputStream(fi);
1✔
604
             InputStream gzi = resolveCompressorInputStream(fileType, bi);
1✔
605
             ArchiveInputStream<?> in = new TarArchiveInputStream(gzi)) {
1✔
606
            unpackArchive(removeRootEntry ? rootEntryName + "/" : "", destinationDir, in);
1✔
607
        }
608
    }
1✔
609

610
    private static InputStream resolveCompressorInputStream(FileType fileType, InputStream in) throws IOException {
611
        switch (fileType) {
1✔
612
            case TGZ:
613
            case TAR_GZ:
614
                return new GzipCompressorInputStream(in);
1✔
615
            case TBZ2:
616
            case TAR_BZ2:
617
                return new BZip2CompressorInputStream(in);
1✔
618
            case TXZ:
619
            case TAR_XZ:
620
                return new XZCompressorInputStream(in);
1✔
621
            case TAR_ZST:
622
                return new ZstdCompressorInputStream(in);
1✔
623
            default:
624
                // noop
625
                break;
626
        }
627

628
        return null;
×
629
    }
630

631
    private static void unpackArchive(String basename, File destinationDir, ArchiveInputStream<?> in) throws IOException {
632
        ArchiveEntry entry = null;
1✔
633
        while (null != (entry = in.getNextEntry())) {
1✔
634
            if (!in.canReadEntryData(entry)) {
1✔
635
                // log something?
636
                continue;
×
637
            }
638

639
            String entryName = entry.getName();
1✔
640
            if (isNotBlank(basename) && entryName.startsWith(basename) && entryName.length() > basename.length() + 1) {
1✔
641
                entryName = entryName.substring(basename.length());
1✔
642
            }
643

644
            File file = new File(destinationDir, entryName);
1✔
645
            String destDirPath = destinationDir.getCanonicalPath();
1✔
646
            String destFilePath = file.getCanonicalPath();
1✔
647
            if (!destFilePath.startsWith(destDirPath + File.separator)) {
1✔
648
                throw new IOException(RB.$("ERROR_files_unpack_outside_target", entry.getName()));
×
649
            }
650

651
            if (entry.isDirectory()) {
1✔
UNCOV
652
                if (!file.isDirectory() && !file.mkdirs()) {
×
653
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", file));
×
654
                }
655
            } else {
656
                File parent = file.getParentFile();
1✔
657
                if (!parent.isDirectory() && !parent.mkdirs()) {
1✔
658
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", parent));
×
659
                }
660

661
                if (isSymbolicLink(entry)) {
1✔
662
                    Files.createSymbolicLink(file.toPath(), Paths.get(getLinkName(in, entry)));
×
663
                } else {
664
                    try (OutputStream o = Files.newOutputStream(file.toPath())) {
1✔
665
                        IOUtils.copy(in, o);
1✔
666
                        Files.setLastModifiedTime(file.toPath(), FileTime.from(entry.getLastModifiedDate().toInstant()));
1✔
667
                        chmod(file, getEntryMode(entry, file));
1✔
668
                    }
669
                }
670
            }
671
        }
1✔
672
    }
1✔
673

674
    private static void unpackArchive(String basename, File destinationDir, ZipFile zipFile) throws IOException {
675
        Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
1✔
676
        while (entries.hasMoreElements()) {
1✔
677
            ZipArchiveEntry entry = entries.nextElement();
1✔
678
            if (!zipFile.canReadEntryData(entry)) {
1✔
679
                // log something?
680
                continue;
×
681
            }
682

683
            String entryName = entry.getName();
1✔
684
            if (isNotBlank(basename) && entryName.startsWith(basename) && entryName.length() > basename.length() + 1) {
1✔
685
                entryName = entryName.substring(basename.length());
1✔
686
            }
687

688
            File file = new File(destinationDir, entryName);
1✔
689
            String destDirPath = destinationDir.getCanonicalPath();
1✔
690
            String destFilePath = file.getCanonicalPath();
1✔
691
            if (!destFilePath.startsWith(destDirPath + File.separator)) {
1✔
692
                throw new IOException(RB.$("ERROR_files_unpack_outside_target", entry.getName()));
×
693
            }
694

695
            if (entry.isDirectory()) {
1✔
696
                if (!file.isDirectory() && !file.mkdirs()) {
1✔
697
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", file));
×
698
                }
699
            } else {
700
                File parent = file.getParentFile();
1✔
701
                if (!parent.isDirectory() && !parent.mkdirs()) {
1✔
702
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", parent));
×
703
                }
704

705
                if (entry.isUnixSymlink()) {
1✔
706
                    Files.createSymbolicLink(file.toPath(), Paths.get(zipFile.getUnixSymlink(entry)));
×
707
                } else {
708
                    try (OutputStream o = Files.newOutputStream(file.toPath())) {
1✔
709
                        IOUtils.copy(zipFile.getInputStream(entry), o);
1✔
710
                        Files.setLastModifiedTime(file.toPath(), FileTime.from(entry.getLastModifiedDate().toInstant()));
1✔
711
                        chmod(file, getEntryMode(entry, file));
1✔
712
                    }
713
                }
714
            }
715
        }
1✔
716
    }
1✔
717

718
    private static boolean isSymbolicLink(ArchiveEntry entry) {
719
        if (entry instanceof ZipArchiveEntry) {
1✔
720
            return ((ZipArchiveEntry) entry).isUnixSymlink();
×
721
        } else if (entry instanceof TarArchiveEntry) {
1✔
722
            return ((TarArchiveEntry) entry).isSymbolicLink();
1✔
723
        }
724
        return false;
×
725
    }
726

727
    private static String getLinkName(ArchiveInputStream<?> in, ArchiveEntry entry) throws IOException {
728
        if (entry instanceof ZipArchiveEntry) {
×
729
            try (ByteArrayOutputStream o = new ByteArrayOutputStream()) {
×
730
                IOUtils.copy(in, o);
×
731
                return IoUtils.toString(o);
×
732
            }
733
        } else if (entry instanceof TarArchiveEntry) {
×
734
            return ((TarArchiveEntry) entry).getLinkName();
×
735
        }
736
        return "";
×
737
    }
738

739
    private static int getEntryMode(ArchiveEntry entry, File file) {
740
        if (entry instanceof TarArchiveEntry) {
1✔
741
            return getEntryMode(entry, ((TarArchiveEntry) entry).getMode(), file);
1✔
742
        }
743
        return getEntryMode(entry, ((ZipArchiveEntry) entry).getUnixMode(), file);
1✔
744
    }
745

746
    private static int getEntryMode(ArchiveEntry entry, int mode, File file) {
747
        int unixMode = mode & 0777;
1✔
748
        if (unixMode == 0) {
1✔
749
            if (entry.isDirectory()) {
1✔
750
                unixMode = 0755;
×
751
            } else if ("bin".equalsIgnoreCase(file.getParentFile().getName())) {
1✔
752
                // zipEntry.unixMode returns 0 most times even if the entry is executable
753
                // force executable bit only if parent dir == 'bin'
754
                unixMode = 0777;
×
755
            } else {
756
                unixMode = 0644;
1✔
757
            }
758
        }
759
        return unixMode;
1✔
760
    }
761

762
    public static void chmod(File file, int mode) throws IOException {
763
        chmod(file.toPath(), mode);
1✔
764
    }
1✔
765

766
    public static void chmod(Path path, int mode) throws IOException {
767
        if (supportsPosix(path)) {
1✔
768
            PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
1✔
769
            fileAttributeView.setPermissions(convertToPermissionsSet(mode));
1✔
770
        } else {
1✔
771
            path.toFile().setExecutable(true);
×
772
        }
773
    }
1✔
774

775
    private static boolean supportsPosix(Path path) {
776
        return path.getFileSystem().supportedFileAttributeViews().contains("posix");
1✔
777
    }
778

779
    private static Set<PosixFilePermission> convertToPermissionsSet(int mode) {
780
        Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class);
1✔
781

782
        if ((mode & 256) == 256) {
1✔
783
            result.add(PosixFilePermission.OWNER_READ);
1✔
784
        }
785

786
        if ((mode & 128) == 128) {
1✔
787
            result.add(PosixFilePermission.OWNER_WRITE);
1✔
788
        }
789

790
        if ((mode & 64) == 64) {
1✔
791
            result.add(PosixFilePermission.OWNER_EXECUTE);
1✔
792
        }
793

794
        if ((mode & 32) == 32) {
1✔
795
            result.add(PosixFilePermission.GROUP_READ);
1✔
796
        }
797

798
        if ((mode & 16) == 16) {
1✔
799
            result.add(PosixFilePermission.GROUP_WRITE);
×
800
        }
801

802
        if ((mode & 8) == 8) {
1✔
803
            result.add(PosixFilePermission.GROUP_EXECUTE);
1✔
804
        }
805

806
        if ((mode & 4) == 4) {
1✔
807
            result.add(PosixFilePermission.OTHERS_READ);
1✔
808
        }
809

810
        if ((mode & 2) == 2) {
1✔
811
            result.add(PosixFilePermission.OTHERS_WRITE);
×
812
        }
813

814
        if ((mode & 1) == 1) {
1✔
815
            result.add(PosixFilePermission.OTHERS_EXECUTE);
1✔
816
        }
817

818
        return result;
1✔
819
    }
820

821
    public static List<String> inspectArchive(Path src) throws IOException {
822
        String filename = src.getFileName().toString();
1✔
823
        for (String extension : TAR_COMPRESSED_EXTENSIONS) {
1✔
824
            if (filename.endsWith(extension)) {
1✔
825
                return inspectArchiveCompressed(src);
1✔
826
            }
827
        }
828

829
        try (InputStream fi = Files.newInputStream(src);
1✔
830
             InputStream bi = new BufferedInputStream(fi);
1✔
831
             ArchiveInputStream<?> in = new ArchiveStreamFactory().createArchiveInputStream(bi)) {
1✔
832
            return inspectArchive(in);
1✔
833
        } catch (ArchiveException e) {
×
834
            throw new IOException(e.getMessage(), e);
×
835
        }
836
    }
837

838
    public static String resolveRootEntryName(Path src) {
839
        try {
840
            String filename = src.getFileName().toString();
1✔
841
            for (String extension : TAR_COMPRESSED_EXTENSIONS) {
1✔
842
                if (filename.endsWith(extension)) {
1✔
843
                    return resolveRootEntryNameCompressed(src);
1✔
844
                }
845
            }
846

847
            if (filename.endsWith(ZIP.extension()) || filename.endsWith(TAR.extension())) {
1✔
848
                try (InputStream fi = Files.newInputStream(src);
1✔
849
                     InputStream bi = new BufferedInputStream(fi);
1✔
850
                     ArchiveInputStream<?> in = new ArchiveStreamFactory().createArchiveInputStream(bi)) {
1✔
851
                    return resolveRootEntryName(in);
1✔
852
                } catch (ArchiveException e) {
×
853
                    throw new IOException(e.getMessage(), e);
×
854
                }
855
            }
856
            return "";
1✔
857
        } catch (IOException e) {
×
858
            throw new IllegalStateException(RB.$("ERROR_unexpected_error"), e);
×
859
        }
860
    }
861

862
    public static String resolveRootEntryNameCompressed(Path src) throws IOException {
863
        String filename = src.getFileName().toString();
1✔
864
        String artifactFileName = getFilename(filename, FileType.getSupportedExtensions());
1✔
865
        String artifactExtension = filename.substring(artifactFileName.length());
1✔
866
        String artifactFileFormat = artifactExtension.substring(1);
1✔
867
        FileType fileType = FileType.of(artifactFileFormat);
1✔
868

869
        try (InputStream fi = Files.newInputStream(src);
1✔
870
             InputStream bi = new BufferedInputStream(fi);
1✔
871
             InputStream gzi = resolveCompressorInputStream(fileType, bi);
1✔
872
             ArchiveInputStream<?> in = new TarArchiveInputStream(gzi)) {
1✔
873
            return resolveRootEntryName(in);
1✔
874
        }
875
    }
876

877
    private static String resolveRootEntryName(ArchiveInputStream<?> in) throws IOException {
878
        ArchiveEntry entry = null;
1✔
879
        while (null != (entry = in.getNextEntry())) {
1✔
880
            if (!in.canReadEntryData(entry)) {
1✔
881
                // log something?
882
                continue;
×
883
            }
884
            String entryName = entry.getName();
1✔
885
            return entryName.split("/")[0];
1✔
886
        }
887

888
        return "";
×
889
    }
890

891
    public static CategorizedArchive categorizeUnixArchive(String windowsExtension, Path archive) throws IOException {
892
        List<String> entries = FileUtils.inspectArchive(archive);
1✔
893

894
        Set<String> directories = new LinkedHashSet<>();
1✔
895
        List<String> binaries = new ArrayList<>();
1✔
896
        List<String> files = new ArrayList<>();
1✔
897

898
        String rootEntryName = resolveRootEntryName(archive);
1✔
899

900
        entries.stream()
1✔
901
            // skip Windows executables
902
            .filter(e -> !e.endsWith(windowsExtension))
1✔
903
            // skip directories
904
            .filter(e -> !e.endsWith("/"))
1✔
905
            // remove root from name
906
            .map(e -> e.substring(rootEntryName.length() + 1))
1✔
907
            .sorted()
1✔
908
            .forEach(entry -> {
1✔
909
                if (isBinaryEntry(entry)) {
1✔
910
                    binaries.add(entry);
1✔
911
                } else {
912
                    String[] parts = entry.split("/");
1✔
913
                    if (parts.length > 1) directories.add(parts[0]);
1✔
914
                    files.add(entry);
1✔
915
                }
916
            });
1✔
917

918
        return new CategorizedArchive(directories, binaries, files);
1✔
919
    }
920

921
    private static boolean isBinaryEntry(String entry) {
922
        String[] parts = entry.split("/");
1✔
923
        if (parts.length > 1) {
1✔
924
            return "bin".equalsIgnoreCase(parts[parts.length - 2]);
1✔
925
        }
926

927
        return false;
1✔
928
    }
929

930
    public static List<String> inspectArchiveCompressed(Path src) throws IOException {
931
        String filename = src.getFileName().toString();
1✔
932
        String artifactFileName = getFilename(filename, FileType.getSupportedExtensions());
1✔
933
        String artifactExtension = filename.substring(artifactFileName.length());
1✔
934
        String artifactFileFormat = artifactExtension.substring(1);
1✔
935
        FileType fileType = FileType.of(artifactFileFormat);
1✔
936

937
        try (InputStream fi = Files.newInputStream(src);
1✔
938
             InputStream bi = new BufferedInputStream(fi);
1✔
939
             InputStream gzi = resolveCompressorInputStream(fileType, bi);
1✔
940
             ArchiveInputStream<?> in = new TarArchiveInputStream(gzi)) {
1✔
941
            return inspectArchive(in);
1✔
942
        }
943
    }
944

945
    private static List<String> inspectArchive(ArchiveInputStream<?> in) throws IOException {
946
        List<String> entries = new ArrayList<>();
1✔
947

948
        ArchiveEntry entry = null;
1✔
949
        while (null != (entry = in.getNextEntry())) {
1✔
950
            if (!in.canReadEntryData(entry)) {
1✔
951
                // log something?
952
                continue;
×
953
            }
954
            entries.add(entry.getName());
1✔
955
        }
956

957
        return entries;
1✔
958
    }
959

960
    public static void deleteFiles(Path path) throws IOException {
961
        deleteFiles(path, false);
1✔
962
    }
1✔
963

964
    public static void deleteFiles(Path path, boolean keepRoot) throws IOException {
965
        if (Files.exists(path)) {
1✔
966
            try (Stream<Path> stream = Files.walk(path)) {
1✔
967
                stream
1✔
968
                    .sorted(Comparator.reverseOrder())
1✔
969
                    .map(Path::toFile)
1✔
970
                    .forEach(File::delete);
1✔
971
            }
972
            if (!keepRoot) Files.deleteIfExists(path);
1✔
973
        }
974
    }
1✔
975

976
    public static void createDirectoriesWithFullAccess(Path path) throws IOException {
977
        createDirectories(path, "rwxrwxrwx");
1✔
978
    }
1✔
979

980
    public static void createDirectories(Path path, String accessRights) throws IOException {
981
        if (supportsPosix(path)) {
1✔
982
            Set<PosixFilePermission> perms = PosixFilePermissions.fromString(accessRights);
1✔
983
            FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
1✔
984
            Files.createDirectories(path, attr);
1✔
985
        } else {
1✔
986
            Files.createDirectories(path);
×
987
        }
988
    }
1✔
989

990
    public static void grantFullAccess(Path path) throws IOException {
991
        grantAccess(path, "rwxrwxrwx");
1✔
992
    }
1✔
993

994
    public static void grantExecutableAccess(Path path) throws IOException {
995
        grantAccess(path, "r-xr-xr-x");
1✔
996
    }
1✔
997

998
    public static void grantAccess(Path path, String accessRights) throws IOException {
999
        if (supportsPosix(path)) {
1✔
1000
            Set<PosixFilePermission> perms = PosixFilePermissions.fromString(accessRights);
1✔
1001
            Files.setPosixFilePermissions(path, perms);
1✔
1002
        } else if (accessRights.contains("r")) {
1✔
1003
            path.toFile().setReadable(true);
×
1004
        } else if (accessRights.contains("w")) {
×
1005
            path.toFile().setWritable(true);
×
1006
        } else if (accessRights.contains("x")) {
×
1007
            path.toFile().setExecutable(true);
×
1008
        }
1009
    }
1✔
1010

1011
    public static void copyPermissions(Path src, Path dest) throws IOException {
1012
        if (supportsPosix(src)) {
1✔
1013
            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(src);
1✔
1014
            Files.setPosixFilePermissions(dest, perms);
1✔
1015
        } else {
1✔
1016
            File s = src.toFile();
×
1017
            File d = dest.toFile();
×
1018
            d.setReadable(s.canRead());
×
1019
            d.setWritable(s.canWrite());
×
1020
            d.setExecutable(s.canExecute());
×
1021
        }
1022
    }
1✔
1023

1024
    public static void copyFiles(JReleaserLogger logger, Path source, Path target) throws IOException {
1025
        copyFiles(logger, source, target, path -> true);
1✔
1026
    }
1✔
1027

1028
    public static void copyFiles(JReleaserLogger logger, Path source, Path target, Predicate<Path> filter) throws IOException {
1029
        if (!Files.exists(source)) return;
1✔
1030

1031
        Predicate<Path> actualFilter = null != filter ? filter : path -> true;
1✔
1032
        IOException[] thrown = new IOException[1];
1✔
1033

1034
        try (Stream<Path> stream = Files.list(source)) {
1✔
1035
            Files.createDirectories(target);
1✔
1036
            stream
1✔
1037
                .filter(Files::isRegularFile)
1✔
1038
                .filter(actualFilter)
1✔
1039
                .forEach(child -> {
1✔
1040
                    try {
1041
                        Files.copy(child, target.resolve(child.getFileName()), REPLACE_EXISTING);
1✔
1042
                    } catch (IOException e) {
×
1043
                        logger.error(RB.$("ERROR_files_copy"), child, e);
×
1044
                        if (null == thrown[0]) thrown[0] = e;
×
1045
                    }
1✔
1046
                });
1✔
1047
        }
1048

1049
        if (null != thrown[0]) {
1✔
1050
            throw thrown[0];
×
1051
        }
1052
    }
1✔
1053

1054
    public static void copyFiles(JReleaserLogger logger, Path source, Path target, Set<Path> paths) throws IOException {
1055
        logger.debug(RB.$("files.copy", source, target));
1✔
1056

1057
        for (Path path : paths) {
1✔
1058
            Path srcPath = source.resolve(path);
1✔
1059
            Path targetPath = target.resolve(path);
1✔
1060

1061
            Files.createDirectories(targetPath.getParent());
1✔
1062
            Files.copy(srcPath, targetPath, REPLACE_EXISTING);
1✔
1063
        }
1✔
1064
    }
1✔
1065

1066
    public static boolean copyFilesRecursive(JReleaserLogger logger, Path source, Path target) throws IOException {
1067
        return copyFilesRecursive(logger, source, target, null);
1✔
1068
    }
1069

1070
    public static boolean copyFilesRecursive(JReleaserLogger logger, Path source, Path target, Predicate<Path> filter) throws IOException {
1071
        FileTreeCopy copier = new FileTreeCopy(logger, source, target, filter);
1✔
1072
        Files.walkFileTree(source, copier);
1✔
1073
        return copier.isSuccessful();
1✔
1074
    }
1075

1076
    public static class CategorizedArchive {
1077
        private final Set<String> directories = new LinkedHashSet<>();
1✔
1078
        private final List<String> binaries = new ArrayList<>();
1✔
1079
        private final List<String> files = new ArrayList<>();
1✔
1080

1081
        public CategorizedArchive(Set<String> directories, List<String> binaries, List<String> files) {
1✔
1082
            this.directories.addAll(directories);
1✔
1083
            this.binaries.addAll(binaries);
1✔
1084
            this.files.addAll(files);
1✔
1085
        }
1✔
1086

1087
        public Set<String> getDirectories() {
1088
            return unmodifiableSet(directories);
1✔
1089
        }
1090

1091
        public List<String> getBinaries() {
1092
            return unmodifiableList(binaries);
1✔
1093
        }
1094

1095
        public List<String> getFiles() {
1096
            return unmodifiableList(files);
1✔
1097
        }
1098
    }
1099

1100
    private static class FileTreeCopy implements FileVisitor<Path> {
1101
        private final JReleaserLogger logger;
1102
        private final Path source;
1103
        private final Path target;
1104
        private final Predicate<Path> filter;
1105
        private boolean success = true;
1✔
1106

1107
        FileTreeCopy(JReleaserLogger logger, Path source, Path target, Predicate<Path> filter) {
1✔
1108
            this.logger = logger;
1✔
1109
            this.source = source;
1✔
1110
            this.target = target;
1✔
1111
            this.filter = filter;
1✔
1112
            logger.debug(RB.$("files.copy", source, target));
1✔
1113
        }
1✔
1114

1115
        private boolean filtered(Path path) {
1116
            if (null != filter) {
1✔
1117
                return filter.test(path);
1✔
1118
            }
1119
            return false;
1✔
1120
        }
1121

1122
        public boolean isSuccessful() {
1123
            return success;
1✔
1124
        }
1125

1126
        @Override
1127
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
1128
            if (filtered(dir)) return SKIP_SUBTREE;
1✔
1129

1130
            Path newdir = target.resolve(source.relativize(dir));
1✔
1131
            try {
1132
                Files.copy(dir, newdir);
1✔
1133
                FileUtils.grantFullAccess(newdir);
1✔
1134
            } catch (FileAlreadyExistsException ignored) {
1✔
1135
                // noop
1136
            } catch (IOException e) {
×
1137
                logger.error(RB.$("ERROR_files_create"), newdir, e);
×
1138
                success = false;
×
1139
                return SKIP_SUBTREE;
×
1140
            }
1✔
1141
            return CONTINUE;
1✔
1142
        }
1143

1144
        @Override
1145
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
1146
            if (filtered(file)) return CONTINUE;
1✔
1147

1148
            try {
1149
                Path newfile = target.resolve(source.relativize(file));
1✔
1150
                Files.copy(file, newfile, REPLACE_EXISTING);
1✔
1151
                FileUtils.copyPermissions(file, newfile);
1✔
1152
            } catch (IOException e) {
×
1153
                logger.error(RB.$("ERROR_files_copy"), source, e);
×
1154
                success = false;
×
1155
            }
1✔
1156
            return CONTINUE;
1✔
1157
        }
1158

1159
        @Override
1160
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
1161
            if (filtered(dir)) return CONTINUE;
1✔
1162

1163
            if (null == exc) {
1✔
1164
                Path newdir = target.resolve(source.relativize(dir));
1✔
1165
                try {
1166
                    FileTime time = Files.getLastModifiedTime(dir);
1✔
1167
                    Files.setLastModifiedTime(newdir, time);
1✔
1168
                } catch (IOException e) {
×
1169
                    logger.warn(RB.$("ERROR_files_copy_attributes"), newdir, e);
×
1170
                }
1✔
1171
            }
1172
            return CONTINUE;
1✔
1173
        }
1174

1175
        @Override
1176
        public FileVisitResult visitFileFailed(Path file, IOException e) {
1177
            if (e instanceof FileSystemLoopException) {
×
1178
                logger.error(RB.$("ERROR_files_cycle"), file);
×
1179
            } else {
1180
                logger.error(RB.$("ERROR_files_copy"), file, e);
×
1181
            }
1182
            success = false;
×
1183
            return CONTINUE;
×
1184
        }
1185
    }
1186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc