Coverage Report - wjhk.jupload2.filedata.PictureFileData
 
Classes in this File Line Coverage Branch Coverage Complexity
PictureFileData
0 %
0/273
0 %
0/98
4,042
 
 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  0
     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  0
     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  0
     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  0
     int originalWidth = -1;
 97  
 
 98  
     /**
 99  
      * Same as {@link #originalWidth}, for the height of the first image in the picture file.
 100  
      */
 101  0
     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  0
     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  0
     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  0
     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  0
         super(file, uploadPolicy);
 142  
 
 143  0
         String fileExtension = getFileExtension();
 144  
 
 145  
         // Is it a picture?
 146  0
         uploadPolicy.displayDebug("Looking for iterator of extension '" + file + "'", 80);
 147  0
         this.isPicture = isFileAPicture(file);
 148  
 
 149  
         // Let's log the test result
 150  0
         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  0
         if (this.isPicture) {
 155  0
             setMimeTypeByExtension(fileExtension);
 156  
         }
 157  0
     }
 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  0
         Runtime rt = Runtime.getRuntime();
 168  
 
 169  0
         rt.runFinalization();
 170  0
         rt.gc();
 171  
 
 172  0
         if (uploadPolicy.getDebugLevel() >= 50) {
 173  0
             uploadPolicy.displayDebug(
 174  0
                     "freeMemory (after " + caller + ") : " + rt.freeMemory() + " (maxMemory: " + rt.maxMemory()
 175  0
                             + ", totalMemory: " + rt.totalMemory() + ")", 50);
 176  
         }
 177  0
     }
 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  0
         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.beforeUpload()", 95);
 189  
 
 190  0
         if (!this.preparedForUpload) {
 191  0
             if (this.uploadLength < 0) {
 192  
                 try {
 193  
                     // Picture management is a big work. Let's try to free some
 194  
                     // memory.
 195  0
                     freeMemory("Picture manabeforeUpload(): before initTransformedPictureFile", uploadPolicy);
 196  
 
 197  
                     // Get the transformed picture file, if needed.
 198  0
                     initTransformedPictureFile();
 199  
 
 200  
                     // More debug output, to understand where the applet
 201  
                     // freezes.
 202  0
                     uploadPolicy.displayDebug(this.getClass().getName()
 203  
                             + ".beforeUpload(): after call to initTransformedPictureFile()", 100);
 204  
 
 205  0
                 } 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  0
                     uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): OutOfMemoryError", 30);
 216  0
                     deleteTransformedPictureFile();
 217  0
                     deleteWorkingCopyPictureFile();
 218  
 
 219  
                     // Let's try to free some memory.
 220  0
                     freeMemory("beforeUpload(): in OutOfMemoryError", uploadPolicy);
 221  
 
 222  0
                     tooBigPicture();
 223  0
                 }
 224  
 
 225  
                 // If the transformed picture is correctly created, we'll upload
 226  
                 // it.
 227  
                 // Else we upload the original file.
 228  0
                 synchronized (this) {
 229  0
                     if (this.transformedPictureFile != null) {
 230  0
                         this.uploadLength = this.transformedPictureFile.length();
 231  0
                         setMimeTypeByExtension(this.targetPictureFormat);
 232  
                     } else {
 233  0
                         this.uploadLength = getFile().length();
 234  
                     }
 235  0
                 }
 236  
             }
 237  
         }
 238  
 
 239  
         // Let's check that everything is Ok
 240  
         // More debug output, to understand where the applet freezes.
 241  0
         uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): before call to super.beforeUpload()",
 242  
                 100);
 243  
 
 244  0
         super.beforeUpload(uploadFileRoot);
 245  
 
 246  
         // Let's check that everything is Ok
 247  
         // More debug output, to understand where the applet freezes.
 248  0
         uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): after call to super.beforeUpload()",
 249  
                 100);
 250  0
     }
 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  0
         if (!this.preparedForUpload) {
 263  0
             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
 264  
         }
 265  0
         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  0
         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.getInputStream()", 95);
 278  0
         if (!this.preparedForUpload) {
 279  0
             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
 280  
         }
 281  
         // Do we have to transform the picture ?
 282  0
         if (this.transformedPictureFile != null) {
 283  
             try {
 284  0
                 return new FileInputStream(this.transformedPictureFile);
 285  0
             } catch (FileNotFoundException e) {
 286  0
                 throw new JUploadIOException(e);
 287  
             }
 288  
         }
 289  
         // Otherwise : we read the file, in the standard way.
 290  0
         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  0
         deleteTransformedPictureFile();
 302  0
         deleteWorkingCopyPictureFile();
 303  0
         this.uploadLength = -1;
 304  
 
 305  0
         super.afterUpload();
 306  0
     }
 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  0
         Image localImage = null;
 322  
 
 323  
         // ////////////////////////////////////////////////////////////////////////
 324  
         // ////////////// Some preliminary tests.
 325  
         // ////////////////////////////////////////////////////////////////////////
 326  
 
 327  0
         if (canvas == null) {
 328  0
             throw new JUploadException("canvas null in PictureFileData.getImage");
 329  
         }
 330  
 
 331  0
         if (shadow && this.offscreenImage != null) {
 332  0
             return this.offscreenImage;
 333  
         }
 334  
 
 335  0
         int canvasWidth = canvas.getWidth();
 336  0
         int canvasHeight = canvas.getHeight();
 337  0
         if (canvasWidth <= 0 || canvasHeight <= 0) {
 338  0
             uploadPolicy.displayDebug("canvas width and/or height null in PictureFileData.getImage()", 1);
 339  0
             return null;
 340  
         }
 341  
 
 342  0
         if (!this.isPicture) {
 343  0
             uploadPolicy
 344  0
                     .displayWarn("canvas width and/or height null in PictureFileData.getImage(). PictureFileData.getImage will return null");
 345  0
             return null;
 346  
         }
 347  
         // ////////////////////////////////////////////////////////////////////////
 348  
         // ////////////// End of preliminary tests: let's work.
 349  
         // ////////////////////////////////////////////////////////////////////////
 350  
 
 351  
         try {
 352  
             // First: load the picture.
 353  0
             ImageReaderWriterHelper irwh = new ImageReaderWriterHelper((PictureUploadPolicy) uploadPolicy, this);
 354  0
             BufferedImage sourceImage = irwh.readImage(0);
 355  0
             irwh.dispose();
 356  0
             irwh = null;
 357  0
             ImageHelper ih = new ImageHelper((PictureUploadPolicy) uploadPolicy, this, canvasWidth, canvasHeight,
 358  
                     this.quarterRotation);
 359  0
             localImage = ih.getBufferedImage(((PictureUploadPolicy) uploadPolicy).getHighQualityPreview(), sourceImage);
 360  
             // We free memory ASAP.
 361  0
             sourceImage.flush();
 362  0
             sourceImage = null;
 363  0
         } catch (OutOfMemoryError e) {
 364  
             // Too bad
 365  0
             localImage = null;
 366  0
             tooBigPicture();
 367  0
         }
 368  
 
 369  
         // We store it, if asked to.
 370  0
         if (shadow) {
 371  0
             this.offscreenImage = localImage;
 372  
         }
 373  
 
 374  0
         freeMemory("end of " + this.getClass().getName() + ".getImage()", uploadPolicy);
 375  
 
 376  
         // The picture is now loaded. We clear the progressBar
 377  0
         uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar().setValue(0);
 378  
 
 379  0
         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  0
         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  0
         deleteWorkingCopyPictureFile();
 396  0
         deleteTransformedPictureFile();
 397  0
         this.uploadLength = -1;
 398  
 
 399  
         // We keep the 'quarter' in the segment [0;4[
 400  0
         while (this.quarterRotation < 0) {
 401  0
             this.quarterRotation += 4;
 402  
         }
 403  0
         while (this.quarterRotation >= 4) {
 404  0
             this.quarterRotation -= 4;
 405  
         }
 406  
 
 407  
         // We need to change the precalculated picture, if any
 408  0
         if (this.offscreenImage != null) {
 409  0
             this.offscreenImage.flush();
 410  0
             this.offscreenImage = null;
 411  
         }
 412  0
     }
 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  0
         return this.isPicture;
 421  
     }
 422  
 
 423  
     /** @see FileData#getMimeType() */
 424  
     @Override
 425  
     public String getMimeType() {
 426  0
         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  0
         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.deleteTransformedPictureFile()", 95);
 439  
         // Free the temporary file ... if any.
 440  0
         if (null != this.transformedPictureFile && uploadPolicy.getDebugLevel() <= 100) {
 441  0
             if (!this.transformedPictureFile.delete()) {
 442  0
                 uploadPolicy.displayWarn("Unable to delete " + this.transformedPictureFile.getName());
 443  
             }
 444  0
             this.transformedPictureFile = null;
 445  
 
 446  
         }
 447  0
     }
 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  0
         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  0
         if (this.quarterRotation == 0) {
 468  0
             targetMaxWidth = ((PictureUploadPolicy) uploadPolicy).getMaxWidth();
 469  0
             targetMaxHeight = ((PictureUploadPolicy) uploadPolicy).getMaxHeight();
 470  
         } else {
 471  0
             targetMaxWidth = ((PictureUploadPolicy) uploadPolicy).getRealMaxWidth();
 472  0
             targetMaxHeight = ((PictureUploadPolicy) uploadPolicy).getRealMaxHeight();
 473  
         }
 474  
 
 475  
         // Some Helper will .. help us !
 476  
         // I like useful comment :-)
 477  0
         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  0
         synchronized (this) {
 483  0
             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  0
                     createTranformedPictureFile(imageHelper);
 490  0
                 } catch (JUploadException e) {
 491  
                     // Hum, too bad.
 492  
                     // if any file was created, we remove it.
 493  0
                     deleteTransformedPictureFile();
 494  0
                     throw e;
 495  0
                 }
 496  
             }
 497  0
         }
 498  0
     }// 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  0
         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createTransformedPictureFile()", 95);
 510  
 
 511  0
         IIOMetadata metadata = null;
 512  0
         IIOImage iioImage = null;
 513  0
         BufferedImage originalImage = null;
 514  0
         BufferedImage transformedImage = null;
 515  0
         ImageReaderWriterHelper imageWriterHelper = new ImageReaderWriterHelper((PictureUploadPolicy) uploadPolicy,
 516  
                 this);
 517  0
         boolean transmitMetadata = ((PictureUploadPolicy) uploadPolicy).getPictureTransmitMetadata();
 518  
 
 519  
         // Creation of the transformed picture file.
 520  0
         createTransformedTempFile();
 521  0
         this.targetPictureFormat = imageWriterHelper.getTargetPictureFormat();
 522  0
         imageWriterHelper.setOutput(this.transformedPictureFile);
 523  
 
 524  
         // How many picture should we read from the input file.
 525  
         // Default number of pictures is one.
 526  0
         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  0
         if (getExtension(getFileName()).equalsIgnoreCase("gif")) {
 530  0
             nbPictures = Integer.MAX_VALUE;
 531  
         }
 532  0
         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  0
             for (int i = 0; i < nbPictures; i += 1) {
 540  0
                 originalImage = imageWriterHelper.readImage(i);
 541  0
                 transformedImage = imageHelper.getBufferedImage(true, originalImage);
 542  
 
 543  
                 // If necessary, we load the metadata for the current
 544  
                 // picture
 545  0
                 if (transmitMetadata) {
 546  0
                     metadata = imageWriterHelper.getImageMetadata(i);
 547  
                 }
 548  
 
 549  0
                 iioImage = new IIOImage(transformedImage, null, metadata);
 550  0
                 imageWriterHelper.write(iioImage);
 551  
 
 552  
                 // Let's clear picture, to force getBufferedImage to read a new
 553  
                 // one,
 554  
                 // in the next loop.
 555  0
                 if (originalImage != null) {
 556  0
                     originalImage.flush();
 557  0
                     originalImage = null;
 558  
                 }
 559  
             }// for
 560  0
         } 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  0
             uploadPolicy
 565  0
                     .displayDebug("IndexOutOfBoundsException catched: end of reading for file " + getFileName(), 10);
 566  0
         }
 567  
 
 568  0
         if (originalImage != null) {
 569  0
             originalImage.flush();
 570  0
             originalImage = null;
 571  
         }
 572  
 
 573  
         // Let's free any used resource.
 574  0
         imageWriterHelper.dispose();
 575  
 
 576  0
     }
 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  0
         String msg = uploadPolicy.getLocalizedString("tooBigPicture", getFileName());
 585  0
         uploadPolicy.displayWarn(msg);
 586  0
         JOptionPane.showMessageDialog(null, msg, "Warning", JOptionPane.WARNING_MESSAGE);
 587  0
     }
 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  0
         String ext = fileExtension.toLowerCase();
 595  0
         if (ext.equals("jpg")) {
 596  0
             ext = "jpeg";
 597  
         }
 598  0
         this.mimeType = "image/" + ext;
 599  0
     }
 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  0
         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createTransformedTempFile()", 95);
 609  
 
 610  0
         if (this.transformedPictureFile == null) {
 611  
             try {
 612  0
                 this.transformedPictureFile = File.createTempFile("jupload_", ".tmp");
 613  0
             } catch (IOException e) {
 614  0
                 throw new JUploadIOException("PictureFileData.createTransformedTempFile()", e);
 615  0
             }
 616  0
             uploadPolicy.getContext().registerUnload(this, "deleteTransformedPictureFile");
 617  0
             uploadPolicy.displayDebug("Using transformed temp file " + this.transformedPictureFile.getAbsolutePath()
 618  0
                     + " for " + getFileName(), 30);
 619  
         }
 620  0
     }
 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  0
         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  0
             Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(getFileExtension().toLowerCase());
 638  0
             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  0
                     FileImageInputStream fiis = new FileImageInputStream(getFile());
 643  0
                     ImageReader ir = iter.next();
 644  0
                     ir.setInput(fiis);
 645  0
                     this.originalHeight = ir.getHeight(0);
 646  0
                     this.originalWidth = ir.getWidth(0);
 647  0
                     ir.dispose();
 648  0
                     fiis.close();
 649  0
                 } catch (IOException e) {
 650  0
                     throw new JUploadIOException("PictureFileData()", e);
 651  0
                 }
 652  
             }
 653  
         }
 654  0
     }
 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  0
         uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createWorkingCopyTempFile()", 95);
 664  0
         if (this.workingCopyTempFile == null) {
 665  
             // The temporary file must have the correct extension, so that
 666  
             // native Java method works on it.
 667  0
             this.workingCopyTempFile = File.createTempFile("jupload_",
 668  0
                     ".tmp." + DefaultFileData.getExtension(getFileName()));
 669  0
             uploadPolicy.getContext().registerUnload(this, "deleteWorkingCopyPictureFile");
 670  0
             uploadPolicy.displayDebug("Using working copy temp file " + this.workingCopyTempFile.getAbsolutePath()
 671  0
                     + " for " + getFileName(), 30);
 672  
         }
 673  0
     }
 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  0
         if (null != this.workingCopyTempFile && uploadPolicy.getDebugLevel() <= 100) {
 683  0
             if (!this.workingCopyTempFile.delete()) {
 684  0
                 uploadPolicy.displayWarn("Unable to delete " + this.workingCopyTempFile.getName());
 685  
             }
 686  0
             this.workingCopyTempFile = null;
 687  
 
 688  
         }
 689  0
     }
 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  0
         if (this.workingCopyTempFile == null) {
 702  0
             uploadPolicy.displayDebug("[getWorkingSourceFile] Creating a copy of " + getFileName()
 703  
                     + " as a source working target.", 30);
 704  0
             FileInputStream is = null;
 705  0
             FileOutputStream os = null;
 706  
             try {
 707  0
                 createWorkingCopyTempFile();
 708  
 
 709  0
                 is = new FileInputStream(getFile());
 710  0
                 os = new FileOutputStream(this.workingCopyTempFile);
 711  0
                 byte b[] = new byte[1024];
 712  
                 int l;
 713  0
                 while ((l = is.read(b)) > 0) {
 714  0
                     os.write(b, 0, l);
 715  
                 }
 716  0
             } catch (IOException e) {
 717  0
                 throw new JUploadIOException("ImageReaderWriterHelper.getWorkingSourceFile()", e);
 718  
             } finally {
 719  0
                 if (is != null) {
 720  
                     try {
 721  0
                         is.close();
 722  0
                     } catch (IOException e) {
 723  0
                         uploadPolicy
 724  0
                                 .displayWarn(e.getClass().getName()
 725  
                                         + " while trying to close FileInputStream, in PictureUploadPolicy.copyOriginalToWorkingCopyTempFile.");
 726  0
                     } finally {
 727  0
                         is = null;
 728  0
                     }
 729  
                 }
 730  0
                 if (os != null) {
 731  
                     try {
 732  0
                         os.close();
 733  0
                     } catch (IOException e) {
 734  0
                         uploadPolicy
 735  0
                                 .displayWarn(e.getClass().getName()
 736  
                                         + " while trying to close FileOutputStream, in PictureUploadPolicy.copyOriginalToWorkingCopyTempFile.");
 737  0
                     } finally {
 738  0
                         os = null;
 739  0
                     }
 740  
                 }
 741  0
             }
 742  
         }
 743  0
         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  0
         initWidthAndHeight();
 752  0
         return this.originalWidth;
 753  
     }
 754  
 
 755  
     /**
 756  
      * @return the originalHeight of the picture
 757  
      * @throws JUploadIOException
 758  
      */
 759  
     public int getOriginalHeight() throws JUploadIOException {
 760  0
         initWidthAndHeight();
 761  0
         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  0
         ImageIcon imageIcon = null;
 782  
 
 783  0
         if (pictureFile != null) {
 784  0
             BufferedImage tmpImage = null;
 785  
             try {
 786  
                 // We need a reader for this picture.
 787  0
                 if (pictureFile.canRead() && pictureFile.isFile()) {
 788  
                     // It seems to be a valid file. Can we get its icon?
 789  0
                     if (ImageIO.getImageReaders(new FileImageInputStream(pictureFile)).hasNext()) {
 790  0
                         tmpImage = ImageIO.read(pictureFile);
 791  
                     }
 792  
                 }
 793  0
             } catch (IOException e) {
 794  0
                 throw new JUploadIOException("An error occured while loading the image for "
 795  0
                         + pictureFile.getAbsolutePath(), e);
 796  0
             }
 797  0
             if (tmpImage != null) {
 798  
                 // Let's calculate the asked icon.
 799  0
                 double scaleWidth = ((double) maxWidth) / tmpImage.getWidth();
 800  0
                 double scaleHeight = ((double) maxHeight) / tmpImage.getHeight();
 801  0
                 double scale = Math.min(scaleWidth, scaleHeight);
 802  
 
 803  0
                 if (scale < 1) {
 804  0
                     int width = (int) (scale * tmpImage.getWidth());
 805  0
                     int height = (int) (scale * tmpImage.getHeight());
 806  
                     // We need to miniaturize the current Icon
 807  0
                     Image rescaledImage = tmpImage.getScaledInstance(width, height, Image.SCALE_FAST);
 808  0
                     BufferedImage tempBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
 809  0
                     tempBufferedImage.getGraphics().drawImage(rescaledImage, 0, 0, null);
 810  0
                     imageIcon = new ImageIcon(tempBufferedImage);
 811  0
                     uploadPolicy.displayDebug("Icon generated (witdh=" + imageIcon.getIconWidth() + ", imageheight="
 812  0
                             + imageIcon.getIconHeight() + ")", 80);
 813  
                 }
 814  0
                 tmpImage.flush();
 815  0
                 tmpImage = null;
 816  
 
 817  0
                 freeMemory("PictureFileData.getImageIcon()", uploadPolicy);
 818  
             }
 819  
         }
 820  0
         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  0
         Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(DefaultFileData.getExtension(file.getName())
 835  0
                 .toLowerCase());
 836  0
         return iter.hasNext();
 837  
     }
 838  
 }