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

jreleaser / jreleaser / #556

22 Nov 2025 04:17PM UTC coverage: 46.213% (-2.0%) from 48.203%
#556

push

github

aalmiray
feat(jdks): Allow filtering by platform

Closes #2000

Co-authored-by: Ixchel Ruiz <ixchelruiz@yahoo.com>

0 of 42 new or added lines in 5 files covered. (0.0%)

1116 existing lines in 107 files now uncovered.

24939 of 53965 relevant lines covered (46.21%)

0.46 hits per line

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

78.05
/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 boolean isHidden(Path path) {
130
        try {
131
            return Files.isHidden(path);
1✔
132
        } catch (IOException e) {
×
133
            throw new RuntimeException(e);
×
134
        }
135
    }
136

137
    public static void listFilesAndConsume(Path path, Consumer<Stream<Path>> consumer) throws IOException {
138
        if (!Files.exists(path)) return;
1✔
139
        try (Stream<Path> files = Files.list(path)) {
1✔
140
            consumer.accept(files);
1✔
141
        }
142
    }
1✔
143

144
    public static <T> Optional<T> listFilesAndProcess(Path path, Function<Stream<Path>, T> function) throws IOException {
145
        if (!Files.exists(path)) return Optional.empty();
1✔
146
        try (Stream<Path> files = Files.list(path)) {
1✔
147
            return Optional.ofNullable(function.apply(files));
1✔
148
        }
149
    }
150

151
    public static Optional<Path> findLicenseFile(Path basedir) {
UNCOV
152
        for (String licenseFilename : LICENSE_FILE_NAMES) {
×
UNCOV
153
            Path path = basedir.resolve(licenseFilename);
×
UNCOV
154
            if (Files.exists(path)) {
×
UNCOV
155
                return Optional.of(path);
×
156
            }
157
            path = basedir.resolve(licenseFilename.toLowerCase(Locale.ENGLISH));
×
158
            if (Files.exists(path)) {
×
159
                return Optional.of(path);
×
160
            }
161
        }
162

163
        return Optional.empty();
×
164
    }
165

166
    public static Path resolveOutputDirectory(Path basedir, Path outputdir, String baseOutput) {
167
        String od = Env.resolve("OUTPUT_DIRECTORY", "");
1✔
168
        if (isNotBlank(od)) {
1✔
169
            return basedir.resolve(od).resolve("jreleaser").normalize();
1✔
170
        }
171
        if (null != outputdir) {
×
172
            return basedir.resolve(outputdir).resolve("jreleaser").normalize();
×
173
        }
174
        return basedir.resolve(baseOutput).resolve("jreleaser").normalize();
×
175
    }
176

177
    public static void zip(Path src, Path dest) throws IOException {
178
        zip(src, dest, new ArchiveOptions());
1✔
179
    }
1✔
180

181
    public static void zip(Path src, Path dest, ArchiveOptions options) throws IOException {
182
        try (ZipArchiveOutputStream out = new ZipArchiveOutputStream(dest.toFile())) {
1✔
183
            out.setMethod(ZipOutputStream.DEFLATED);
1✔
184

185
            Set<Path> paths = !options.getIncludedPaths().isEmpty() ? options.getIncludedPaths() : collectPaths(src);
1✔
186
            FileTime fileTime = null != options.getTimestamp() ? FileTime.from(options.getTimestamp().toInstant()) : null;
1✔
187
            String rootEntryName = options.getRootEntryName();
1✔
188
            if (null == rootEntryName) {
1✔
189
                rootEntryName = "";
1✔
190
            } else if (!rootEntryName.endsWith("/")) {
×
191
                rootEntryName += "/";
×
192
            }
193

194
            Set<String> entryNames = new TreeSet<>();
1✔
195
            for (Path path : paths) {
1✔
196
                String entryName = rootEntryName + src.relativize(path);
1✔
197
                entryNames.add(entryName);
1✔
198

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

220
                File inputFile = path.toFile();
1✔
221
                ZipArchiveEntry archiveEntry = new ZipArchiveEntry(inputFile, entryName);
1✔
222
                if (null != fileTime) archiveEntry.setTime(fileTime);
1✔
223

224
                archiveEntry.setMethod(ZipOutputStream.DEFLATED);
1✔
225
                if (inputFile.isFile() && Files.isExecutable(path)) {
1✔
226
                    archiveEntry.setUnixMode(0100755);
1✔
227
                }
228

229
                out.putArchiveEntry(archiveEntry);
1✔
230

231
                if (inputFile.isFile()) {
1✔
232
                    out.write(Files.readAllBytes(path));
1✔
233
                }
234
                out.closeArchiveEntry();
1✔
235
            }
1✔
236
        }
237
    }
1✔
238

239
    public static void ar(Path src, Path dest) throws IOException {
240
        ar(src, dest, new ArchiveOptions());
×
241
    }
×
242

243
    public static void ar(Path src, Path dest, ArchiveOptions options) throws IOException {
244
        try (ArArchiveOutputStream out = new ArArchiveOutputStream(
1✔
245
            Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING))) {
1✔
246
            ar(src, out, options);
1✔
247
        }
248
    }
1✔
249

250
    public static void tar(Path src, Path dest) throws IOException {
251
        tar(src, dest, new ArchiveOptions());
×
252
    }
×
253

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

