1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.archivers.zip;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.Serializable;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.SeekableByteChannel;
26 import java.nio.file.Files;
27 import java.nio.file.OpenOption;
28 import java.nio.file.Path;
29 import java.nio.file.StandardOpenOption;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Comparator;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.regex.Pattern;
36 import java.util.stream.Collectors;
37 import java.util.stream.Stream;
38
39 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
40 import org.apache.commons.compress.utils.MultiReadOnlySeekableByteChannel;
41 import org.apache.commons.io.file.PathUtils;
42
43
44
45
46
47
48
49
50
51 public class ZipSplitReadOnlySeekableByteChannel extends MultiReadOnlySeekableByteChannel {
52 private static final class ZipSplitSegmentComparator implements Comparator<Path>, Serializable {
53 private static final long serialVersionUID = 20200123L;
54
55 @Override
56 public int compare(final Path file1, final Path file2) {
57 final String extension1 = PathUtils.getExtension(file1);
58 final String extension2 = PathUtils.getExtension(file2);
59 if (!extension1.startsWith("z")) {
60 return -1;
61 }
62 if (!extension2.startsWith("z")) {
63 return 1;
64 }
65 final Integer splitSegmentNumber1 = Integer.parseInt(extension1.substring(1));
66 final Integer splitSegmentNumber2 = Integer.parseInt(extension2.substring(1));
67 return splitSegmentNumber1.compareTo(splitSegmentNumber2);
68 }
69 }
70
71 private static final Path[] EMPTY_PATH_ARRAY = {};
72 private static final int ZIP_SPLIT_SIGNATURE_LENGTH = 4;
73
74
75
76
77
78
79
80
81
82 public static SeekableByteChannel buildFromLastSplitSegment(final File lastSegmentFile) throws IOException {
83 return buildFromLastSplitSegment(lastSegmentFile.toPath());
84 }
85
86
87
88
89
90
91
92
93
94
95 public static SeekableByteChannel buildFromLastSplitSegment(final Path lastSegmentPath) throws IOException {
96 final String extension = PathUtils.getExtension(lastSegmentPath);
97 if (!extension.equalsIgnoreCase(ArchiveStreamFactory.ZIP)) {
98 throw new IllegalArgumentException("The extension of last ZIP split segment should be .zip");
99 }
100 final Path parent = Objects.nonNull(lastSegmentPath.getParent()) ? lastSegmentPath.getParent() : lastSegmentPath.getFileSystem().getPath(".");
101 final String fileBaseName = PathUtils.getBaseName(lastSegmentPath);
102 final ArrayList<Path> splitZipSegments;
103
104 final Pattern pattern = Pattern.compile(Pattern.quote(fileBaseName) + ".[zZ][0-9]+");
105 try (Stream<Path> walk = Files.walk(parent, 1)) {
106 splitZipSegments = walk.filter(Files::isRegularFile).filter(path -> pattern.matcher(path.getFileName().toString()).matches())
107 .sorted(new ZipSplitSegmentComparator()).collect(Collectors.toCollection(ArrayList::new));
108 }
109 return forPaths(lastSegmentPath, splitZipSegments);
110 }
111
112
113
114
115
116
117
118
119
120
121
122 public static SeekableByteChannel forFiles(final File... files) throws IOException {
123 final List<Path> paths = new ArrayList<>();
124 for (final File f : Objects.requireNonNull(files, "files")) {
125 paths.add(f.toPath());
126 }
127 return forPaths(paths.toArray(EMPTY_PATH_ARRAY));
128 }
129
130
131
132
133
134
135
136
137
138
139
140 public static SeekableByteChannel forFiles(final File lastSegmentFile, final Iterable<File> files) throws IOException {
141 Objects.requireNonNull(files, "files");
142 Objects.requireNonNull(lastSegmentFile, "lastSegmentFile");
143 final List<Path> filesList = new ArrayList<>();
144 files.forEach(f -> filesList.add(f.toPath()));
145 return forPaths(lastSegmentFile.toPath(), filesList);
146 }
147
148
149
150
151
152
153
154
155
156
157 public static SeekableByteChannel forOrderedSeekableByteChannels(final SeekableByteChannel... channels) throws IOException {
158 if (Objects.requireNonNull(channels, "channels").length == 1) {
159 return channels[0];
160 }
161 return new ZipSplitReadOnlySeekableByteChannel(Arrays.asList(channels));
162 }
163
164
165
166
167
168
169
170
171
172
173
174 public static SeekableByteChannel forOrderedSeekableByteChannels(final SeekableByteChannel lastSegmentChannel, final Iterable<SeekableByteChannel> channels)
175 throws IOException {
176 Objects.requireNonNull(channels, "channels");
177 Objects.requireNonNull(lastSegmentChannel, "lastSegmentChannel");
178 final List<SeekableByteChannel> channelsList = new ArrayList<>();
179 channels.forEach(channelsList::add);
180 channelsList.add(lastSegmentChannel);
181 return forOrderedSeekableByteChannels(channelsList.toArray(new SeekableByteChannel[0]));
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196 public static SeekableByteChannel forPaths(final List<Path> paths, final OpenOption[] openOptions) throws IOException {
197 final List<SeekableByteChannel> channels = new ArrayList<>();
198 for (final Path path : Objects.requireNonNull(paths, "paths")) {
199 channels.add(Files.newByteChannel(path, openOptions));
200 }
201 if (channels.size() == 1) {
202 return channels.get(0);
203 }
204 return new ZipSplitReadOnlySeekableByteChannel(channels);
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218 public static SeekableByteChannel forPaths(final Path... paths) throws IOException {
219 return forPaths(Arrays.asList(paths), new OpenOption[] { StandardOpenOption.READ });
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233 public static SeekableByteChannel forPaths(final Path lastSegmentPath, final Iterable<Path> paths) throws IOException {
234 Objects.requireNonNull(paths, "paths");
235 Objects.requireNonNull(lastSegmentPath, "lastSegmentPath");
236 final List<Path> filesList = new ArrayList<>();
237 paths.forEach(filesList::add);
238 filesList.add(lastSegmentPath);
239 return forPaths(filesList.toArray(EMPTY_PATH_ARRAY));
240 }
241
242 private final ByteBuffer zipSplitSignatureByteBuffer = ByteBuffer.allocate(ZIP_SPLIT_SIGNATURE_LENGTH);
243
244
245
246
247
248
249
250
251
252
253
254
255
256 public ZipSplitReadOnlySeekableByteChannel(final List<SeekableByteChannel> channels) throws IOException {
257 super(channels);
258
259 assertSplitSignature(channels);
260 }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277 private void assertSplitSignature(final List<SeekableByteChannel> channels) throws IOException {
278 final SeekableByteChannel channel = channels.get(0);
279
280 channel.position(0L);
281 zipSplitSignatureByteBuffer.rewind();
282 channel.read(zipSplitSignatureByteBuffer);
283 final ZipLong signature = new ZipLong(zipSplitSignatureByteBuffer.array());
284 if (!signature.equals(ZipLong.DD_SIG)) {
285 channel.position(0L);
286 throw new IOException("The first ZIP split segment does not begin with split ZIP file signature");
287 }
288 channel.position(0L);
289 }
290 }