Commit | Line | Data |
---|---|---|
ec1f3444 NR |
1 | package be.nikiroo.utils; |
2 | ||
80500544 | 3 | import java.io.ByteArrayOutputStream; |
ec1f3444 NR |
4 | import java.io.File; |
5 | import java.io.FileInputStream; | |
6 | import java.io.FileOutputStream; | |
ec1f3444 NR |
7 | import java.io.IOException; |
8 | import java.io.InputStream; | |
9 | import java.io.OutputStream; | |
59864f77 NR |
10 | import java.util.ArrayList; |
11 | import java.util.List; | |
ec1f3444 | 12 | import java.util.zip.ZipEntry; |
4110f63b | 13 | import java.util.zip.ZipInputStream; |
ec1f3444 NR |
14 | import java.util.zip.ZipOutputStream; |
15 | ||
8e76f6ab NR |
16 | import be.nikiroo.utils.streams.MarkableFileInputStream; |
17 | ||
ec1f3444 | 18 | /** |
bb60bd13 | 19 | * This class offer some utilities based around Streams and Files. |
ec1f3444 NR |
20 | * |
21 | * @author niki | |
22 | */ | |
23 | public class IOUtils { | |
24 | /** | |
25 | * Write the data to the given {@link File}. | |
26 | * | |
27 | * @param in | |
28 | * the data source | |
29 | * @param target | |
30 | * the target {@link File} | |
31 | * | |
a2c1d5fe NR |
32 | * @return the number of bytes written |
33 | * | |
ec1f3444 NR |
34 | * @throws IOException |
35 | * in case of I/O error | |
36 | */ | |
a2c1d5fe | 37 | public static long write(InputStream in, File target) throws IOException { |
ec1f3444 NR |
38 | OutputStream out = new FileOutputStream(target); |
39 | try { | |
a2c1d5fe | 40 | return write(in, out); |
ec1f3444 NR |
41 | } finally { |
42 | out.close(); | |
43 | } | |
44 | } | |
45 | ||
46 | /** | |
47 | * Write the data to the given {@link OutputStream}. | |
48 | * | |
49 | * @param in | |
50 | * the data source | |
db31c358 | 51 | * @param out |
ec1f3444 NR |
52 | * the target {@link OutputStream} |
53 | * | |
a2c1d5fe NR |
54 | * @return the number of bytes written |
55 | * | |
ec1f3444 NR |
56 | * @throws IOException |
57 | * in case of I/O error | |
58 | */ | |
a2c1d5fe | 59 | public static long write(InputStream in, OutputStream out) |
ec1f3444 | 60 | throws IOException { |
a2c1d5fe | 61 | long written = 0; |
ea152609 | 62 | byte buffer[] = new byte[4096]; |
e378894c | 63 | int len = in.read(buffer); |
028ff7c2 | 64 | while (len > -1) { |
ec1f3444 | 65 | out.write(buffer, 0, len); |
a2c1d5fe | 66 | written += len; |
e378894c | 67 | len = in.read(buffer); |
ec1f3444 | 68 | } |
a2c1d5fe NR |
69 | |
70 | return written; | |
ec1f3444 NR |
71 | } |
72 | ||
73 | /** | |
74 | * Recursively Add a {@link File} (which can thus be a directory, too) to a | |
75 | * {@link ZipOutputStream}. | |
76 | * | |
77 | * @param zip | |
78 | * the stream | |
79 | * @param base | |
80 | * the path to prepend to the ZIP info before the actual | |
81 | * {@link File} path | |
82 | * @param target | |
83 | * the source {@link File} (which can be a directory) | |
84 | * @param targetIsRoot | |
85 | * FALSE if we need to add a {@link ZipEntry} for base/target, | |
86 | * TRUE to add it at the root of the ZIP | |
87 | * | |
88 | * @throws IOException | |
89 | * in case of I/O error | |
90 | */ | |
91 | public static void zip(ZipOutputStream zip, String base, File target, | |
92 | boolean targetIsRoot) throws IOException { | |
93 | if (target.isDirectory()) { | |
94 | if (!targetIsRoot) { | |
95 | if (base == null || base.isEmpty()) { | |
96 | base = target.getName(); | |
97 | } else { | |
98 | base += "/" + target.getName(); | |
99 | } | |
100 | zip.putNextEntry(new ZipEntry(base + "/")); | |
101 | } | |
0988831f NR |
102 | |
103 | File[] files = target.listFiles(); | |
104 | if (files != null) { | |
105 | for (File file : files) { | |
106 | zip(zip, base, file, false); | |
107 | } | |
ec1f3444 NR |
108 | } |
109 | } else { | |
110 | if (base == null || base.isEmpty()) { | |
111 | base = target.getName(); | |
112 | } else { | |
113 | base += "/" + target.getName(); | |
114 | } | |
115 | zip.putNextEntry(new ZipEntry(base)); | |
116 | FileInputStream in = new FileInputStream(target); | |
117 | try { | |
118 | IOUtils.write(in, zip); | |
119 | } finally { | |
120 | in.close(); | |
121 | } | |
122 | } | |
123 | } | |
124 | ||
125 | /** | |
126 | * Zip the given source into dest. | |
127 | * | |
128 | * @param src | |
129 | * the source {@link File} (which can be a directory) | |
130 | * @param dest | |
131 | * the destination <tt>.zip</tt> file | |
db31c358 | 132 | * @param srcIsRoot |
ec1f3444 NR |
133 | * FALSE if we need to add a {@link ZipEntry} for src, TRUE to |
134 | * add it at the root of the ZIP | |
135 | * | |
136 | * @throws IOException | |
137 | * in case of I/O error | |
138 | */ | |
139 | public static void zip(File src, File dest, boolean srcIsRoot) | |
140 | throws IOException { | |
141 | OutputStream out = new FileOutputStream(dest); | |
142 | try { | |
143 | ZipOutputStream zip = new ZipOutputStream(out); | |
144 | try { | |
145 | IOUtils.zip(zip, "", src, srcIsRoot); | |
146 | } finally { | |
147 | zip.close(); | |
148 | } | |
149 | } finally { | |
150 | out.close(); | |
151 | } | |
152 | } | |
153 | ||
4110f63b NR |
154 | /** |
155 | * Unzip the given ZIP file into the target directory. | |
156 | * | |
157 | * @param zipFile | |
158 | * the ZIP file | |
159 | * @param targetDirectory | |
160 | * the target directory | |
161 | * | |
162 | * @return the number of extracted files (not directories) | |
163 | * | |
164 | * @throws IOException | |
165 | * in case of I/O errors | |
166 | */ | |
167 | public static long unzip(File zipFile, File targetDirectory) | |
168 | throws IOException { | |
169 | long count = 0; | |
170 | ||
171 | if (targetDirectory.exists() && targetDirectory.isFile()) { | |
172 | throw new IOException("Cannot unzip " + zipFile + " into " | |
173 | + targetDirectory + ": it is not a directory"); | |
174 | } | |
175 | ||
176 | targetDirectory.mkdir(); | |
177 | if (!targetDirectory.exists()) { | |
178 | throw new IOException("Cannot create target directory " | |
179 | + targetDirectory); | |
180 | } | |
181 | ||
182 | FileInputStream in = new FileInputStream(zipFile); | |
183 | try { | |
184 | ZipInputStream zipStream = new ZipInputStream(in); | |
185 | try { | |
186 | for (ZipEntry entry = zipStream.getNextEntry(); entry != null; entry = zipStream | |
187 | .getNextEntry()) { | |
188 | File file = new File(targetDirectory, entry.getName()); | |
189 | if (entry.isDirectory()) { | |
190 | file.mkdirs(); | |
191 | } else { | |
192 | IOUtils.write(zipStream, file); | |
193 | count++; | |
194 | } | |
195 | } | |
196 | } finally { | |
197 | zipStream.close(); | |
198 | } | |
199 | } finally { | |
200 | in.close(); | |
201 | } | |
202 | ||
203 | return count; | |
204 | } | |
205 | ||
ec1f3444 NR |
206 | /** |
207 | * Write the {@link String} content to {@link File}. | |
208 | * | |
209 | * @param dir | |
210 | * the directory where to write the {@link File} | |
211 | * @param filename | |
212 | * the {@link File} name | |
213 | * @param content | |
214 | * the content | |
215 | * | |
216 | * @throws IOException | |
217 | * in case of I/O error | |
218 | */ | |
219 | public static void writeSmallFile(File dir, String filename, String content) | |
220 | throws IOException { | |
221 | if (!dir.exists()) { | |
222 | dir.mkdirs(); | |
223 | } | |
224 | ||
ce060f5a NR |
225 | writeSmallFile(new File(dir, filename), content); |
226 | } | |
227 | ||
228 | /** | |
229 | * Write the {@link String} content to {@link File}. | |
230 | * | |
231 | * @param file | |
232 | * the {@link File} to write | |
233 | * @param content | |
234 | * the content | |
235 | * | |
236 | * @throws IOException | |
237 | * in case of I/O error | |
238 | */ | |
239 | public static void writeSmallFile(File file, String content) | |
240 | throws IOException { | |
e378894c | 241 | FileOutputStream out = new FileOutputStream(file); |
ec1f3444 | 242 | try { |
f8147a0e | 243 | out.write(StringUtils.getBytes(content)); |
ec1f3444 | 244 | } finally { |
e378894c | 245 | out.close(); |
ec1f3444 NR |
246 | } |
247 | } | |
248 | ||
249 | /** | |
250 | * Read the whole {@link File} content into a {@link String}. | |
251 | * | |
252 | * @param file | |
253 | * the {@link File} | |
254 | * | |
255 | * @return the content | |
256 | * | |
257 | * @throws IOException | |
258 | * in case of I/O error | |
259 | */ | |
260 | public static String readSmallFile(File file) throws IOException { | |
3f8349b7 NR |
261 | InputStream stream = new FileInputStream(file); |
262 | try { | |
263 | return readSmallStream(stream); | |
264 | } finally { | |
265 | stream.close(); | |
266 | } | |
267 | } | |
268 | ||
269 | /** | |
270 | * Read the whole {@link InputStream} content into a {@link String}. | |
271 | * | |
272 | * @param stream | |
273 | * the {@link InputStream} | |
274 | * | |
275 | * @return the content | |
276 | * | |
277 | * @throws IOException | |
278 | * in case of I/O error | |
279 | */ | |
280 | public static String readSmallStream(InputStream stream) throws IOException { | |
e378894c NR |
281 | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
282 | try { | |
283 | write(stream, out); | |
284 | return out.toString("UTF-8"); | |
285 | } finally { | |
c60b070d | 286 | out.close(); |
ec1f3444 NR |
287 | } |
288 | } | |
289 | ||
59864f77 NR |
290 | /** |
291 | * Recursively delete the given {@link File}, which may of course also be a | |
292 | * directory. | |
293 | * <p> | |
294 | * Will either silently continue or throw an exception in case of error, | |
295 | * depending upon the parameters. | |
296 | * | |
297 | * @param target | |
298 | * the target to delete | |
299 | * @param exception | |
300 | * TRUE to throw an {@link IOException} in case of error, FALSE | |
301 | * to silently continue | |
302 | * | |
303 | * @return TRUE if all files were deleted, FALSE if an error occurred | |
304 | * | |
305 | * @throws IOException | |
306 | * if an error occurred and the parameters allow an exception to | |
307 | * be thrown | |
308 | */ | |
309 | public static boolean deltree(File target, boolean exception) | |
310 | throws IOException { | |
311 | List<File> list = deltree(target, null); | |
312 | if (exception && !list.isEmpty()) { | |
0988831f | 313 | StringBuilder slist = new StringBuilder(); |
59864f77 | 314 | for (File file : list) { |
0988831f | 315 | slist.append("\n").append(file.getPath()); |
59864f77 NR |
316 | } |
317 | ||
318 | throw new IOException("Cannot delete all the files from: <" // | |
0988831f | 319 | + target + ">:" + slist.toString()); |
59864f77 NR |
320 | } |
321 | ||
322 | return list.isEmpty(); | |
323 | } | |
324 | ||
ec1f3444 NR |
325 | /** |
326 | * Recursively delete the given {@link File}, which may of course also be a | |
327 | * directory. | |
328 | * <p> | |
329 | * Will silently continue in case of error. | |
330 | * | |
331 | * @param target | |
332 | * the target to delete | |
59864f77 NR |
333 | * |
334 | * @return TRUE if all files were deleted, FALSE if an error occurred | |
335 | */ | |
336 | public static boolean deltree(File target) { | |
337 | return deltree(target, null).isEmpty(); | |
338 | } | |
339 | ||
340 | /** | |
341 | * Recursively delete the given {@link File}, which may of course also be a | |
342 | * directory. | |
343 | * <p> | |
344 | * Will collect all {@link File} that cannot be deleted in the given | |
345 | * accumulator. | |
346 | * | |
347 | * @param target | |
348 | * the target to delete | |
349 | * @param errorAcc | |
350 | * the accumulator to use for errors, or NULL to create a new one | |
351 | * | |
352 | * @return the errors accumulator | |
ec1f3444 | 353 | */ |
59864f77 NR |
354 | public static List<File> deltree(File target, List<File> errorAcc) { |
355 | if (errorAcc == null) { | |
356 | errorAcc = new ArrayList<File>(); | |
357 | } | |
358 | ||
7ee9568b NR |
359 | File[] files = target.listFiles(); |
360 | if (files != null) { | |
361 | for (File file : files) { | |
59864f77 | 362 | errorAcc = deltree(file, errorAcc); |
ec1f3444 NR |
363 | } |
364 | } | |
365 | ||
366 | if (!target.delete()) { | |
59864f77 | 367 | errorAcc.add(target); |
ec1f3444 | 368 | } |
59864f77 NR |
369 | |
370 | return errorAcc; | |
ec1f3444 | 371 | } |
b607df60 | 372 | |
087a6e8e NR |
373 | /** |
374 | * Open the resource next to the given {@link Class}. | |
375 | * | |
376 | * @param location | |
377 | * the location where to look for the resource | |
378 | * @param name | |
379 | * the resource name (only the filename, no path) | |
380 | * | |
381 | * @return the opened resource if found, NULL if not | |
382 | */ | |
383 | public static InputStream openResource( | |
384 | @SuppressWarnings("rawtypes") Class location, String name) { | |
385 | String loc = location.getName().replace(".", "/") | |
386 | .replaceAll("/[^/]*$", "/"); | |
387 | return openResource(loc + name); | |
388 | } | |
389 | ||
16d59378 NR |
390 | /** |
391 | * Open the given /-separated resource (from the binary root). | |
392 | * | |
393 | * @param name | |
087a6e8e | 394 | * the resource name (the full path, with "/" as separator) |
16d59378 | 395 | * |
087a6e8e | 396 | * @return the opened resource if found, NULL if not |
16d59378 NR |
397 | */ |
398 | public static InputStream openResource(String name) { | |
399 | ClassLoader loader = IOUtils.class.getClassLoader(); | |
400 | if (loader == null) { | |
401 | loader = ClassLoader.getSystemClassLoader(); | |
402 | } | |
403 | ||
404 | return loader.getResourceAsStream(name); | |
405 | } | |
80500544 NR |
406 | |
407 | /** | |
408 | * Return a resetable {@link InputStream} from this stream, and reset it. | |
409 | * | |
410 | * @param in | |
411 | * the input stream | |
412 | * @return the resetable stream, which <b>may</b> be the same | |
413 | * | |
414 | * @throws IOException | |
415 | * in case of I/O error | |
416 | */ | |
417 | public static InputStream forceResetableStream(InputStream in) | |
418 | throws IOException { | |
80500544 NR |
419 | boolean resetable = in.markSupported(); |
420 | if (resetable) { | |
421 | try { | |
422 | in.reset(); | |
423 | } catch (IOException e) { | |
424 | resetable = false; | |
425 | } | |
426 | } | |
427 | ||
428 | if (resetable) { | |
429 | return in; | |
430 | } | |
431 | ||
75712fb3 | 432 | final File tmp = File.createTempFile(".tmp-stream.", ".tmp"); |
80500544 NR |
433 | try { |
434 | write(in, tmp); | |
75712fb3 NR |
435 | in.close(); |
436 | ||
7194ac50 | 437 | return new MarkableFileInputStream(tmp) { |
75712fb3 NR |
438 | @Override |
439 | public void close() throws IOException { | |
440 | try { | |
7194ac50 | 441 | super.close(); |
75712fb3 NR |
442 | } finally { |
443 | tmp.delete(); | |
444 | } | |
80500544 | 445 | } |
75712fb3 NR |
446 | }; |
447 | } catch (IOException e) { | |
448 | tmp.delete(); | |
449 | throw e; | |
80500544 NR |
450 | } |
451 | } | |
452 | ||
453 | /** | |
454 | * Convert the {@link InputStream} into a byte array. | |
455 | * | |
456 | * @param in | |
457 | * the input stream | |
458 | * | |
459 | * @return the array | |
460 | * | |
461 | * @throws IOException | |
462 | * in case of I/O error | |
463 | */ | |
464 | public static byte[] toByteArray(InputStream in) throws IOException { | |
465 | ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
e378894c NR |
466 | try { |
467 | write(in, out); | |
468 | return out.toByteArray(); | |
469 | } finally { | |
470 | out.close(); | |
471 | } | |
80500544 | 472 | } |
bb60bd13 NR |
473 | |
474 | /** | |
475 | * Convert the {@link File} into a byte array. | |
476 | * | |
477 | * @param file | |
478 | * the input {@link File} | |
479 | * | |
480 | * @return the array | |
481 | * | |
482 | * @throws IOException | |
483 | * in case of I/O error | |
484 | */ | |
485 | public static byte[] toByteArray(File file) throws IOException { | |
486 | FileInputStream fis = new FileInputStream(file); | |
487 | try { | |
488 | return toByteArray(fis); | |
489 | } finally { | |
490 | fis.close(); | |
491 | } | |
492 | } | |
ec1f3444 | 493 | } |