View Javadoc
1   //
2   // $Id: PictureFileData.java 287 2007-06-17 09:07:04 +0000 (dim., 17 juin 2007)
3   // felfert $
4   //
5   // jupload - A file upload applet.
6   // Copyright 2007 The JUpload Team
7   //
8   // Created: 2006-05-09
9   // Creator: etienne_sf
10  // Last modified: $Date: 2015-03-14 15:13:43 +0100 (sam., 14 mars 2015) $
11  //
12  // This program is free software; you can redistribute it and/or modify it under
13  // the terms of the GNU General Public License as published by the Free Software
14  // Foundation; either version 2 of the License, or (at your option) any later
15  // version. This program is distributed in the hope that it will be useful, but
16  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18  // details. You should have received a copy of the GNU General Public License
19  // along with this program; if not, write to the Free Software Foundation, Inc.,
20  // 675 Mass Ave, Cambridge, MA 02139, USA.
21  
22  package wjhk.jupload2.filedata;
23  
24  import java.awt.Canvas;
25  import java.awt.Image;
26  import java.awt.image.BufferedImage;
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileNotFoundException;
30  import java.io.FileOutputStream;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.util.Iterator;
34  
35  import javax.imageio.IIOImage;
36  import javax.imageio.ImageIO;
37  import javax.imageio.ImageReader;
38  import javax.imageio.metadata.IIOMetadata;
39  import javax.imageio.stream.FileImageInputStream;
40  import javax.swing.ImageIcon;
41  import javax.swing.JOptionPane;
42  
43  import wjhk.jupload2.exception.JUploadException;
44  import wjhk.jupload2.exception.JUploadIOException;
45  import wjhk.jupload2.filedata.helper.ImageHelper;
46  import wjhk.jupload2.filedata.helper.ImageReaderWriterHelper;
47  import wjhk.jupload2.policies.PictureUploadPolicy;
48  import wjhk.jupload2.policies.UploadPolicy;
49  
50  /**
51   * This class contains all data about files to upload as a picture. It adds the following elements to the
52   * {@link wjhk.jupload2.filedata.FileData} class :<BR>
53   * <UL>
54   * <LI>Ability to define a target format (to convert pictures to JPG before upload, for instance)
55   * <LI>Optional definition of a maximal width and/or height.
56   * <LI>Ability to rotate a picture, with {@link #addRotation(int)}
57   * <LI>Ability to store a picture into a BufferedImage. This is actualy a bad idea within an applet (should run within a
58   * java application) : the applet runs very quickly out of memory. With pictures from my Canon EOS20D (3,5M), I can only
59   * display two pictures. The third one generates an out of memory error, despite the System.finalize and System.gc I've
60   * put everywhere in the code!
61   * </UL>
62   * 
63   * @author etienne_sf
64   * @version $Revision: 1718 $
65   */
66  public class PictureFileData extends DefaultFileData {
67  
68      /**
69       * Indicates if this file is a picture or not. This is bases on the return of ImageIO.getImageReadersByFormatName().
70       */
71      boolean isPicture = false;
72  
73      /**
74       * This picture is precalculated, and stored to avoid to calculate it each time the user select this picture again,
75       * or each time the use switch from an application to another.
76       */
77      Image offscreenImage = null;
78  
79      /**
80       * quarterRotation contains the current rotation that will be applied to the picture. Its value should be one of 0,
81       * 1, 2, 3. It is controled by the {@link #addRotation(int)} method.
82       * <UL>
83       * <LI>0 means no rotation.
84       * <LI>1 means a rotation of 90� clockwise (word = Ok ??).
85       * <LI>2 means a rotation of 180�.
86       * <LI>3 means a rotation of 900 counterclockwise (word = Ok ??).
87       * </UL>
88       */
89      int quarterRotation = 0;
90  
91      /**
92       * Width of the original picture. The width is taken from the first image of the file. We expect that all pictures
93       * in a file are of the same size (for instance, for animated gif). Calculated in the
94       * {@link #PictureFileData(File, File, PictureUploadPolicy)} constructor.
95       */
96      int originalWidth = -1;
97  
98      /**
99       * Same as {@link #originalWidth}, for the height of the first image in the picture file.
100      */
101     int originalHeight = -1;
102 
103     /**
104      * transformedPictureFile contains the reference to the temporary file that stored the transformed picture, during
105      * upload. It is created by {@link #getInputStream()} and freed by {@link #afterUpload()}.
106      */
107     File transformedPictureFile = null;
108 
109     /**
110      * uploadLength contains the uploadLength, which is : <BR>
111      * - The size of the original file, if no transformation is needed. <BR>
112      * - The size of the transformed file, if a transformation were made. <BR>
113      * <BR>
114      * It is set to -1 whenever its calculation is to be done again (for instance, when the user ask for a rotation,
115      * which is currently the only action that need to recalculate the picture).
116      */
117     long uploadLength = -1;
118 
119     /**
120      * Contains the reference to a copy of the original picture files. Originally created because a SUN bug would
121      * prevent picture to be correctly resized if the original picture filename contains accents (or any non-ASCII
122      * characters).
123      */
124 
125     File workingCopyTempFile = null;
126 
127     /**
128      * will be set if in {@link #createTranformedPictureFile(ImageHelper)}, if an image-transformation has occured
129      */
130     String targetPictureFormat;
131 
132     /**
133      * Standard constructor: needs a PictureFileDataPolicy.
134      * 
135      * @param file The files which data are to be handled by this instance.
136      * @param root The root directory, to calculate the relative dir (see {@link #getRelativeDir()}.
137      * @param uploadPolicy The current upload policy
138      * @throws JUploadIOException Encapsulation of the IOException, if any would occurs.
139      */
140     public PictureFileData(File file, PictureUploadPolicy uploadPolicy) throws JUploadIOException {
141         super(file, uploadPolicy);
142 
143         String fileExtension = getFileExtension();
144 
145         // Is it a picture?
146         uploadPolicy.displayDebug("Looking for iterator of extension '" + file + "'", 80);
147         this.isPicture = isFileAPicture(file);
148 
149         // Let's log the test result
150         uploadPolicy.displayDebug("isPicture=" + this.isPicture + " (" + file.getName() + "), extension="
151                 + fileExtension, 50);
152 
153         // If it's a picture, we override the default mime type:
154         if (this.isPicture) {
155             setMimeTypeByExtension(fileExtension);
156         }
157     }
158 
159     /**
160      * Free any available memory. This method is called very often here, to be sure that we don't use too much memory.
161      * But we still run out of memory in some case.
162      * 
163      * @param caller Indicate the method or treatment from which this method is called.
164      * @param uploadPolicy The current upload policy is not available, to this static method...
165      */
166     public static void freeMemory(String caller, UploadPolicy uploadPolicy) {
167         Runtime rt = Runtime.getRuntime();
168 
169         rt.runFinalization();
170         rt.gc();
171 
172         if (uploadPolicy.getDebugLevel() >= 50) {
173             uploadPolicy.displayDebug(
174                     "freeMemory (after " + caller + ") : " + rt.freeMemory() + " (maxMemory: " + rt.maxMemory()
175                             + ", totalMemory: " + rt.totalMemory() + ")", 50);
176         }
177     }
178 
179     /**
180      * If this pictures needs transformation, a temporary file is created. This can occurs if the original picture is
181      * bigger than the maxWidth or maxHeight, of if it has to be rotated. This temporary file contains the transformed
182      * picture. <BR>
183      * The call to this method is optional, if the caller calls {@link #getUploadLength()}. This method calls
184      * beforeUpload() if the uploadLength is unknown.
185      */
186     @Override
187     public void beforeUpload(String uploadFileRoot) throws JUploadException {
188         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.beforeUpload()", 95);
189 
190         if (!this.preparedForUpload) {
191             if (this.uploadLength < 0) {
192                 try {
193                     // Picture management is a big work. Let's try to free some
194                     // memory.
195                     freeMemory("Picture manabeforeUpload(): before initTransformedPictureFile", uploadPolicy);
196 
197                     // Get the transformed picture file, if needed.
198                     initTransformedPictureFile();
199 
200                     // More debug output, to understand where the applet
201                     // freezes.
202                     uploadPolicy.displayDebug(this.getClass().getName()
203                             + ".beforeUpload(): after call to initTransformedPictureFile()", 100);
204 
205                 } catch (OutOfMemoryError e) {
206                     // Oups ! My EOS 20D has too big pictures to handle more
207                     // than
208                     // two pictures in a navigator applet !!!!!
209                     // :-(
210                     //
211                     // We don't transform it. We clean the file, if it has been
212                     // created.
213                     // More debug output, to understand where the applet
214                     // freezes.
215                     uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): OutOfMemoryError", 30);
216                     deleteTransformedPictureFile();
217                     deleteWorkingCopyPictureFile();
218 
219                     // Let's try to free some memory.
220                     freeMemory("beforeUpload(): in OutOfMemoryError", uploadPolicy);
221 
222                     tooBigPicture();
223                 }
224 
225                 // If the transformed picture is correctly created, we'll upload
226                 // it.
227                 // Else we upload the original file.
228                 synchronized (this) {
229                     if (this.transformedPictureFile != null) {
230                         this.uploadLength = this.transformedPictureFile.length();
231                         setMimeTypeByExtension(this.targetPictureFormat);
232                     } else {
233                         this.uploadLength = getFile().length();
234                     }
235                 }
236             }
237         }
238 
239         // Let's check that everything is Ok
240         // More debug output, to understand where the applet freezes.
241         uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): before call to super.beforeUpload()",
242                 100);
243 
244         super.beforeUpload(uploadFileRoot);
245 
246         // Let's check that everything is Ok
247         // More debug output, to understand where the applet freezes.
248         uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): after call to super.beforeUpload()",
249                 100);
250     }
251 
252     /**
253      * Returns the number of bytes, for this upload. If needed, that is, if uploadlength is unknown,
254      * {@link #beforeUpload()} is called.
255      * 
256      * @return The length of upload. In this class, this is ... the size of the original file, or the transformed file!
257      */
258     @Override
259     public long getUploadLength() {
260         // Just for debug, to be removed before release.
261 
262         if (!this.preparedForUpload) {
263             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
264         }
265         return this.uploadLength;
266     }
267 
268     /**
269      * This function create an input stream for this file. The caller is responsible for closing this input stream. <BR>
270      * This function assumes that the {@link #getUploadLength()} method has already be called : it is responsible for
271      * creating the temporary file (if needed). If not called, the original file will be sent.
272      * 
273      * @return An inputStream
274      */
275     @Override
276     public synchronized InputStream getInputStream() throws JUploadException {
277         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.getInputStream()", 95);
278         if (!this.preparedForUpload) {
279             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
280         }
281         // Do we have to transform the picture ?
282         if (this.transformedPictureFile != null) {
283             try {
284                 return new FileInputStream(this.transformedPictureFile);
285             } catch (FileNotFoundException e) {
286                 throw new JUploadIOException(e);
287             }
288         }
289         // Otherwise : we read the file, in the standard way.
290         return super.getInputStream();
291     }
292 
293     /**
294      * Cleaning of the temporary file on the hard drive, if any. <BR>
295      * <B>Note:</B> if the debugLevel is 100 (or more) this temporary file is not removed. This allow control of this
296      * created file.
297      */
298     @Override
299     public void afterUpload() {
300         // Free the temporary file ... if any.
301         deleteTransformedPictureFile();
302         deleteWorkingCopyPictureFile();
303         this.uploadLength = -1;
304 
305         super.afterUpload();
306     }
307 
308     /**
309      * This method creates a new Image, from the current picture. The resulting width and height will be less or equal
310      * than the given maximum width and height. The scale is maintained. Thus the width or height may be inferior than
311      * the given values.
312      * 
313      * @param canvas The canvas on which the picture will be displayed.
314      * @param shadow True if the pictureFileData should store this picture. False if the pictureFileData instance should
315      *            not store this picture. Store this picture avoid calculating the image each time the user selects it
316      *            in the file panel.
317      * @return The rescaled image.
318      * @throws JUploadException Encapsulation of the Exception, if any would occurs.
319      */
320     public Image getImage(Canvas canvas, boolean shadow) throws JUploadException {
321         Image localImage = null;
322 
323         // ////////////////////////////////////////////////////////////////////////
324         // ////////////// Some preliminary tests.
325         // ////////////////////////////////////////////////////////////////////////
326 
327         if (canvas == null) {
328             throw new JUploadException("canvas null in PictureFileData.getImage");
329         }
330 
331         if (shadow && this.offscreenImage != null) {
332             return this.offscreenImage;
333         }
334 
335         int canvasWidth = canvas.getWidth();
336         int canvasHeight = canvas.getHeight();
337         if (canvasWidth <= 0 || canvasHeight <= 0) {
338             uploadPolicy.displayDebug("canvas width and/or height null in PictureFileData.getImage()", 1);
339             return null;
340         }
341 
342         if (!this.isPicture) {
343             uploadPolicy
344                     .displayWarn("canvas width and/or height null in PictureFileData.getImage(). PictureFileData.getImage will return null");
345             return null;
346         }
347         // ////////////////////////////////////////////////////////////////////////
348         // ////////////// End of preliminary tests: let's work.
349         // ////////////////////////////////////////////////////////////////////////
350 
351         try {
352             // First: load the picture.
353             ImageReaderWriterHelper irwh = new ImageReaderWriterHelper((PictureUploadPolicy) uploadPolicy, this);
354             BufferedImage sourceImage = irwh.readImage(0);
355             irwh.dispose();
356             irwh = null;
357             ImageHelper ih = new ImageHelper((PictureUploadPolicy) uploadPolicy, this, canvasWidth, canvasHeight,
358                     this.quarterRotation);
359             localImage = ih.getBufferedImage(((PictureUploadPolicy) uploadPolicy).getHighQualityPreview(), sourceImage);
360             // We free memory ASAP.
361             sourceImage.flush();
362             sourceImage = null;
363         } catch (OutOfMemoryError e) {
364             // Too bad
365             localImage = null;
366             tooBigPicture();
367         }
368 
369         // We store it, if asked to.
370         if (shadow) {
371             this.offscreenImage = localImage;
372         }
373 
374         freeMemory("end of " + this.getClass().getName() + ".getImage()", uploadPolicy);
375 
376         // The picture is now loaded. We clear the progressBar
377         uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar().setValue(0);
378 
379         return localImage;
380     }// getImage
381 
382     /**
383      * This function is used to rotate the picture. The current rotation state is kept in the quarterRotation private
384      * attribute.
385      * 
386      * @param quarter Number of quarters (90 degrees) the picture should rotate. 1 means rotating of 90 degrees
387      *            clockwise. Can be negative.
388      */
389     public void addRotation(int quarter) {
390         this.quarterRotation += quarter;
391 
392         // We'll have to recalculate the upload length, as the resulting file is
393         // different.
394         // If any file has been prepared, they must be deleted
395         deleteWorkingCopyPictureFile();
396         deleteTransformedPictureFile();
397         this.uploadLength = -1;
398 
399         // We keep the 'quarter' in the segment [0;4[
400         while (this.quarterRotation < 0) {
401             this.quarterRotation += 4;
402         }
403         while (this.quarterRotation >= 4) {
404             this.quarterRotation -= 4;
405         }
406 
407         // We need to change the precalculated picture, if any
408         if (this.offscreenImage != null) {
409             this.offscreenImage.flush();
410             this.offscreenImage = null;
411         }
412     }
413 
414     /**
415      * Indicates if this file is actually a picture or not.
416      * 
417      * @return the isPicture flag.
418      */
419     public boolean isPicture() {
420         return this.isPicture;
421     }
422 
423     /** @see FileData#getMimeType() */
424     @Override
425     public String getMimeType() {
426         return this.mimeType;
427     }
428 
429     // ///////////////////////////////////////////////////////////////////////////////////////////
430     // /////////////////////////// private METHODS
431     // ///////////////////////////////////////////////////////////////////////////////////////////
432 
433     /**
434      * File.deleteOnExit() is pretty unreliable, especially in applets. Therefore the applet provides a callback which
435      * is executed during applet termination. This method performs the actual cleanup.
436      */
437     public synchronized void deleteTransformedPictureFile() {
438         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.deleteTransformedPictureFile()", 95);
439         // Free the temporary file ... if any.
440         if (null != this.transformedPictureFile && uploadPolicy.getDebugLevel() <= 100) {
441             if (!this.transformedPictureFile.delete()) {
442                 uploadPolicy.displayWarn("Unable to delete " + this.transformedPictureFile.getName());
443             }
444             this.transformedPictureFile = null;
445 
446         }
447     }
448 
449     /**
450      * Creation of a temporary file, that contains the transformed picture. For instance, it can be resized or rotated.
451      * This method doesn't throw exception when there is an IOException within its procedure. If an exception occurs
452      * while building the temporary file, the exception is caught, a warning is displayed, the temporary file is deleted
453      * (if it was created), and the upload will go on with the original file. <BR>
454      * Note: any JUploadException thrown by a method called within getTransformedPictureFile() will be thrown within
455      * this method.
456      */
457     void initTransformedPictureFile() throws JUploadException {
458         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.initTransformedPictureFile()", 95);
459         int targetMaxWidth;
460         int targetMaxHeight;
461 
462         // If the image is rotated, we compare to realMaxWidth and
463         // realMaxHeight, instead of maxWidth and maxHeight. This allows
464         // to have a different picture size for rotated and not rotated
465         // pictures. See the UploadPolicy javadoc for details ... and a
466         // good reason ! ;-)
467         if (this.quarterRotation == 0) {
468             targetMaxWidth = ((PictureUploadPolicy) uploadPolicy).getMaxWidth();
469             targetMaxHeight = ((PictureUploadPolicy) uploadPolicy).getMaxHeight();
470         } else {
471             targetMaxWidth = ((PictureUploadPolicy) uploadPolicy).getRealMaxWidth();
472             targetMaxHeight = ((PictureUploadPolicy) uploadPolicy).getRealMaxHeight();
473         }
474 
475         // Some Helper will .. help us !
476         // I like useful comment :-)
477         ImageHelper imageHelper = new ImageHelper((PictureUploadPolicy) uploadPolicy, this, targetMaxWidth,
478                 targetMaxHeight, this.quarterRotation);
479 
480         // Should transform the file, and do we already created the transformed
481         // file ?
482         synchronized (this) {
483             if (imageHelper.hasToTransformPicture() && this.transformedPictureFile == null) {
484 
485                 // We have to create a resized or rotated picture file, and all
486                 // needed information.
487                 // ...let's do it
488                 try {
489                     createTranformedPictureFile(imageHelper);
490                 } catch (JUploadException e) {
491                     // Hum, too bad.
492                     // if any file was created, we remove it.
493                     deleteTransformedPictureFile();
494                     throw e;
495                 }
496             }
497         }
498     }// end of initTransformedPictureFile
499 
500     /**
501      * Creates a transformed picture file of the given max width and max height. If the {@link #transformedPictureFile}
502      * attribute is not set before calling this method, it will be set. If set before, the existing
503      * {@link #transformedPictureFile} is replaced by the newly transformed picture file. It is cleared if an error
504      * occured. <BR>
505      * 
506      * @param imageHelper The {@link ImageHelper} that was initialized with current parameters.
507      */
508     synchronized void createTranformedPictureFile(ImageHelper imageHelper) throws JUploadException {
509         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createTransformedPictureFile()", 95);
510 
511         IIOMetadata metadata = null;
512         IIOImage iioImage = null;
513         BufferedImage originalImage = null;
514         BufferedImage transformedImage = null;
515         ImageReaderWriterHelper imageWriterHelper = new ImageReaderWriterHelper((PictureUploadPolicy) uploadPolicy,
516                 this);
517         boolean transmitMetadata = ((PictureUploadPolicy) uploadPolicy).getPictureTransmitMetadata();
518 
519         // Creation of the transformed picture file.
520         createTransformedTempFile();
521         this.targetPictureFormat = imageWriterHelper.getTargetPictureFormat();
522         imageWriterHelper.setOutput(this.transformedPictureFile);
523 
524         // How many picture should we read from the input file.
525         // Default number of pictures is one.
526         int nbPictures = 1;
527         // For gif file, we put a max to MAX_VALUE, and we check the
528         // IndexOutOfBoundsException to identify when we've read all pictures
529         if (getExtension(getFileName()).equalsIgnoreCase("gif")) {
530             nbPictures = Integer.MAX_VALUE;
531         }
532         uploadPolicy.displayDebug("Reading image with imageWriterHelper.readImage(i)", 50);
533         // Now, we have to read each picture from the original file, apply
534         // the calculated transformation, and write each transformed picture
535         // to the writer.
536         // As indicated in javadoc for ImageReader.getNumImages(), we go
537         // through pictures, until we get an IndexOutOfBoundsException.
538         try {
539             for (int i = 0; i < nbPictures; i += 1) {
540                 originalImage = imageWriterHelper.readImage(i);
541                 transformedImage = imageHelper.getBufferedImage(true, originalImage);
542 
543                 // If necessary, we load the metadata for the current
544                 // picture
545                 if (transmitMetadata) {
546                     metadata = imageWriterHelper.getImageMetadata(i);
547                 }
548 
549                 iioImage = new IIOImage(transformedImage, null, metadata);
550                 imageWriterHelper.write(iioImage);
551 
552                 // Let's clear picture, to force getBufferedImage to read a new
553                 // one,
554                 // in the next loop.
555                 if (originalImage != null) {
556                     originalImage.flush();
557                     originalImage = null;
558                 }
559             }// for
560         } catch (IndexOutOfBoundsException e) {
561             // Was sent by imageWriterHelper.readImage(i)
562             // Ok, no more picture to read. We just want to go out of
563             // the loop. No error.
564             uploadPolicy
565                     .displayDebug("IndexOutOfBoundsException catched: end of reading for file " + getFileName(), 10);
566         }
567 
568         if (originalImage != null) {
569             originalImage.flush();
570             originalImage = null;
571         }
572 
573         // Let's free any used resource.
574         imageWriterHelper.dispose();
575 
576     }
577 
578     /**
579      * This method is called when an OutOfMemoryError occurs. This can easily happen within the navigator, with big
580      * pictures: I've put a lot of freeMemory calls within the code, but they don't seem to work very well. When running
581      * from eclipse, the memory is freed Ok !
582      */
583     void tooBigPicture() {
584         String msg = uploadPolicy.getLocalizedString("tooBigPicture", getFileName());
585         uploadPolicy.displayWarn(msg);
586         JOptionPane.showMessageDialog(null, msg, "Warning", JOptionPane.WARNING_MESSAGE);
587     }
588 
589     /**
590      * This methods set the {@link DefaultFileData#mimeType} to the image mime type, that should be associate with the
591      * picture.
592      */
593     void setMimeTypeByExtension(String fileExtension) {
594         String ext = fileExtension.toLowerCase();
595         if (ext.equals("jpg")) {
596             ext = "jpeg";
597         }
598         this.mimeType = "image/" + ext;
599     }
600 
601     /**
602      * If {@link #transformedPictureFile} is null, create a new temporary file, and assign it to
603      * {@link #transformedPictureFile}. Otherwise, no action.
604      * 
605      * @throws IOException
606      */
607     synchronized void createTransformedTempFile() throws JUploadIOException {
608         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createTransformedTempFile()", 95);
609 
610         if (this.transformedPictureFile == null) {
611             try {
612                 this.transformedPictureFile = File.createTempFile("jupload_", ".tmp");
613             } catch (IOException e) {
614                 throw new JUploadIOException("PictureFileData.createTransformedTempFile()", e);
615             }
616             uploadPolicy.getContext().registerUnload(this, "deleteTransformedPictureFile");
617             uploadPolicy.displayDebug("Using transformed temp file " + this.transformedPictureFile.getAbsolutePath()
618                     + " for " + getFileName(), 30);
619         }
620     }
621 
622     /**
623      * This method loads the picture width and height of the picture. It's called by the current instance when
624      * necessary.
625      * 
626      * @throws JUploadIOException
627      * @see #getOriginalHeight()
628      * @see #getOriginalWidth()
629      */
630     void initWidthAndHeight() throws JUploadIOException {
631         // Is it a picture?
632         if (this.isPicture && (this.originalHeight < 0 || this.originalWidth < 0)) {
633             // Ok: it's a picture and is original width and height have not been
634             // loaded yet.
635             // In the windows world, file extension may be in upper case, which
636             // is not compatible with the core Java API.
637             Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(getFileExtension().toLowerCase());
638             if (iter.hasNext()) {
639                 // It's a picture: we store its original width and height, for
640                 // further calculation (rescaling and rotation).
641                 try {
642                     FileImageInputStream fiis = new FileImageInputStream(getFile());
643                     ImageReader ir = iter.next();
644                     ir.setInput(fiis);
645                     this.originalHeight = ir.getHeight(0);
646                     this.originalWidth = ir.getWidth(0);
647                     ir.dispose();
648                     fiis.close();
649                 } catch (IOException e) {
650                     throw new JUploadIOException("PictureFileData()", e);
651                 }
652             }
653         }
654     }
655 
656     /**
657      * If {@link #workingCopyTempFile} is null, create a new temporary file, and assign it to
658      * {@link #transformedPictureFile}. Otherwise, no action.
659      * 
660      * @throws IOException
661      */
662     synchronized void createWorkingCopyTempFile() throws IOException {
663         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createWorkingCopyTempFile()", 95);
664         if (this.workingCopyTempFile == null) {
665             // The temporary file must have the correct extension, so that
666             // native Java method works on it.
667             this.workingCopyTempFile = File.createTempFile("jupload_",
668                     ".tmp." + DefaultFileData.getExtension(getFileName()));
669             uploadPolicy.getContext().registerUnload(this, "deleteWorkingCopyPictureFile");
670             uploadPolicy.displayDebug("Using working copy temp file " + this.workingCopyTempFile.getAbsolutePath()
671                     + " for " + getFileName(), 30);
672         }
673     }
674 
675     /**
676      * File.deleteOnExit() is pretty unreliable, especially in applets. Therefore the applet provides a callback which
677      * is executed during applet termination. This method performs the actual cleanup.
678      */
679     public synchronized void deleteWorkingCopyPictureFile() {
680         // for debug : if the debugLevel is enough, we keep the temporary
681         // file (for check).
682         if (null != this.workingCopyTempFile && uploadPolicy.getDebugLevel() <= 100) {
683             if (!this.workingCopyTempFile.delete()) {
684                 uploadPolicy.displayWarn("Unable to delete " + this.workingCopyTempFile.getName());
685             }
686             this.workingCopyTempFile = null;
687 
688         }
689     }
690 
691     /**
692      * Get the file that contains the original picture. This is used as a workaround for the following JVM bug: once in
693      * the navigator, it can't transform picture read from a file whose name contains non-ASCII characters, like French
694      * accents.
695      * 
696      * @return The file that contains the original picture, as the source for picture transformation
697      * @throws JUploadIOException
698      */
699     public synchronized File getWorkingSourceFile() throws JUploadIOException {
700 
701         if (this.workingCopyTempFile == null) {
702             uploadPolicy.displayDebug("[getWorkingSourceFile] Creating a copy of " + getFileName()
703                     + " as a source working target.", 30);
704             FileInputStream is = null;
705             FileOutputStream os = null;
706             try {
707                 createWorkingCopyTempFile();
708 
709                 is = new FileInputStream(getFile());
710                 os = new FileOutputStream(this.workingCopyTempFile);
711                 byte b[] = new byte[1024];
712                 int l;
713                 while ((l = is.read(b)) > 0) {
714                     os.write(b, 0, l);
715                 }
716             } catch (IOException e) {
717                 throw new JUploadIOException("ImageReaderWriterHelper.getWorkingSourceFile()", e);
718             } finally {
719                 if (is != null) {
720                     try {
721                         is.close();
722                     } catch (IOException e) {
723                         uploadPolicy
724                                 .displayWarn(e.getClass().getName()
725                                         + " while trying to close FileInputStream, in PictureUploadPolicy.copyOriginalToWorkingCopyTempFile.");
726                     } finally {
727                         is = null;
728                     }
729                 }
730                 if (os != null) {
731                     try {
732                         os.close();
733                     } catch (IOException e) {
734                         uploadPolicy
735                                 .displayWarn(e.getClass().getName()
736                                         + " while trying to close FileOutputStream, in PictureUploadPolicy.copyOriginalToWorkingCopyTempFile.");
737                     } finally {
738                         os = null;
739                     }
740                 }
741             }
742         }
743         return this.workingCopyTempFile;
744     }// getWorkingSourceFile()
745 
746     /**
747      * @return the originalWidth of the picture
748      * @throws JUploadIOException
749      */
750     public int getOriginalWidth() throws JUploadIOException {
751         initWidthAndHeight();
752         return this.originalWidth;
753     }
754 
755     /**
756      * @return the originalHeight of the picture
757      * @throws JUploadIOException
758      */
759     public int getOriginalHeight() throws JUploadIOException {
760         initWidthAndHeight();
761         return this.originalHeight;
762     }
763 
764     // ////////////////////////////////////////////////////////////////////////////////////////////////////
765     // /////////////////////// static methods
766     // ////////////////////////////////////////////////////////////////////////////////////////////////////
767 
768     /**
769      * Returns an ImageIcon for the given file, resized according to the given dimensions. If the original file contains
770      * a pictures smaller than these width and height, the picture is returned as is (nor resized).
771      * 
772      * @param pictureFile The file, containing a picture, from which the user wants to extract a static picture.
773      * @param maxWidth The maximum allowed width for the static picture to generate.
774      * @param maxHeight The maximum allowed height for the static picture to generate.
775      * @param uploadPolicy The current upload policy, for logging when calling freeMemory.
776      * @return The created static picture, or null if the file is null.
777      * @throws JUploadException If the ImageIcon can not be loaded.
778      */
779     public static ImageIcon getImageIcon(File pictureFile, int maxWidth, int maxHeight, UploadPolicy uploadPolicy)
780             throws JUploadException {
781         ImageIcon imageIcon = null;
782 
783         if (pictureFile != null) {
784             BufferedImage tmpImage = null;
785             try {
786                 // We need a reader for this picture.
787                 if (pictureFile.canRead() && pictureFile.isFile()) {
788                     // It seems to be a valid file. Can we get its icon?
789                     if (ImageIO.getImageReaders(new FileImageInputStream(pictureFile)).hasNext()) {
790                         tmpImage = ImageIO.read(pictureFile);
791                     }
792                 }
793             } catch (IOException e) {
794                 throw new JUploadIOException("An error occured while loading the image for "
795                         + pictureFile.getAbsolutePath(), e);
796             }
797             if (tmpImage != null) {
798                 // Let's calculate the asked icon.
799                 double scaleWidth = ((double) maxWidth) / tmpImage.getWidth();
800                 double scaleHeight = ((double) maxHeight) / tmpImage.getHeight();
801                 double scale = Math.min(scaleWidth, scaleHeight);
802 
803                 if (scale < 1) {
804                     int width = (int) (scale * tmpImage.getWidth());
805                     int height = (int) (scale * tmpImage.getHeight());
806                     // We need to miniaturize the current Icon
807                     Image rescaledImage = tmpImage.getScaledInstance(width, height, Image.SCALE_FAST);
808                     BufferedImage tempBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
809                     tempBufferedImage.getGraphics().drawImage(rescaledImage, 0, 0, null);
810                     imageIcon = new ImageIcon(tempBufferedImage);
811                     uploadPolicy.displayDebug("Icon generated (witdh=" + imageIcon.getIconWidth() + ", imageheight="
812                             + imageIcon.getIconHeight() + ")", 80);
813                 }
814                 tmpImage.flush();
815                 tmpImage = null;
816 
817                 freeMemory("PictureFileData.getImageIcon()", uploadPolicy);
818             }
819         }
820         return imageIcon;
821     }
822 
823     /**
824      * Indicates whether a file is a picture or not. The information is based on the fact the an ImageRead is found, or
825      * not, for this file. This test uses the core Java API. As in the windows world, file extension may be in
826      * uppercase, the test is based on the lowercase value for the given file extension.
827      * 
828      * @param file
829      * @return true if the file can be opened as a picture, false otherwise.
830      */
831     public static boolean isFileAPicture(File file) {
832         // In the windows world, file extension may be in uppercase, which is
833         // not compatible with the core Java API.
834         Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(DefaultFileData.getExtension(file.getName())
835                 .toLowerCase());
836         return iter.hasNext();
837     }
838 }