261
    public static void tgz(Path src, Path dest) throws IOException {
262
        tgz(src, dest, new ArchiveOptions());
×
263
    }
×
264

265
    public static void tgz(Path src, Path dest, ArchiveOptions options) throws IOException {
266
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
267
            new GzipCompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING)))) {
1✔
268
            tar(src, out, options);
1✔
269
        }
270
    }
1✔
271

272
    public static void bz2(Path src, Path dest) throws IOException {
273
        bz2(src, dest, new ArchiveOptions());
×
274
    }
×
275

276
    public static void bz2(Path src, Path dest, ArchiveOptions options) throws IOException {
277
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
278
            new BZip2CompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING)))) {
1✔
279
            tar(src, out, options);
1✔
280
        }
281
    }
1✔
282

283
    public static void xz(Path src, Path dest) throws IOException {
284
        xz(src, dest, new ArchiveOptions());
×
285
    }
×
286

287
    public static void xz(Path src, Path dest, ArchiveOptions options) throws IOException {
288
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
289
            new XZCompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING)))) {
1✔
290
            tar(src, out, options);
1✔
291
        }
292
    }
1✔
293

294
    public static void zst(Path src, Path dest) throws IOException {
295
        zst(src, dest, new ArchiveOptions());
×
296
    }
×
297

298
    public static void zst(Path src, Path dest, ArchiveOptions options) throws IOException {
299
        try (TarArchiveOutputStream out = new TarArchiveOutputStream(
1✔
300
            new ZstdCompressorOutputStream(Files.newOutputStream(dest, CREATE, TRUNCATE_EXISTING),
1✔
301
                Zstd.defaultCompressionLevel(), true))) {
1✔
302
            tar(src, out, options);
1✔
303
        }
304
    }
1✔
305

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

309
        out.setLongFileMode(options.getLongFileMode().toLongFileMode());
1✔
310
        out.setBigNumberMode(options.getBigNumberMode().toBigNumberMode());
1✔
311

312
        FileTime fileTime = null != options.getTimestamp() ? FileTime.from(options.getTimestamp().toInstant()) : null;
1✔
313
        String rootEntryName = options.getRootEntryName();
1✔
314
        if (null == rootEntryName) {
1✔
315
            rootEntryName = "";
1✔
316
        } else if (!rootEntryName.endsWith("/")) {
1✔
317
            rootEntryName += "/";
1✔
318
        }
319

320
        Set<String> entryNames = new TreeSet<>();
1✔
321
        for (Path path : paths) {
1✔
322
            String entryName = rootEntryName + src.relativize(path);
1✔
323
            entryNames.add(entryName);
1✔
324

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

346
            File inputFile = path.toFile();
1✔
347
            TarArchiveEntry archiveEntry = out.createArchiveEntry(inputFile, entryName);
1✔
348
            if (null != fileTime) archiveEntry.setModTime(fileTime);
1✔
349

350
            if (inputFile.isFile() && Files.isExecutable(path)) {
1✔
351
                archiveEntry.setMode(0100755);
1✔
352
            }
353

354
            out.putArchiveEntry(archiveEntry);
1✔
355

356
            if (inputFile.isFile()) {
1✔
357
                out.write(Files.readAllBytes(path));
1✔
358
            }
359

360
            out.closeArchiveEntry();
1✔
361
        }
1✔
362
    }
1✔
363

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

367
        out.setLongFileMode(options.getLongFileMode().toLongFileMode());
1✔
368
        String rootEntryName = options.getRootEntryName();
1✔
369
        if (null == rootEntryName) {
1✔
370
            rootEntryName = "";
1✔
371
        } else if (!rootEntryName.endsWith("/")) {
×
372
            rootEntryName += "/";
×
373
        }
374

375
        Set<String> entryNames = new TreeSet<>();
1✔
376
        for (Path path : paths) {
1✔
377
            String entryName = rootEntryName + src.relativize(path);
1✔
378

379
            entryNames.add(entryName);
1✔
380

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

401
            File inputFile = path.toFile();
1✔
402
            ArArchiveEntry archiveEntry = out.createArchiveEntry(inputFile, entryName);
1✔
403

404
            out.putArchiveEntry(archiveEntry);
1✔
405

406
            if (inputFile.isFile()) {
1✔
407
                out.write(Files.readAllBytes(path));
1✔
408
            }
409

410
            out.closeArchiveEntry();
1✔
411
        }
1✔
412
    }
1✔
413

414
    private static TreeSet<Path> collectPaths(Path src) throws IOException {
415
        TreeSet<Path> paths = new TreeSet<>();
1✔
416
        Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
1✔
417
            @Override
418
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
419
                paths.add(file);
1✔
420
                return super.visitFile(file, attrs);
1✔
421
            }
422
        });
423
        return paths;
1✔
424
    }
425

