Fix \t handling bug in Bundle
[fanfix.git] / src / be / nikiroo / utils / IOUtils.java
CommitLineData
ec1f3444
NR
1package be.nikiroo.utils;
2
b607df60
NR
3import java.awt.Image;
4import java.awt.geom.AffineTransform;
5import java.awt.image.AffineTransformOp;
6import java.awt.image.BufferedImage;
ec1f3444
NR
7import java.io.BufferedReader;
8import java.io.File;
9import java.io.FileInputStream;
10import java.io.FileOutputStream;
11import java.io.FileReader;
12import java.io.FileWriter;
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.OutputStream;
ec1f3444
NR
16import java.util.zip.ZipEntry;
17import java.util.zip.ZipOutputStream;
18
b607df60
NR
19import javax.imageio.ImageIO;
20
ec1f3444 21/**
72c32e88 22 * This class offer some utilities based around Streams.
ec1f3444
NR
23 *
24 * @author niki
25 */
26public class IOUtils {
27 /**
28 * Write the data to the given {@link File}.
29 *
30 * @param in
31 * the data source
32 * @param target
33 * the target {@link File}
34 *
35 * @throws IOException
36 * in case of I/O error
37 */
38 public static void write(InputStream in, File target) throws IOException {
39 OutputStream out = new FileOutputStream(target);
40 try {
41 write(in, out);
42 } finally {
43 out.close();
44 }
45 }
46
47 /**
48 * Write the data to the given {@link OutputStream}.
49 *
50 * @param in
51 * the data source
52 * @param target
53 * the target {@link OutputStream}
54 *
55 * @throws IOException
56 * in case of I/O error
57 */
58 public static void write(InputStream in, OutputStream out)
59 throws IOException {
60 byte buffer[] = new byte[4069];
61 for (int len = 0; (len = in.read(buffer)) > 0;) {
62 out.write(buffer, 0, len);
63 }
64 }
65
66 /**
67 * Recursively Add a {@link File} (which can thus be a directory, too) to a
68 * {@link ZipOutputStream}.
69 *
70 * @param zip
71 * the stream
72 * @param base
73 * the path to prepend to the ZIP info before the actual
74 * {@link File} path
75 * @param target
76 * the source {@link File} (which can be a directory)
77 * @param targetIsRoot
78 * FALSE if we need to add a {@link ZipEntry} for base/target,
79 * TRUE to add it at the root of the ZIP
80 *
81 * @throws IOException
82 * in case of I/O error
83 */
84 public static void zip(ZipOutputStream zip, String base, File target,
85 boolean targetIsRoot) throws IOException {
86 if (target.isDirectory()) {
87 if (!targetIsRoot) {
88 if (base == null || base.isEmpty()) {
89 base = target.getName();
90 } else {
91 base += "/" + target.getName();
92 }
93 zip.putNextEntry(new ZipEntry(base + "/"));
94 }
95 for (File file : target.listFiles()) {
96 zip(zip, base, file, false);
97 }
98 } else {
99 if (base == null || base.isEmpty()) {
100 base = target.getName();
101 } else {
102 base += "/" + target.getName();
103 }
104 zip.putNextEntry(new ZipEntry(base));
105 FileInputStream in = new FileInputStream(target);
106 try {
107 IOUtils.write(in, zip);
108 } finally {
109 in.close();
110 }
111 }
112 }
113
114 /**
115 * Zip the given source into dest.
116 *
117 * @param src
118 * the source {@link File} (which can be a directory)
119 * @param dest
120 * the destination <tt>.zip</tt> file
121 * @param srctIsRoot
122 * FALSE if we need to add a {@link ZipEntry} for src, TRUE to
123 * add it at the root of the ZIP
124 *
125 * @throws IOException
126 * in case of I/O error
127 */
128 public static void zip(File src, File dest, boolean srcIsRoot)
129 throws IOException {
130 OutputStream out = new FileOutputStream(dest);
131 try {
132 ZipOutputStream zip = new ZipOutputStream(out);
133 try {
134 IOUtils.zip(zip, "", src, srcIsRoot);
135 } finally {
136 zip.close();
137 }
138 } finally {
139 out.close();
140 }
141 }
142
143 /**
144 * Write the {@link String} content to {@link File}.
145 *
146 * @param dir
147 * the directory where to write the {@link File}
148 * @param filename
149 * the {@link File} name
150 * @param content
151 * the content
152 *
153 * @throws IOException
154 * in case of I/O error
155 */
156 public static void writeSmallFile(File dir, String filename, String content)
157 throws IOException {
158 if (!dir.exists()) {
159 dir.mkdirs();
160 }
161
162 FileWriter writerVersion = new FileWriter(new File(dir, filename));
163 try {
164 writerVersion.write(content);
165 } finally {
166 writerVersion.close();
167 }
168 }
169
170 /**
171 * Read the whole {@link File} content into a {@link String}.
172 *
173 * @param file
174 * the {@link File}
175 *
176 * @return the content
177 *
178 * @throws IOException
179 * in case of I/O error
180 */
181 public static String readSmallFile(File file) throws IOException {
182 BufferedReader reader = new BufferedReader(new FileReader(file));
183 try {
184 StringBuilder builder = new StringBuilder();
185 for (String line = reader.readLine(); line != null; line = reader
186 .readLine()) {
187 builder.append(line);
188 }
189 return builder.toString();
190 } finally {
191 reader.close();
192 }
193 }
194
195 /**
196 * Recursively delete the given {@link File}, which may of course also be a
197 * directory.
198 * <p>
199 * Will silently continue in case of error.
200 *
201 * @param target
202 * the target to delete
203 */
204 public static void deltree(File target) {
205 for (File file : target.listFiles()) {
206 if (file.isDirectory()) {
207 deltree(file);
208 } else {
209 if (!file.delete()) {
210 System.err.println("Cannot delete file: "
211 + file.getAbsolutePath());
212 }
213 }
214 }
215
216 if (!target.delete()) {
217 System.err.println("Cannot delete file: "
218 + target.getAbsolutePath());
219 }
220 }
b607df60
NR
221
222 /**
223 * Convert the given {@link InputStream} (which should allow calls to
224 * {@link InputStream#reset() for better perfs}) into an {@link Image}
225 * object, respecting the EXIF transformations if any.
226 *
227 * @param in
228 * the 'resetable' {@link InputStream}
229 *
230 * @return the {@link Image} object
231 *
232 * @throws IOException
233 * in case of IO error
234 */
235 static public BufferedImage toImage(InputStream in) throws IOException {
236 MarkableFileInputStream tmpIn = null;
237 File tmp = null;
238 try {
239 in.reset();
240 } catch (IOException e) {
241 tmp = File.createTempFile("fanfic-tmp-image", ".tmp");
242 tmp.deleteOnExit();
243 IOUtils.write(in, tmp);
244 tmpIn = new MarkableFileInputStream(new FileInputStream(tmp));
245 }
246
247 int orientation;
248 try {
249 orientation = getExifTransorm(in);
250 } catch (Exception e) {
251 // no EXIF transform, ok
252 orientation = -1;
253 }
254
255 in.reset();
256 BufferedImage image = ImageIO.read(in);
257
258 if (image == null) {
259 if (tmp != null) {
260 tmp.delete();
261 tmpIn.close();
262 }
263 throw new IOException("Failed to convert input to image");
264 }
265
266 // Note: this code has been found on internet;
267 // thank you anonymous coder.
268 int width = image.getWidth();
269 int height = image.getHeight();
270 AffineTransform affineTransform = new AffineTransform();
271
272 switch (orientation) {
273 case 1:
274 affineTransform = null;
275 break;
276 case 2: // Flip X
277 affineTransform.scale(-1.0, 1.0);
278 affineTransform.translate(-width, 0);
279 break;
280 case 3: // PI rotation
281 affineTransform.translate(width, height);
282 affineTransform.rotate(Math.PI);
283 break;
284 case 4: // Flip Y
285 affineTransform.scale(1.0, -1.0);
286 affineTransform.translate(0, -height);
287 break;
288 case 5: // - PI/2 and Flip X
289 affineTransform.rotate(-Math.PI / 2);
290 affineTransform.scale(-1.0, 1.0);
291 break;
292 case 6: // -PI/2 and -width
293 affineTransform.translate(height, 0);
294 affineTransform.rotate(Math.PI / 2);
295 break;
296 case 7: // PI/2 and Flip
297 affineTransform.scale(-1.0, 1.0);
298 affineTransform.translate(-height, 0);
299 affineTransform.translate(0, width);
300 affineTransform.rotate(3 * Math.PI / 2);
301 break;
302 case 8: // PI / 2
303 affineTransform.translate(0, width);
304 affineTransform.rotate(3 * Math.PI / 2);
305 break;
306 default:
307 affineTransform = null;
308 break;
309 }
310
311 if (affineTransform != null) {
312 AffineTransformOp affineTransformOp = new AffineTransformOp(
313 affineTransform, AffineTransformOp.TYPE_BILINEAR);
314
315 BufferedImage transformedImage = new BufferedImage(width, height,
316 image.getType());
317 transformedImage = affineTransformOp
318 .filter(image, transformedImage);
319
320 image = transformedImage;
321 }
322 //
323
324 if (tmp != null) {
325 tmp.delete();
326 tmpIn.close();
327 }
328
329 return image;
330 }
331
332 /**
333 * Return the EXIF transformation flag of this image if any.
334 *
335 * <p>
336 * Note: this code has been found on internet; thank you anonymous coder.
337 * </p>
338 *
339 * @param in
340 * the data {@link InputStream}
341 *
342 * @return the transformation flag if any
343 *
344 * @throws IOException
345 * in case of IO error
346 */
347 static private int getExifTransorm(InputStream in) throws IOException {
348 int[] exif_data = new int[100];
349 int set_flag = 0;
350 int is_motorola = 0;
351
352 /* Read File head, check for JPEG SOI + Exif APP1 */
353 for (int i = 0; i < 4; i++)
354 exif_data[i] = in.read();
355
356 if (exif_data[0] != 0xFF || exif_data[1] != 0xD8
357 || exif_data[2] != 0xFF || exif_data[3] != 0xE1)
358 return -2;
359
360 /* Get the marker parameter length count */
361 int length = (in.read() << 8 | in.read());
362
363 /* Length includes itself, so must be at least 2 */
364 /* Following Exif data length must be at least 6 */
365 if (length < 8)
366 return -1;
367 length -= 8;
368 /* Read Exif head, check for "Exif" */
369 for (int i = 0; i < 6; i++)
370 exif_data[i] = in.read();
371
372 if (exif_data[0] != 0x45 || exif_data[1] != 0x78
373 || exif_data[2] != 0x69 || exif_data[3] != 0x66
374 || exif_data[4] != 0 || exif_data[5] != 0)
375 return -1;
376
377 /* Read Exif body */
378 length = length > exif_data.length ? exif_data.length : length;
379 for (int i = 0; i < length; i++)
380 exif_data[i] = in.read();
381
382 if (length < 12)
383 return -1; /* Length of an IFD entry */
384
385 /* Discover byte order */
386 if (exif_data[0] == 0x49 && exif_data[1] == 0x49)
387 is_motorola = 0;
388 else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D)
389 is_motorola = 1;
390 else
391 return -1;
392
393 /* Check Tag Mark */
394 if (is_motorola == 1) {
395 if (exif_data[2] != 0)
396 return -1;
397 if (exif_data[3] != 0x2A)
398 return -1;
399 } else {
400 if (exif_data[3] != 0)
401 return -1;
402 if (exif_data[2] != 0x2A)
403 return -1;
404 }
405
406 /* Get first IFD offset (offset to IFD0) */
407 int offset;
408 if (is_motorola == 1) {
409 if (exif_data[4] != 0)
410 return -1;
411 if (exif_data[5] != 0)
412 return -1;
413 offset = exif_data[6];
414 offset <<= 8;
415 offset += exif_data[7];
416 } else {
417 if (exif_data[7] != 0)
418 return -1;
419 if (exif_data[6] != 0)
420 return -1;
421 offset = exif_data[5];
422 offset <<= 8;
423 offset += exif_data[4];
424 }
425 if (offset > length - 2)
426 return -1; /* check end of data segment */
427
428 /* Get the number of directory entries contained in this IFD */
429 int number_of_tags;
430 if (is_motorola == 1) {
431 number_of_tags = exif_data[offset];
432 number_of_tags <<= 8;
433 number_of_tags += exif_data[offset + 1];
434 } else {
435 number_of_tags = exif_data[offset + 1];
436 number_of_tags <<= 8;
437 number_of_tags += exif_data[offset];
438 }
439 if (number_of_tags == 0)
440 return -1;
441 offset += 2;
442
443 /* Search for Orientation Tag in IFD0 */
444 for (;;) {
445 if (offset > length - 12)
446 return -1; /* check end of data segment */
447 /* Get Tag number */
448 int tagnum;
449 if (is_motorola == 1) {
450 tagnum = exif_data[offset];
451 tagnum <<= 8;
452 tagnum += exif_data[offset + 1];
453 } else {
454 tagnum = exif_data[offset + 1];
455 tagnum <<= 8;
456 tagnum += exif_data[offset];
457 }
458 if (tagnum == 0x0112)
459 break; /* found Orientation Tag */
460 if (--number_of_tags == 0)
461 return -1;
462 offset += 12;
463 }
464
465 /* Get the Orientation value */
466 if (is_motorola == 1) {
467 if (exif_data[offset + 8] != 0)
468 return -1;
469 set_flag = exif_data[offset + 9];
470 } else {
471 if (exif_data[offset + 9] != 0)
472 return -1;
473 set_flag = exif_data[offset + 8];
474 }
475 if (set_flag > 8)
476 return -1;
477
478 return set_flag;
479 }
ec1f3444 480}