remove debug
[nikiroo-utils.git] / Image.java
1 package be.nikiroo.utils;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.Closeable;
5 import java.io.File;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.ObjectInputStream;
9 import java.io.ObjectOutputStream;
10 import java.io.OutputStream;
11 import java.io.Serializable;
12
13 import be.nikiroo.utils.streams.Base64InputStream;
14 import be.nikiroo.utils.streams.MarkableFileInputStream;
15
16 /**
17 * This class represents an image data.
18 *
19 * @author niki
20 */
21 public class Image implements Closeable, Serializable {
22 static private final long serialVersionUID = 1L;
23
24 static private File tempRoot;
25 static private TempFiles tmpRepository;
26 static private long count = 0;
27 static private Object lock = new Object();
28
29 private Object instanceLock = new Object();
30 private File data;
31 private long size;
32
33 /**
34 * Do not use -- for serialisation purposes only.
35 */
36 @SuppressWarnings("unused")
37 private Image() {
38 }
39
40 /**
41 * Create a new {@link Image} with the given data.
42 *
43 * @param data
44 * the data
45 */
46 public Image(byte[] data) {
47 ByteArrayInputStream in = new ByteArrayInputStream(data);
48 try {
49 this.data = getTemporaryFile();
50 size = IOUtils.write(in, this.data);
51 } catch (IOException e) {
52 throw new RuntimeException(e);
53 } finally {
54 try {
55 in.close();
56 } catch (IOException e) {
57 throw new RuntimeException(e);
58 }
59 }
60 }
61
62 /**
63 * Create an image from Base64 encoded data.
64 *
65 * <p>
66 * Please use {@link Image#Image(InputStream)} when possible instead, with a
67 * {@link Base64InputStream}; it can be much more efficient.
68 *
69 * @param base64EncodedData
70 * the Base64 encoded data as a String
71 *
72 * @throws IOException
73 * in case of I/O error or badly formated Base64
74 */
75 public Image(String base64EncodedData) throws IOException {
76 this(new Base64InputStream(new ByteArrayInputStream(
77 StringUtils.getBytes(base64EncodedData)), false));
78 }
79
80 /**
81 * Create a new {@link Image} from a stream.
82 *
83 * @param in
84 * the stream
85 *
86 * @throws IOException
87 * in case of I/O error
88 */
89 public Image(InputStream in) throws IOException {
90 data = getTemporaryFile();
91 size = IOUtils.write(in, data);
92 }
93
94 /**
95 * The size of the enclosed image in bytes.
96 *
97 * @return the size
98 */
99 public long getSize() {
100 return size;
101 }
102
103 /**
104 * Generate an {@link InputStream} that you can {@link InputStream#reset()}
105 * for this {@link Image}.
106 * <p>
107 * This {@link InputStream} will (always) be a new one, and <b>you</b> are
108 * responsible for it.
109 * <p>
110 * Note: take care that the {@link InputStream} <b>must not</b> live past
111 * the {@link Image} life time!
112 *
113 * @return the stream
114 *
115 * @throws IOException
116 * in case of I/O error
117 */
118 public InputStream newInputStream() throws IOException {
119 synchronized (instanceLock) {
120 if (data == null) {
121 throw new IOException("Image was close()d");
122 }
123
124 return new MarkableFileInputStream(data);
125 }
126 }
127
128 /**
129 * <b>Read</b> the actual image data, as a byte array.
130 *
131 * @deprecated if possible, prefer the {@link Image#newInputStream()}
132 * method, as it can be more efficient
133 *
134 * @return the image data
135 */
136 @Deprecated
137 public byte[] getData() {
138 try {
139 InputStream in = newInputStream();
140 try {
141 return IOUtils.toByteArray(in);
142 } finally {
143 in.close();
144 }
145 } catch (IOException e) {
146 throw new RuntimeException(e);
147 }
148 }
149
150 /**
151 * Convert the given {@link Image} object into a Base64 representation of
152 * the same {@link Image} object.
153 *
154 * @deprecated Please use {@link Image#newInputStream()} instead, it is more
155 * efficient
156 *
157 * @return the Base64 representation
158 */
159 @Deprecated
160 public String toBase64() {
161 try {
162 Base64InputStream stream = new Base64InputStream(newInputStream(),
163 true);
164 try {
165 return IOUtils.readSmallStream(stream);
166 } finally {
167 stream.close();
168 }
169 } catch (IOException e) {
170 return null;
171 }
172 }
173
174 /**
175 * Closing the {@link Image} will delete the associated temporary file on
176 * disk.
177 * <p>
178 * Note that even if you don't, the program will still <b>try</b> to delete
179 * all the temporary files at JVM termination.
180 */
181 @Override
182 public void close() throws IOException {
183 synchronized (instanceLock) {
184 if (size >= 0) {
185 size = -1;
186 data.delete();
187 data = null;
188
189 synchronized (lock) {
190 count--;
191 if (count <= 0) {
192 count = 0;
193 tmpRepository.close();
194 tmpRepository = null;
195 }
196 }
197 }
198 }
199 }
200
201 @Override
202 protected void finalize() throws Throwable {
203 try {
204 close();
205 } finally {
206 super.finalize();
207 }
208 }
209
210 /**
211 * Return a newly created temporary file to work on.
212 *
213 * @return the file
214 *
215 * @throws IOException
216 * in case of I/O error
217 */
218 private File getTemporaryFile() throws IOException {
219 synchronized (lock) {
220 if (tmpRepository == null) {
221 tmpRepository = new TempFiles(tempRoot, "images");
222 count = 0;
223 }
224
225 count++;
226
227 return tmpRepository.createTempFile("image");
228 }
229 }
230
231 /**
232 * Write this {@link Image} for serialization purposes; that is, write the
233 * content of the backing temporary file.
234 *
235 * @param out
236 * the {@link OutputStream} to write to
237 *
238 * @throws IOException
239 * in case of I/O error
240 */
241 private void writeObject(ObjectOutputStream out) throws IOException {
242 InputStream in = newInputStream();
243 try {
244 IOUtils.write(in, out);
245 } finally {
246 in.close();
247 }
248 }
249
250 /**
251 * Read an {@link Image} written by
252 * {@link Image#writeObject(java.io.ObjectOutputStream)}; that is, create a
253 * new temporary file with the saved content.
254 *
255 * @param in
256 * the {@link InputStream} to read from
257 * @throws IOException
258 * in case of I/O error
259 * @throws ClassNotFoundException
260 * will not be thrown by this method
261 */
262 @SuppressWarnings("unused")
263 private void readObject(ObjectInputStream in) throws IOException,
264 ClassNotFoundException {
265 data = getTemporaryFile();
266 IOUtils.write(in, data);
267 }
268
269 /**
270 * Change the temporary root directory used by the program.
271 * <p>
272 * Caution: the directory will be <b>owned</b> by the system, all its files
273 * now belong to us (and will most probably be deleted).
274 * <p>
275 * Note: it may take some time until the new temporary root is used, we
276 * first need to make sure the previous one is not used anymore (i.e., we
277 * must reach a point where no unclosed {@link Image} remains in memory) to
278 * switch the temporary root.
279 *
280 * @param root
281 * the new temporary root, which will be <b>owned</b> by the
282 * system
283 */
284 public static void setTemporaryFilesRoot(File root) {
285 tempRoot = root;
286 }
287 }