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