1 package be
.nikiroo
.utils
;
4 import java
.awt
.geom
.AffineTransform
;
5 import java
.awt
.image
.AffineTransformOp
;
6 import java
.awt
.image
.BufferedImage
;
7 import java
.io
.BufferedReader
;
8 import java
.io
.ByteArrayOutputStream
;
10 import java
.io
.FileInputStream
;
11 import java
.io
.FileOutputStream
;
12 import java
.io
.FileReader
;
13 import java
.io
.FileWriter
;
14 import java
.io
.IOException
;
15 import java
.io
.InputStream
;
16 import java
.io
.OutputStream
;
17 import java
.util
.zip
.ZipEntry
;
18 import java
.util
.zip
.ZipOutputStream
;
20 import javax
.imageio
.ImageIO
;
23 * This class offer some utilities based around Streams.
27 public class IOUtils
{
29 * Write the data to the given {@link File}.
34 * the target {@link File}
37 * in case of I/O error
39 public static void write(InputStream in
, File target
) throws IOException
{
40 OutputStream out
= new FileOutputStream(target
);
49 * Write the data to the given {@link OutputStream}.
54 * the target {@link OutputStream}
57 * in case of I/O error
59 public static void write(InputStream in
, OutputStream out
)
61 byte buffer
[] = new byte[4069];
62 for (int len
= 0; (len
= in
.read(buffer
)) > 0;) {
63 out
.write(buffer
, 0, len
);
68 * Recursively Add a {@link File} (which can thus be a directory, too) to a
69 * {@link ZipOutputStream}.
74 * the path to prepend to the ZIP info before the actual
77 * the source {@link File} (which can be a directory)
79 * FALSE if we need to add a {@link ZipEntry} for base/target,
80 * TRUE to add it at the root of the ZIP
83 * in case of I/O error
85 public static void zip(ZipOutputStream zip
, String base
, File target
,
86 boolean targetIsRoot
) throws IOException
{
87 if (target
.isDirectory()) {
89 if (base
== null || base
.isEmpty()) {
90 base
= target
.getName();
92 base
+= "/" + target
.getName();
94 zip
.putNextEntry(new ZipEntry(base
+ "/"));
96 for (File file
: target
.listFiles()) {
97 zip(zip
, base
, file
, false);
100 if (base
== null || base
.isEmpty()) {
101 base
= target
.getName();
103 base
+= "/" + target
.getName();
105 zip
.putNextEntry(new ZipEntry(base
));
106 FileInputStream in
= new FileInputStream(target
);
108 IOUtils
.write(in
, zip
);
116 * Zip the given source into dest.
119 * the source {@link File} (which can be a directory)
121 * the destination <tt>.zip</tt> file
123 * FALSE if we need to add a {@link ZipEntry} for src, TRUE to
124 * add it at the root of the ZIP
126 * @throws IOException
127 * in case of I/O error
129 public static void zip(File src
, File dest
, boolean srcIsRoot
)
131 OutputStream out
= new FileOutputStream(dest
);
133 ZipOutputStream zip
= new ZipOutputStream(out
);
135 IOUtils
.zip(zip
, "", src
, srcIsRoot
);
145 * Write the {@link String} content to {@link File}.
148 * the directory where to write the {@link File}
150 * the {@link File} name
154 * @throws IOException
155 * in case of I/O error
157 public static void writeSmallFile(File dir
, String filename
, String content
)
163 FileWriter writerVersion
= new FileWriter(new File(dir
, filename
));
165 writerVersion
.write(content
);
167 writerVersion
.close();
172 * Read the whole {@link File} content into a {@link String}.
177 * @return the content
179 * @throws IOException
180 * in case of I/O error
182 public static String
readSmallFile(File file
) throws IOException
{
183 BufferedReader reader
= new BufferedReader(new FileReader(file
));
185 StringBuilder builder
= new StringBuilder();
186 for (String line
= reader
.readLine(); line
!= null; line
= reader
188 builder
.append(line
);
190 return builder
.toString();
197 * Recursively delete the given {@link File}, which may of course also be a
200 * Will silently continue in case of error.
203 * the target to delete
205 public static void deltree(File target
) {
206 for (File file
: target
.listFiles()) {
207 if (file
.isDirectory()) {
210 if (!file
.delete()) {
211 System
.err
.println("Cannot delete file: "
212 + file
.getAbsolutePath());
217 if (!target
.delete()) {
218 System
.err
.println("Cannot delete file: "
219 + target
.getAbsolutePath());
224 * Convert the given {@link InputStream} (which should allow calls to
225 * {@link InputStream#reset() for better perfs}) into an {@link Image}
226 * object, respecting the EXIF transformations if any.
229 * the 'resetable' {@link InputStream}
231 * @return the {@link Image} object
233 * @throws IOException
234 * in case of IO error
236 public static BufferedImage
toImage(InputStream in
) throws IOException
{
237 MarkableFileInputStream tmpIn
= null;
241 } catch (IOException e
) {
242 tmp
= File
.createTempFile("fanfic-tmp-image", ".tmp");
244 IOUtils
.write(in
, tmp
);
245 tmpIn
= new MarkableFileInputStream(new FileInputStream(tmp
));
250 orientation
= getExifTransorm(in
);
251 } catch (Exception e
) {
252 // no EXIF transform, ok
257 BufferedImage image
= ImageIO
.read(in
);
264 throw new IOException("Failed to convert input to image");
267 // Note: this code has been found on internet;
268 // thank you anonymous coder.
269 int width
= image
.getWidth();
270 int height
= image
.getHeight();
271 AffineTransform affineTransform
= new AffineTransform();
273 switch (orientation
) {
275 affineTransform
= null;
278 affineTransform
.scale(-1.0, 1.0);
279 affineTransform
.translate(-width
, 0);
281 case 3: // PI rotation
282 affineTransform
.translate(width
, height
);
283 affineTransform
.rotate(Math
.PI
);
286 affineTransform
.scale(1.0, -1.0);
287 affineTransform
.translate(0, -height
);
289 case 5: // - PI/2 and Flip X
290 affineTransform
.rotate(-Math
.PI
/ 2);
291 affineTransform
.scale(-1.0, 1.0);
293 case 6: // -PI/2 and -width
294 affineTransform
.translate(height
, 0);
295 affineTransform
.rotate(Math
.PI
/ 2);
297 case 7: // PI/2 and Flip
298 affineTransform
.scale(-1.0, 1.0);
299 affineTransform
.translate(-height
, 0);
300 affineTransform
.translate(0, width
);
301 affineTransform
.rotate(3 * Math
.PI
/ 2);
304 affineTransform
.translate(0, width
);
305 affineTransform
.rotate(3 * Math
.PI
/ 2);
308 affineTransform
= null;
312 if (affineTransform
!= null) {
313 AffineTransformOp affineTransformOp
= new AffineTransformOp(
314 affineTransform
, AffineTransformOp
.TYPE_BILINEAR
);
316 BufferedImage transformedImage
= new BufferedImage(width
, height
,
318 transformedImage
= affineTransformOp
319 .filter(image
, transformedImage
);
321 image
= transformedImage
;
334 * Return the version of the program if it follows the VERSION convention
335 * (i.e., if it has a file called VERSION containing the version as a
336 * {@link String} on its binary root).
338 * @return the version, or NULL
340 public static String
getVersion() {
341 String version
= null;
343 InputStream in
= openResource("VERSION");
346 ByteArrayOutputStream ba
= new ByteArrayOutputStream();
350 version
= ba
.toString("UTF-8");
351 } catch (IOException e
) {
359 * Open the given /-separated resource (from the binary root).
364 * @return the opened resource if found, NLL if not
366 public static InputStream
openResource(String name
) {
367 ClassLoader loader
= IOUtils
.class.getClassLoader();
368 if (loader
== null) {
369 loader
= ClassLoader
.getSystemClassLoader();
372 return loader
.getResourceAsStream(name
);
376 * Return the EXIF transformation flag of this image if any.
379 * Note: this code has been found on internet; thank you anonymous coder.
383 * the data {@link InputStream}
385 * @return the transformation flag if any
387 * @throws IOException
388 * in case of IO error
390 private static int getExifTransorm(InputStream in
) throws IOException
{
391 int[] exif_data
= new int[100];
395 /* Read File head, check for JPEG SOI + Exif APP1 */
396 for (int i
= 0; i
< 4; i
++)
397 exif_data
[i
] = in
.read();
399 if (exif_data
[0] != 0xFF || exif_data
[1] != 0xD8
400 || exif_data
[2] != 0xFF || exif_data
[3] != 0xE1)
403 /* Get the marker parameter length count */
404 int length
= (in
.read() << 8 | in
.read());
406 /* Length includes itself, so must be at least 2 */
407 /* Following Exif data length must be at least 6 */
411 /* Read Exif head, check for "Exif" */
412 for (int i
= 0; i
< 6; i
++)
413 exif_data
[i
] = in
.read();
415 if (exif_data
[0] != 0x45 || exif_data
[1] != 0x78
416 || exif_data
[2] != 0x69 || exif_data
[3] != 0x66
417 || exif_data
[4] != 0 || exif_data
[5] != 0)
421 length
= length
> exif_data
.length ? exif_data
.length
: length
;
422 for (int i
= 0; i
< length
; i
++)
423 exif_data
[i
] = in
.read();
426 return -1; /* Length of an IFD entry */
428 /* Discover byte order */
429 if (exif_data
[0] == 0x49 && exif_data
[1] == 0x49)
431 else if (exif_data
[0] == 0x4D && exif_data
[1] == 0x4D)
437 if (is_motorola
== 1) {
438 if (exif_data
[2] != 0)
440 if (exif_data
[3] != 0x2A)
443 if (exif_data
[3] != 0)
445 if (exif_data
[2] != 0x2A)
449 /* Get first IFD offset (offset to IFD0) */
451 if (is_motorola
== 1) {
452 if (exif_data
[4] != 0)
454 if (exif_data
[5] != 0)
456 offset
= exif_data
[6];
458 offset
+= exif_data
[7];
460 if (exif_data
[7] != 0)
462 if (exif_data
[6] != 0)
464 offset
= exif_data
[5];
466 offset
+= exif_data
[4];
468 if (offset
> length
- 2)
469 return -1; /* check end of data segment */
471 /* Get the number of directory entries contained in this IFD */
473 if (is_motorola
== 1) {
474 number_of_tags
= exif_data
[offset
];
475 number_of_tags
<<= 8;
476 number_of_tags
+= exif_data
[offset
+ 1];
478 number_of_tags
= exif_data
[offset
+ 1];
479 number_of_tags
<<= 8;
480 number_of_tags
+= exif_data
[offset
];
482 if (number_of_tags
== 0)
486 /* Search for Orientation Tag in IFD0 */
488 if (offset
> length
- 12)
489 return -1; /* check end of data segment */
492 if (is_motorola
== 1) {
493 tagnum
= exif_data
[offset
];
495 tagnum
+= exif_data
[offset
+ 1];
497 tagnum
= exif_data
[offset
+ 1];
499 tagnum
+= exif_data
[offset
];
501 if (tagnum
== 0x0112)
502 break; /* found Orientation Tag */
503 if (--number_of_tags
== 0)
508 /* Get the Orientation value */
509 if (is_motorola
== 1) {
510 if (exif_data
[offset
+ 8] != 0)
512 set_flag
= exif_data
[offset
+ 9];
514 if (exif_data
[offset
+ 9] != 0)
516 set_flag
= exif_data
[offset
+ 8];