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