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