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