426
    public static class ArchiveOptions {
1✔
427
        private final Set<Path> includedPaths = new LinkedHashSet<>();
1✔
428
        private String rootEntryName;
429
        private ZonedDateTime timestamp;
430
        private TarMode longFileMode = TarMode.ERROR;
1✔
431
        private TarMode bigNumberMode = TarMode.ERROR;
1✔
432
        private boolean createIntermediateDirs;
433

434
        public boolean isCreateIntermediateDirs() {
435
            return createIntermediateDirs;
1✔
436
        }
437

438
        public String getRootEntryName() {
439
            return rootEntryName;
1✔
440
        }
441

442
        public ZonedDateTime getTimestamp() {
443
            return timestamp;
1✔
444
        }
445

446
        public TarMode getLongFileMode() {
447
            return longFileMode;
1✔
448
        }
449

450
        public TarMode getBigNumberMode() {
451
            return bigNumberMode;
1✔
452
        }
453

454
        public Set<Path> getIncludedPaths() {
455
            return includedPaths;
1✔
456
        }
457

458
        public ArchiveOptions withCreateIntermediateDirs(boolean createIntermediateDirs) {
459
            this.createIntermediateDirs = createIntermediateDirs;
1✔
460
            return this;
1✔
461
        }
462

463
        public ArchiveOptions withIncludedPath(Path path) {
464
            this.includedPaths.add(path);
1✔
465
            return this;
1✔
466
        }
467

468
        public ArchiveOptions withRootEntryName(String rootEntryName) {
469
            this.rootEntryName = rootEntryName;
1✔
470
            return this;
1✔
471
        }
472

473
        public ArchiveOptions withTimestamp(ZonedDateTime timestamp) {
474
            this.timestamp = timestamp;
1✔
475
            return this;
1✔
476
        }
477

478
        public ArchiveOptions withLongFileMode(TarMode longFileMode) {
479
            if (null != longFileMode) this.longFileMode = longFileMode;
1✔
480
            return this;
1✔
481
        }
482

483
        public ArchiveOptions withBigNumberMode(TarMode bigNumberMode) {
484
            if (null != bigNumberMode) this.bigNumberMode = bigNumberMode;
1✔
485
            return this;
1✔
486
        }
487

488
        public enum TarMode {
1✔
489
            GNU,
1✔
490
            POSIX,
1✔
491
            ERROR,
1✔
492
            TRUNCATE;
1✔
493

494
            public String formatted() {
495
                return name().toLowerCase(Locale.ENGLISH);
×
496
            }
497

498
            public static TarMode of(String str) {
499
                if (isBlank(str)) return null;
1✔
500
                return valueOf(str.toUpperCase(Locale.ENGLISH).trim());
1✔
501
            }
502

503
            public int toLongFileMode() {
504
                switch (this) {
1✔
505
                    case GNU:
506
                        return TarArchiveOutputStream.LONGFILE_GNU;
×
507
                    case POSIX:
508
                        return TarArchiveOutputStream.LONGFILE_POSIX;
1✔
509
                    case TRUNCATE:
510
                        return TarArchiveOutputStream.LONGFILE_TRUNCATE;
×
511
                    case ERROR:
512
                    default:
513
                        return TarArchiveOutputStream.LONGFILE_ERROR;
1✔
514
                }
515
            }
516

517
            public int toBigNumberMode() {
518
                switch (this) {
1✔
519
                    case GNU:
520
                        return TarArchiveOutputStream.BIGNUMBER_STAR;
×
521
                    case POSIX:
522
                        return TarArchiveOutputStream.BIGNUMBER_POSIX;
×
523
                    case ERROR:
524
                    default:
525
                        return TarArchiveOutputStream.BIGNUMBER_ERROR;
1✔
526
                }
527
            }
528
        }
529
    }
530

531
    public static void packArchive(Path src, Path dest) throws IOException {
532
        packArchive(src, dest, new ArchiveOptions());
×
533
    }
×
534

535
    public static void packArchive(Path src, Path dest, ArchiveOptions options) throws IOException {
536
        String filename = dest.getFileName().toString();
1✔
537
        if (filename.endsWith(ZIP.extension())) {
1✔
538
            zip(src, dest, options);
1✔
539
        } else if (filename.endsWith(TAR_BZ2.extension()) || filename.endsWith(TBZ2.extension())) {
1✔
540
            bz2(src, dest, options);
1✔
541
        } else if (filename.endsWith(TAR_GZ.extension()) || filename.endsWith(TGZ.extension())) {
1✔
542
            tgz(src, dest, options);
1✔
543
        } else if (filename.endsWith(TAR_XZ.extension()) || filename.endsWith(TXZ.extension())) {
1✔
544
            xz(src, dest, options);
1✔
545
        } else if (filename.endsWith(TAR_ZST.extension())) {
1✔
546
            zst(src, dest, options);
1✔
547
        } else if (filename.endsWith(TAR.extension())) {
1✔
548
            tar(src, dest, options);
1✔
549
        }
550
    }
1✔
551

552
    public static void unpackArchive(Path src, Path dest) throws IOException {
553
        unpackArchive(src, dest, true);
1✔
554
    }
1✔
555

556
    public static void unpackArchive(Path src, Path dest, boolean removeRootEntry) throws IOException {
557
        unpackArchive(src, dest, removeRootEntry, true);
1✔
558
    }
1✔
559

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

563
        for (String extension : TAR_COMPRESSED_EXTENSIONS) {
1✔
564
            if (filename.endsWith(extension)) {
1✔
565
                unpackArchiveCompressed(src, dest, removeRootEntry);
1✔
566
                return;
1✔
567
            }
568
        }
569

570
        if (cleanDirectory) deleteFiles(dest, true);
1✔
571
        File destinationDir = dest.toFile();
1✔
572

