1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.io.compress;
33
34 import com.google.common.annotations.Beta;
35 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36 import java.io.BufferedOutputStream;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.io.UncheckedIOException;
41 import java.net.URI;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.FileSystem;
44 import java.nio.file.FileSystems;
45 import java.nio.file.FileVisitResult;
46 import java.nio.file.Files;
47 import java.nio.file.Path;
48 import java.nio.file.Paths;
49 import java.nio.file.SimpleFileVisitor;
50 import java.nio.file.StandardCopyOption;
51 import java.nio.file.attribute.BasicFileAttributes;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Set;
57 import java.util.function.Predicate;
58 import java.util.stream.Stream;
59 import java.util.zip.ZipEntry;
60 import java.util.zip.ZipInputStream;
61 import java.util.zip.ZipOutputStream;
62 import javax.annotation.Nonnull;
63 import javax.annotation.ParametersAreNonnullByDefault;
64 import org.spf4j.io.BufferedInputStream;
65 import org.spf4j.io.Streams;
66 import org.spf4j.recyclable.impl.ArraySuppliers;
67
68
69
70
71 @Beta
72 @SuppressFBWarnings("AFBR_ABNORMAL_FINALLY_BLOCK_RETURN")
73 @ParametersAreNonnullByDefault
74 public final class Compress {
75
76 private Compress() {
77 }
78
79
80
81
82
83
84
85 @Nonnull
86 public static Path zip(final Path fileOrFolderToCompress) throws IOException {
87 Path parent = fileOrFolderToCompress.getParent();
88 if (parent == null) {
89 throw new IllegalArgumentException("Not a file: " + fileOrFolderToCompress);
90 }
91 Path destFile = parent.resolve(fileOrFolderToCompress.getFileName() + ".zip");
92 zip(fileOrFolderToCompress, destFile);
93 return destFile;
94 }
95
96
97
98
99
100
101
102 @Nonnull
103 public static void zip(final Path fileOrFolderToCompress,
104 final Path destFile) throws IOException {
105 zip(fileOrFolderToCompress, destFile, (p) -> true);
106 }
107
108
109
110
111
112
113
114 @Nonnull
115 public static void zip(final Path fileOrFolderToCompress,
116 final Path destFile, final Predicate<Path> filter) throws IOException {
117 Path parent = destFile.getParent();
118 if (parent == null) {
119 throw new IllegalArgumentException("Parent is null for: " + fileOrFolderToCompress);
120 }
121 Path tmpFile = Files.createTempFile(parent, ".", "tmp");
122 try {
123 Path relativePath;
124 if (Files.isDirectory(fileOrFolderToCompress)) {
125 relativePath = fileOrFolderToCompress;
126 } else {
127 relativePath = fileOrFolderToCompress.getParent();
128 }
129 try (BufferedOutputStream fos = new BufferedOutputStream(Files.newOutputStream(tmpFile));
130 ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8)) {
131 try (Stream<Path> ws = Files.walk(fileOrFolderToCompress)) {
132 ws.forEach((path) -> {
133 if (Files.isDirectory(path)) {
134 return;
135 }
136 if (!filter.test(path)) {
137 return;
138 }
139 String fileName = relativePath.relativize(path).toString();
140 try (InputStream in = new BufferedInputStream(Files.newInputStream(path),
141 8192, ArraySuppliers.Bytes.TL_SUPPLIER)) {
142 ZipEntry ze = new ZipEntry(fileName);
143 zos.putNextEntry(ze);
144 Streams.copy(in, zos);
145 } catch (IOException ex) {
146 throw new UncheckedIOException("Error compressing " + path, ex);
147 }
148 });
149 }
150 }
151 Files.move(tmpFile, destFile,
152 StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
153 } finally {
154 Files.deleteIfExists(tmpFile);
155 }
156 }
157
158
159
160
161
162
163
164
165 public static void copyFileAtomic(final Path source, final Path destinationFile) throws IOException {
166 Path parent = destinationFile.getParent();
167 if (parent == null) {
168 throw new IllegalArgumentException("Destination " + destinationFile + " is not a file");
169 }
170 Path tmpFile = Files.createTempFile(parent, ".", null);
171 try {
172 try (InputStream in = new BufferedInputStream(Files.newInputStream(source),
173 8192, ArraySuppliers.Bytes.TL_SUPPLIER);
174 OutputStream os = new BufferedOutputStream(Files.newOutputStream(tmpFile))) {
175 Streams.copy(in, os);
176 }
177 Files.move(tmpFile, destinationFile,
178 StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
179 } finally {
180 Files.deleteIfExists(tmpFile);
181 }
182 }
183
184
185
186
187
188
189
190
191 @Nonnull
192 public static List<Path> unzip(final Path zipFile) throws IOException {
193 Path parent = zipFile.getParent();
194 if (parent == null) {
195 throw new IllegalArgumentException("File " + zipFile + " cannot be unzipped to null parent folder");
196 }
197 return unzip(zipFile, parent);
198 }
199
200
201
202
203
204
205
206
207 @Nonnull
208 public static List<Path> unzip(final Path zipFile, final Path destinationDirectory) throws IOException {
209 return unzip(zipFile, destinationDirectory, (p) -> true);
210 }
211
212
213
214
215
216
217
218
219 @Nonnull
220 public static List<Path> unzip(final Path zipFile, final Path destinationDirectory,
221 final Predicate<Path> filter) throws IOException {
222 if (!Files.exists(destinationDirectory)) {
223 Files.createDirectories(destinationDirectory);
224 }
225 if (!Files.isDirectory(destinationDirectory)) {
226 throw new IllegalArgumentException("Destination " + destinationDirectory + " must be a directory");
227 }
228 final List<Path> response = new ArrayList<>();
229 URI zipUri = URI.create("jar:" + zipFile.toUri().toURL());
230 synchronized (zipUri.toString().intern()) {
231 try (FileSystem zipFs = FileSystems.newFileSystem(zipUri, Collections.emptyMap())) {
232 for (Path root : zipFs.getRootDirectories()) {
233 Path dest = destinationDirectory.resolve(root.toString().substring(1));
234 Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
235
236 private final Set<Path> created = new HashSet<>();
237
238 @Override
239 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
240 throws IOException {
241 if (!filter.test(file)) {
242 return FileVisitResult.CONTINUE;
243 }
244 Path destination = dest.resolve(root.relativize(file).toString());
245 Path parent = destination.getParent();
246 if (parent != null && created.add(parent)) {
247 Files.createDirectories(parent);
248 }
249 copyFileAtomic(file, destination);
250 response.add(destination);
251 return FileVisitResult.CONTINUE;
252 }
253
254 @Override
255 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
256 if (!filter.test(dir)) {
257 return FileVisitResult.CONTINUE;
258 }
259 return FileVisitResult.CONTINUE;
260 }
261 });
262 }
263 } catch (IOException | RuntimeException ex) {
264 for (Path path : response) {
265 try {
266 Files.delete(path);
267 } catch (IOException | RuntimeException ex2) {
268 ex.addSuppressed(ex2);
269 }
270 }
271 throw ex;
272 }
273 }
274 return response;
275 }
276
277 @Nonnull
278 public static List<Path> unzip2(final Path zipFile, final Path destinationDirectory) throws IOException {
279 return unzip2(zipFile, destinationDirectory, (p) -> true);
280 }
281
282 @Nonnull
283 @SuppressFBWarnings("PATH_TRAVERSAL_IN")
284 public static List<Path> unzip2(final Path zipFile, final Path destDir,
285 final Predicate<Path> filter) throws IOException {
286 final List<Path> response = new ArrayList<>();
287 final Set<Path> created = new HashSet<>();
288 try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(Files.newInputStream(zipFile)))) {
289 ZipEntry zipEntry = zis.getNextEntry();
290 while (zipEntry != null) {
291 String fName = zipEntry.getName();
292 if (fName.contains("..")) {
293 throw new IllegalArgumentException("Backreference " + fName + " not allowed in " + zipFile);
294 }
295 if (filter.test(Paths.get(fName))) {
296 Path newFile = destDir.resolve(fName);
297 Path parent = newFile.getParent();
298 if (parent != null && created.add(parent)) {
299 Files.createDirectories(parent);
300 }
301 try (OutputStream fos = new BufferedOutputStream(Files.newOutputStream(newFile))) {
302 Streams.copy(zis, fos);
303 }
304 response.add(newFile);
305 }
306 zipEntry = zis.getNextEntry();
307 }
308 zis.closeEntry();
309 }
310 return response;
311 }
312
313
314
315 }