Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / utils / IOUtils.java
1 package be.nikiroo.utils;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.OutputStream;
10 import java.util.ArrayList;
11 import java.util.List;
12 import java.util.zip.ZipEntry;
13 import java.util.zip.ZipInputStream;
14 import java.util.zip.ZipOutputStream;
15
16 import be.nikiroo.utils.streams.MarkableFileInputStream;
17
18 /**
19 * This class offer some utilities based around Streams and Files.
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 *
32 * @return the number of bytes written
33 *
34 * @throws IOException
35 * in case of I/O error
36 */
37 public static long write(InputStream in, File target) throws IOException {
38 OutputStream out = new FileOutputStream(target);
39 try {
40 return write(in, out);
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
51 * @param out
52 * the target {@link OutputStream}
53 *
54 * @return the number of bytes written
55 *
56 * @throws IOException
57 * in case of I/O error
58 */
59 public static long write(InputStream in, OutputStream out)
60 throws IOException {
61 long written = 0;
62 byte buffer[] = new byte[4096];
63 int len = in.read(buffer);
64 while (len > -1) {
65 out.write(buffer, 0, len);
66 written += len;
67 len = in.read(buffer);
68 }
69
70 return written;
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 }
102
103 File[] files = target.listFiles();
104 if (files != null) {
105 for (File file : files) {
106 zip(zip, base, file, false);
107 }
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
132 * @param srcIsRoot
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
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
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
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 {
241 FileOutputStream out = new FileOutputStream(file);
242 try {
243 out.write(StringUtils.getBytes(content));
244 } finally {
245 out.close();
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 {
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 {
281 ByteArrayOutputStream out = new ByteArrayOutputStream();
282 try {
283 write(stream, out);
284 return out.toString("UTF-8");
285 } finally {
286 out.close();
287 }
288 }
289
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()) {
313 StringBuilder slist = new StringBuilder();
314 for (File file : list) {
315 slist.append("\n").append(file.getPath());
316 }
317
318 throw new IOException("Cannot delete all the files from: <" //
319 + target + ">:" + slist.toString());
320 }
321
322 return list.isEmpty();
323 }
324
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
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
353 */
354 public static List<File> deltree(File target, List<File> errorAcc) {
355 if (errorAcc == null) {
356 errorAcc = new ArrayList<File>();
357 }
358
359 File[] files = target.listFiles();
360 if (files != null) {
361 for (File file : files) {
362 errorAcc = deltree(file, errorAcc);
363 }
364 }
365
366 if (!target.delete()) {
367 errorAcc.add(target);
368 }
369
370 return errorAcc;
371 }
372
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
390 /**
391 * Open the given /-separated resource (from the binary root).
392 *
393 * @param name
394 * the resource name (the full path, with "/" as separator)
395 *
396 * @return the opened resource if found, NULL if not
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 }
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 {
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
432 final File tmp = File.createTempFile(".tmp-stream.", ".tmp");
433 try {
434 write(in, tmp);
435 in.close();
436
437 return new MarkableFileInputStream(tmp) {
438 @Override
439 public void close() throws IOException {
440 try {
441 super.close();
442 } finally {
443 tmp.delete();
444 }
445 }
446 };
447 } catch (IOException e) {
448 tmp.delete();
449 throw e;
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();
466 try {
467 write(in, out);
468 return out.toByteArray();
469 } finally {
470 out.close();
471 }
472 }
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 }
493 }