573
        String rootEntryName = resolveRootEntryName(src);
1✔
574
        if (filename.endsWith(ZIP.extension())) {
1✔
575
            try (ZipFile zipFile = ZipFile.builder().setFile(src.toFile()).get()) {
1✔
576
                unpackArchive(removeRootEntry ? rootEntryName + "/" : "", destinationDir, zipFile);
1✔
577
            }
578
            return;
1✔
579
        }
580

581
        try (InputStream fi = Files.newInputStream(src);
1✔
582
             InputStream bi = new BufferedInputStream(fi);
1✔
583
             ArchiveInputStream<?> in = new ArchiveStreamFactory().createArchiveInputStream(bi)) {
1✔
584

585
            unpackArchive(removeRootEntry ? rootEntryName + "/" : "", destinationDir, in);
1✔
586
        } catch (ArchiveException e) {
×
587
            throw new IOException(e.getMessage(), e);
×
588
        }
1✔
589
    }
1✔
590

591
    public static void unpackArchiveCompressed(Path src, Path dest) throws IOException {
592
        unpackArchiveCompressed(src, dest, true);
×
593
    }
×
594

595
    public static void unpackArchiveCompressed(Path src, Path dest, boolean removeRootEntry) throws IOException {
596
        unpackArchiveCompressed(src, dest, removeRootEntry, true);
1✔
597
    }
1✔
598

599
    public static void unpackArchiveCompressed(Path src, Path dest, boolean removeRootEntry, boolean cleanDirectory) throws IOException {
600
        if (cleanDirectory) deleteFiles(dest, true);
1✔
601
        File destinationDir = dest.toFile();
1✔
602

603
        String filename = src.getFileName().toString();
1✔
604
        String artifactFileName = getFilename(filename, FileType.getSupportedExtensions());
1✔
605
        String rootEntryName = resolveRootEntryName(src);
1✔
606
        String artifactExtension = filename.substring(artifactFileName.length());
1✔
607
        String artifactFileFormat = artifactExtension.substring(1);
1✔
608
        FileType fileType = FileType.of(artifactFileFormat);
1✔
609

610
        try (InputStream fi = Files.newInputStream(src);
1✔
611
             InputStream bi = new BufferedInputStream(fi);
1✔
612
             InputStream gzi = resolveCompressorInputStream(fileType, bi);
1✔
613
             ArchiveInputStream<?> in = new TarArchiveInputStream(gzi)) {
1✔
614
            unpackArchive(removeRootEntry ? rootEntryName + "/" : "", destinationDir, in);
1✔
615
        }
616
    }
1✔
617

618
    private static InputStream resolveCompressorInputStream(FileType fileType, InputStream in) throws IOException {
619
        switch (fileType) {
1✔
620
            case TGZ:
621
            case TAR_GZ:
622
                return new GzipCompressorInputStream(in);
1✔
623
            case TBZ2:
624
            case TAR_BZ2:
625
                return new BZip2CompressorInputStream(in);
1✔
626
            case TXZ:
627
            case TAR_XZ:
628
                return new XZCompressorInputStream(in);
1✔
629
            case TAR_ZST:
630
                return new ZstdCompressorInputStream(in);
1✔
631
            default:
632
                // noop
633
                break;
634
        }
635

636
        return null;
×
637
    }
638

639
    private static void unpackArchive(String basename, File destinationDir, ArchiveInputStream<?> in) throws IOException {
640
        ArchiveEntry entry = null;
1✔
641
        while (null != (entry = in.getNextEntry())) {
1✔
642
            if (!in.canReadEntryData(entry)) {
1✔
643
                // log something?
644
                continue;
×
645
            }
646

647
            String entryName = entry.getName();
1✔
648
            if (isNotBlank(basename) && entryName.startsWith(basename) && entryName.length() > basename.length() + 1) {
1✔
649
                entryName = entryName.substring(basename.length());
1✔
650
            }
651

652
            File file = new File(destinationDir, entryName);
1✔
653
            String destDirPath = destinationDir.getCanonicalPath();
1✔
654
            String destFilePath = file.getCanonicalPath();
1✔
655
            if (!destFilePath.startsWith(destDirPath + File.separator)) {
1✔
656
                throw new IOException(RB.$("ERROR_files_unpack_outside_target", entry.getName()));
×
657
            }
658

659
            if (entry.isDirectory()) {
1✔
660
                if (!file.isDirectory() && !file.mkdirs()) {
×
661
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", file));
×
662
                }
663
            } else {
664
                File parent = file.getParentFile();
1✔
665
                if (!parent.isDirectory() && !parent.mkdirs()) {
1✔
666
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", parent));
×
667
                }
668

669
                if (isSymbolicLink(entry)) {
1✔
670
                    Files.createSymbolicLink(file.toPath(), Paths.get(getLinkName(in, entry)));
×
671
                } else {
672
                    try (OutputStream o = Files.newOutputStream(file.toPath())) {
1✔
673
                        IOUtils.copy(in, o);
1✔
674
                        Files.setLastModifiedTime(file.toPath(), FileTime.from(entry.getLastModifiedDate().toInstant()));
1✔
675
                        chmod(file, getEntryMode(entry, file));
1✔
676
                    }
677
                }
678
            }
679
        }
1✔
680
    }
1✔
681

682
    private static void unpackArchive(String basename, File destinationDir, ZipFile zipFile) throws IOException {
683
        Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
1✔
684
        while (entries.hasMoreElements()) {
1✔
685
            ZipArchiveEntry entry = entries.nextElement();
1✔
686
            if (!zipFile.canReadEntryData(entry)) {
1✔
687
                // log something?
688
                continue;
×
689
            }
690

691
            String entryName = entry.getName();
1✔
692
            if (isNotBlank(basename) && entryName.startsWith(basename) && entryName.length() > basename.length() + 1) {
1✔
693
                entryName = entryName.substring(basename.length());
1✔
694
            }
695

696
            File file = new File(destinationDir, entryName);
1✔
697
            String destDirPath = destinationDir.getCanonicalPath();
1✔
698
            String destFilePath = file.getCanonicalPath();
1✔
699
            if (!destFilePath.startsWith(destDirPath + File.separator)) {
1✔
700
                throw new IOException(RB.$("ERROR_files_unpack_outside_target", entry.getName()));
×
701
            }
702

703
            if (entry.isDirectory()) {
1✔
704
                if (!file.isDirectory() && !file.mkdirs()) {
1✔
705
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", file));
×
706
                }
707
            } else {
708
                File parent = file.getParentFile();
1✔
709
                if (!parent.isDirectory() && !parent.mkdirs()) {
1✔
710
                    throw new IOException(RB.$("ERROR_files_unpack_fail_dir", parent));
×
711
                }
712

713
                if (entry.isUnixSymlink()) {
1✔
714
                    Files.createSymbolicLink(file.toPath(), Paths.get(zipFile.getUnixSymlink(entry)));
×
715
                } else {
716
                    try (OutputStream o = Files.newOutputStream(file.toPath())) {
1✔
717
                        IOUtils.copy(zipFile.getInputStream(entry), o);
1✔
718
                        Files.setLastModifiedTime(file.toPath(), FileTime.from(entry.getLastModifiedDate().toInstant()));
1✔
719
                        chmod(file, getEntryMode(entry, file));
1✔
720
                    }
721
                }
722
            }
723
        }
1✔
724
    }
1✔
725

726
    private static boolean isSymbolicLink(ArchiveEntry entry) {
727
        if (entry instanceof ZipArchiveEntry) {
1✔
728
            return ((ZipArchiveEntry) entry).isUnixSymlink();
×
729
        } else if (entry instanceof TarArchiveEntry) {
1✔
730
            return ((TarArchiveEntry) entry).isSymbolicLink();
1✔
731
        }
732
        return false;
×
733
    }
734

735
    private static String getLinkName(ArchiveInputStream<?> in, ArchiveEntry entry) throws IOException {
736
        if (entry instanceof ZipArchiveEntry) {
×
737
            try (ByteArrayOutputStream o = new ByteArrayOutputStream()) {
×
738
                IOUtils.copy(in, o);
×
739
                return IoUtils.toString(o);
×
740
            }
741
        } else if (entry instanceof TarArchiveEntry) {
×
742
            return ((TarArchiveEntry) entry).getLinkName();
×
743
        }
744
        return "";
×
745
    }
746

747
    private static int getEntryMode(ArchiveEntry entry, File file) {
748
        if (entry instanceof TarArchiveEntry) {
1✔
749
            return getEntryMode(entry, ((TarArchiveEntry) entry).getMode(), file);
1✔
750
        }
751
        return getEntryMode(entry, ((ZipArchiveEntry) entry).getUnixMode(), file);
1✔
752
    }
753

754
    private static int getEntryMode(ArchiveEntry entry, int mode, File file) {
755
        int unixMode = mode & 0777;
1✔
756
        if (unixMode == 0) {
1✔
757
            if (entry.isDirectory()) {
1✔
758
                unixMode = 0755;
×
759
            } else if ("bin".equalsIgnoreCase(file.getParentFile().getName())) {
1✔
760
                // zipEntry.unixMode returns 0 most times even if the entry is executable
761
                // force executable bit only if parent dir == 'bin'
762
                unixMode = 0777;
×
763
            } else {
764
                unixMode = 0644;
1✔
765
            }
766
        }
767
        return unixMode;
1✔
768
    }
769

770
    public static void chmod(File file, int mode) throws IOException {
771
        chmod(file.toPath(), mode);
1✔
772
    }
1✔
773

774
    public static void chmod(Path path, int mode) throws IOException {
775
        if (supportsPosix(path)) {
1✔
776
            PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
1✔
777
            fileAttributeView.setPermissions(convertToPermissionsSet(mode));
1✔
778
        } else {
1✔
779
            path.toFile().setExecutable(true);
×
780
        }
781
    }
1✔
782

783
    private static boolean supportsPosix(Path path) {
784
        return path.getFileSystem().supportedFileAttributeViews().contains("posix");
1✔
785
    }
786

787
    private static Set<PosixFilePermission> convertToPermissionsSet(int mode) {
788
        Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class);
1✔
789

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

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

798
        if ((mode & 64) == 64) {
1✔
799
            result.add(PosixFilePermission.OWNER_EXECUTE);
1✔
800
        }
801

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

806
        if ((mode & 16) == 16) {
1✔
807
            result.add(PosixFilePermission.GROUP_WRITE);
×
808
        }
809

810
        if ((mode & 8) == 8) {
1✔
811
            result.add(PosixFilePermission.GROUP_EXECUTE);
1✔
812
        }
813

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

818
        if ((mode & 2) == 2) {
1✔
819
            result.add(PosixFilePermission.OTHERS_WRITE);
×
820
        }
821

822
        if ((mode & 1) == 1) {
1✔
823
            result.add(PosixFilePermission.OTHERS_EXECUTE);
1✔
824
        }
825

826
        return result;
1✔
827
    }
828

829
    public static List<String> inspectArchive(Path src) throws IOException {
830
        String filename = src.getFileName().toString();
1✔
831
        for (String extension : TAR_COMPRESSED_EXTENSIONS) {
1✔
832
            if (filename.endsWith(extension)) {
1✔
833
                return inspectArchiveCompressed(src);
1✔
834
            }
835
        }
836

837
        try (InputStream fi = Files.newInputStream(src);
1✔
838
             InputStream bi = new BufferedInputStream(fi);
1✔
839
             ArchiveInputStream<?> in = new ArchiveStreamFactory().createArchiveInputStream(bi)) {
1✔
840
            return inspectArchive(in);
1✔
841
        } catch (ArchiveException e) {
×
842
            throw new IOException(e.getMessage(), e);
×
843
        }
844
    }
845

846
    public static String resolveRootEntryName(Path src) {
847
        try {
848
            String filename = src.getFileName().toString();
1✔
849
            for (String extension : TAR_COMPRESSED_EXTENSIONS) {
1✔
850
                if (filename.endsWith(extension)) {
1✔
851
                    return resolveRootEntryNameCompressed(src);
1✔
852
                }
853
            }
854

855
            if (filename.endsWith(ZIP.extension()) || filename.endsWith(TAR.extension())) {
1✔
856
                try (InputStream fi = Files.newInputStream(src);
1✔
857
                     InputStream bi = new BufferedInputStream(fi);
1✔
858
                     ArchiveInputStream<?> in = new ArchiveStreamFactory().createArchiveInputStream(bi)) {
1✔
859
                    return resolveRootEntryName(in);
1✔
860
                } catch (ArchiveException e) {
×
861
                    throw new IOException(e.getMessage(), e);
×
862
                }
863
            }
864
            return "";
1✔
865
        } catch (IOException e) {
×
866
            throw new IllegalStateException(RB.$("ERROR_unexpected_error"), e);
×
867
        }
868
    }
869

870
    public static String resolveRootEntryNameCompressed(Path src) throws IOException {
871
        String filename = src.getFileName().toString();
1✔
872
        String artifactFileName = getFilename(filename, FileType.getSupportedExtensions());
1✔
873
        String artifactExtension = filename.substring(artifactFileName.length());
1✔
874
        String artifactFileFormat = artifactExtension.substring(1);
1✔
875
        FileType fileType = FileType.of(artifactFileFormat);
1✔
876

877
        try (InputStream fi = Files.newInputStream(src);
1✔
878
             InputStream bi = new BufferedInputStream(fi);
1✔
879
             InputStream gzi = resolveCompressorInputStream(fileType, bi);
1✔
880
             ArchiveInputStream<?> in = new TarArchiveInputStream(gzi)) {
1✔
881
            return resolveRootEntryName(in);
1✔
882
        }
883
    }
884

885
    private static String resolveRootEntryName(ArchiveInputStream<?> in) throws IOException {
886
        ArchiveEntry entry = null;
1✔
887
        while (null != (entry = in.getNextEntry())) {
1✔
888
            if (!in.canReadEntryData(entry)) {
1✔
889
                // log something?
890
                continue;
×
891
            }
892
            String entryName = entry.getName();
1✔
893
            return entryName.split("/")[0];
1✔
894
        }
895

896
        return "";
×
897
    }
898

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

902
        Set<String> directories = new LinkedHashSet<>();
1✔
903
        List<String> binaries = new ArrayList<>();
1✔
904
        List<String> files = new ArrayList<>();
1✔
905

906
        String rootEntryName = resolveRootEntryName(archive);
1✔
907

908
        entries.stream()
1✔
909
            // skip Windows executables
910
            .filter(e -> !e.endsWith(windowsExtension))
1✔
911
            // skip directories
912
            .filter(e -> !e.endsWith("/"))
1✔
913
            // remove root from name
914
            .map(e -> e.substring(rootEntryName.length() + 1))
1✔
915
            .sorted()
1✔
916
            .forEach(entry -> {
1✔
917
                if (isBinaryEntry(entry)) {
1✔
918
                    binaries.add(entry);
1✔
919
                } else {
920
                    String[] parts = entry.split("/");
1✔
921
                    if (parts.length > 1) directories.add(parts[0]);
1✔
922
                    files.add(entry);
1✔
923
                }
924
            });
1✔
925

926
        return new CategorizedArchive(directories, binaries, files);
1✔
927
    }
928

929
    private static boolean isBinaryEntry(String entry) {
930
        String[] parts = entry.split("/");
1✔
931
        if (parts.length > 1) {
1✔
932
            return "bin".equalsIgnoreCase(parts[parts.length - 2]);
1✔
933
        }
934

935
        return false;
1✔
936
    }
937

938
    public static List<String> inspectArchiveCompressed(Path src) throws IOException {
939
        String filename = src.getFileName().toString();
1✔
940
        String artifactFileName = getFilename(filename, FileType.getSupportedExtensions());
1✔
941
        String artifactExtension = filename.substring(artifactFileName.length());
1✔
942
        String artifactFileFormat = artifactExtension.substring(1);
1✔
943
        FileType fileType = FileType.of(artifactFileFormat);
1✔
944

945
        try (InputStream fi = Files.newInputStream(src);
1✔
946
             InputStream bi = new BufferedInputStream(fi);
1✔
947
             InputStream gzi = resolveCompressorInputStream(fileType, bi);
1✔
948
             ArchiveInputStream<?> in = new TarArchiveInputStream(gzi)) {
1✔
949
            return inspectArchive(in);
1✔
950
        }
951
    }
952

953
    private static List<String> inspectArchive(ArchiveInputStream<?> in) throws IOException {
954
        List<String> entries = new ArrayList<>();
1✔
955

956
        ArchiveEntry entry = null;
1✔
957
        while (null != (entry = in.getNextEntry())) {
1✔
958
            if (!in.canReadEntryData(entry)) {
1✔
959
                // log something?
960
                continue;
×
961
            }
962
            entries.add(entry.getName());
1✔
963
        }
964

965
        return entries;
1✔
966
    }
967

968
    public static void deleteFiles(Path path) throws IOException {
969
        deleteFiles(path, false);
1✔
970
    }
1✔
971

972
    public static void deleteFiles(Path path, boolean keepRoot) throws IOException {
973
        if (Files.exists(path)) {
1✔
974
            try (Stream<Path> stream = Files.walk(path)) {
1✔
975
                stream
1✔
976
                    .sorted(Comparator.reverseOrder())
1✔
977
                    .map(Path::toFile)
1✔
978
                    .forEach(File::delete);
1✔
979
            }
980
            if (!keepRoot) Files.deleteIfExists(path);
1✔
981
        }
982
    }
1✔
983

984
    public static void createDirectoriesWithFullAccess(Path path) throws IOException {
985
        createDirectories(path, "rwxrwxrwx");
1✔
986
    }
1✔
987

988
    public static void createDirectories(Path path, String accessRights) throws IOException {
989
        if (supportsPosix(path)) {
1✔
990
            Set<PosixFilePermission> perms = PosixFilePermissions.fromString(accessRights);
1✔
991
            FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
1✔
992
            Files.createDirectories(path, attr);
1✔
993
        } else {
1✔
994
            Files.createDirectories(path);
×
995
        }
996
    }
1✔
997

998
    public static void grantFullAccess(Path path) throws IOException {
999
        grantAccess(path, "rwxrwxrwx");
1✔
1000
    }
1✔
1001

1002
    public static void grantExecutableAccess(Path path) throws IOException {
1003
        grantAccess(path, "r-xr-xr-x");
1✔
1004
    }
1✔
1005

1006
    public static void grantAccess(Path path, String accessRights) throws IOException {
1007
        if (supportsPosix(path)) {
1✔
1008
            Set<PosixFilePermission> perms = PosixFilePermissions.fromString(accessRights);
1✔
1009
            Files.setPosixFilePermissions(path, perms);
1✔
1010
        } else if (accessRights.contains("r")) {
1✔
1011
            path.toFile().setReadable(true);
×
1012
        } else if (accessRights.contains("w")) {
×
1013
            path.toFile().setWritable(true);
×
1014
        } else if (accessRights.contains("x")) {
×
1015
            path.toFile().setExecutable(true);
×
1016
        }
1017
    }
1✔
1018

1019
    public static void copyPermissions(Path src, Path dest) throws IOException {
1020
        if (supportsPosix(src)) {
1✔
1021
            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(src);
1✔
1022
            Files.setPosixFilePermissions(dest, perms);
1✔
1023
        } else {
1✔
1024
            File s = src.toFile();
×
1025
            File d = dest.toFile();
×
1026
            d.setReadable(s.canRead());
×
1027
            d.setWritable(s.canWrite());
×
1028
            d.setExecutable(s.canExecute());
×
1029
        }
1030
    }
1✔
1031

1032
    public static void copyFiles(JReleaserLogger logger, Path source, Path target) throws IOException {
1033
        copyFiles(logger, source, target, path -> true);
1✔
1034
    }
1✔
1035

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

1039
        Predicate<Path> actualFilter = null != filter ? filter : path -> true;
1✔
1040
        IOException[] thrown = new IOException[1];
1✔
1041

1042
        try (Stream<Path> stream = Files.list(source)) {
1✔
1043
            Files.createDirectories(target);
1✔
1044
            stream
1✔
1045
                .filter(Files::isRegularFile)
1✔
1046
                .filter(actualFilter)
1✔
1047
                .forEach(child -> {
1✔
1048
                    try {
1049
                        Files.copy(child, target.resolve(child.getFileName()), REPLACE_EXISTING);
1✔
1050
                    } catch (IOException e) {
×
1051
                        logger.error(RB.$("ERROR_files_copy"), child, e);
×
1052
                        if (null == thrown[0]) thrown[0] = e;
×
1053
                    }
1✔
1054
                });
1✔
1055
        }
1056

1057
        if (null != thrown[0]) {
1✔
1058
            throw thrown[0];
×
1059
        }
1060
    }
1✔
1061

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

1065
        for (Path path : paths) {
1✔
1066
            Path srcPath = source.resolve(path);
1✔
1067
            Path targetPath = target.resolve(path);
1✔
1068

1069
            Files.createDirectories(targetPath.getParent());
1✔
1070
            Files.copy(srcPath, targetPath, REPLACE_EXISTING);
1✔
1071
        }
1✔
1072
    }
1✔
1073

1074
    public static boolean copyFilesRecursive(JReleaserLogger logger, Path source, Path target) throws IOException {
1075
        return copyFilesRecursive(logger, source, target, null);
1✔
1076
    }
1077

1078
    public static boolean copyFilesRecursive(JReleaserLogger logger, Path source, Path target, Predicate<Path> filter) throws IOException {
1079
        FileTreeCopy copier = new FileTreeCopy(logger, source, target, filter);
1✔
1080
        Files.walkFileTree(source, copier);
1✔
1081
        return copier.isSuccessful();
1✔
1082
    }
1083

1084
    public static class CategorizedArchive {
1085
        private final Set<String> directories = new LinkedHashSet<>();
1✔
1086
        private final List<String> binaries = new ArrayList<>();
1✔
1087
        private final List<String> files = new ArrayList<>();
1✔
1088

1089
        public CategorizedArchive(Set<String> directories, List<String> binaries, List<String> files) {
1✔
1090
            this.directories.addAll(directories);
1✔
1091
            this.binaries.addAll(binaries);
1✔
1092
            this.files.addAll(files);
1✔
1093
        }
1✔
1094

1095
        public Set<String> getDirectories() {
1096
            return unmodifiableSet(directories);
1✔
1097
        }
1098

1099
        public List<String> getBinaries() {
1100
            return unmodifiableList(binaries);
1✔
1101
        }
1102

1103
        public List<String> getFiles() {
1104
            return unmodifiableList(files);
1✔
1105
        }
1106
    }
1107

1108
    private static class FileTreeCopy implements FileVisitor<Path> {
1109
        private final JReleaserLogger logger;
1110
        private final Path source;
1111
        private final Path target;
1112
        private final Predicate<Path> filter;
1113
        private boolean success = true;
1✔
1114

1115
        FileTreeCopy(JReleaserLogger logger, Path source, Path target, Predicate<Path> filter) {
1✔
1116
            this.logger = logger;
1✔
1117
            this.source = source;
1✔
1118
            this.target = target;
1✔
1119
            this.filter = filter;
1✔
1120
            logger.debug(RB.$("files.copy", source, target));
1✔
1121
        }
1✔
1122

1123
        private boolean filtered(Path path) {
1124
            if (null != filter) {
1✔
1125
                return filter.test(path);
1✔
1126
            }
1127
            return false;
1✔
1128
        }
1129

1130
        public boolean isSuccessful() {
1131
            return success;
1✔
1132
        }
1133

1134
        @Override
1135
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
1136
            if (filtered(dir)) return SKIP_SUBTREE;
1✔
1137

1138
            Path newdir = target.resolve(source.relativize(dir));
1✔
1139
            try {
1140
                Files.copy(dir, newdir);
1✔
1141
                FileUtils.grantFullAccess(newdir);
1✔
1142
            } catch (FileAlreadyExistsException ignored) {
1✔
1143
                // noop
1144
            } catch (IOException e) {
×
1145
                logger.error(RB.$("ERROR_files_create"), newdir, e);
×
1146
                success = false;
×
1147
                return SKIP_SUBTREE;
×
1148
            }
1✔
1149
            return CONTINUE;
1✔
1150
        }
1151

1152
        @Override
1153
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
1154
            if (filtered(file)) return CONTINUE;
1✔
1155

1156
            try {
1157
                Path newfile = target.resolve(source.relativize(file));
1✔
1158
                Files.copy(file, newfile, REPLACE_EXISTING);
1✔
1159
                FileUtils.copyPermissions(file, newfile);
1✔
1160
            } catch (IOException e) {
×
1161
                logger.error(RB.$("ERROR_files_copy"), source, e);
×
1162
                success = false;
×
1163
            }
1✔
1164
            return CONTINUE;
1✔
1165
        }
1166

1167
        @Override
1168
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
1169
            if (filtered(dir)) return CONTINUE;
1✔
1170

1171
            if (null == exc) {
1✔
1172
                Path newdir = target.resolve(source.relativize(dir));
1✔
1173
                try {
1174
                    FileTime time = Files.getLastModifiedTime(dir);
1✔
1175
                    Files.setLastModifiedTime(newdir, time);
1✔
1176
                } catch (IOException e) {
×
1177
                    logger.warn(RB.$("ERROR_files_copy_attributes"), newdir, e);
×
1178
                }
1✔
1179
            }
1180
            return CONTINUE;
1✔
1181
        }
1182

1183
        @Override
1184
        public FileVisitResult visitFileFailed(Path file, IOException e) {
1185
            if (e instanceof FileSystemLoopException) {
×
1186
                logger.error(RB.$("ERROR_files_cycle"), file);
×
1187
            } else {
1188
                logger.error(RB.$("ERROR_files_copy"), file, e);
×
1189
            }
1190
            success = false;
×
1191
            return CONTINUE;
×
1192
        }
1193
    }
1194
}
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