org.photovault.imginfo.ImageFile.java Source code

Java tutorial

Introduction

Here is the source code for org.photovault.imginfo.ImageFile.java

Source

/*
  Copyright (c) 2007 Harri Kaimio
     
  This file is part of Photovault.
     
  Photovault is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
     
  Photovault is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even therrore implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.
     
  You should have received a copy of the GNU General Public License
  along with Photovault; if not, write to the Free Software Foundation,
  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

package org.photovault.imginfo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.MapKey;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.photovault.common.PhotovaultException;
import org.photovault.dcraw.RawImage;

/**
 * ImageFile represents info about a certain image file that may or may not exist 
 * in some of Photovault volumes. Image files are regarded as immutable objects -
 * in practice it is of course possible to modify an image file but after the
 * modification it is regarded as a new one. Therefore, an image file in file system 
 * can be linked to an ImageFile object by its MD5 hash code.
 * <p>
 * ImageFile contains one or more {@link ImageInstance}s. There can be several (or 
 * no) copies of an ImageFile available in different volumes. The known locations 
 * are described bu {@link FileLocation} objects.
 * </p>
 * @author Harri Kaimio
 * @since 0.6.0
 */

@NamedQueries({
        /*
         Find the image file object that describes file in an external volume
         Parameters:
         volume - The external volume
         fname - Name of the file (i.e. relative path from volume base directory)
         */
        @NamedQuery(name = "findImageFileByLocation", query = "select f from ImageFile f join f.locations loc "
                + "where loc.volume = :volume and loc.fname = :fname"),
        /*
         * Find all image files that have an instance stored in a certain directory
         * in external volume
         * Parameters:
         * volume - the external volume
         * dir - path to the directory
         */
        @NamedQuery(name = "findImageFilesInDir", query = "select f from ImageFile f join f.locations loc "
                + "where loc.volume = :volume and loc.dirName = :dir") })
@Entity
@Table(name = "pv_image_files")
public class ImageFile implements java.io.Serializable {

    static Log log = LogFactory.getLog(ImageFile.class);

    /** Creates a new instance of ImageFile */
    public ImageFile() {
        id = UUID.randomUUID();
    }

    /**
     * Creates a new ImageFile from given file. Note that the file is not added
     * to the image as a new location, this must be done separately.
     * @param f The file from which the ImageFile fields are calculated.
     * @throws org.photovault.common.PhotovaultException If there is an error
     * @throws java.io.IOException If reading the image file failed.
     */
    public ImageFile(File f) throws PhotovaultException, IOException {
        // Set lastChecked to a time that is certainly earlier than any data read 
        // from file system
        hash = calcHash(f);
        init(f);
    }

    /**
     Create a new image file when the MD5 hash of the file is already known.
     * @param f The file from which the ImageFile fields are calculated.
     * @param hash MD5 hash for file f.
     * @throws org.photovault.common.PhotovaultException If there is an error
     * @throws java.io.IOException If reading the image file failed.
     */
    public ImageFile(File f, byte[] hash) throws PhotovaultException, IOException {
        if (hash != null) {
            this.hash = hash;
        } else {
            hash = calcHash(f);
        }
        init(f);
    }

    private void init(File f) throws PhotovaultException, IOException {
        id = UUID.nameUUIDFromBytes(hash);
        size = f.length();
        readImageFile(f);
    }

    private UUID id;

    /**
     * Get the UUID of this ImageFile
     * @return UUID of this object
     */
    @Id
    @Column(name = "id")
    @org.hibernate.annotations.Type(type = "org.photovault.persistence.UUIDUserType")
    public UUID getId() {
        return id;
    }

    /**
     * Set UUID of this object. Note that this should be called by persistence layer 
     * only.
     * @param id UUID for the ImageFile object.
     */
    public void setId(UUID id) {
        this.id = id;
    }

    long size;

    /**
     * Get size of the associated image file
     * @return Size of the file in bytes.
     */
    @Column(name = "file_size")
    public long getFileSize() {
        return size;
    }

    /**
     * Set size of the associated file. Tgis method should be called only by persistence 
     * layer.
     * @param size Size of the associated file in bytes.
     */
    public void setFileSize(long size) {
        this.size = size;
    }

    private byte[] hash;

    /**
     * Get MD5 hash of the associated image file.
     * @return Hash of the file
     */
    @Column(name = "md5_hash")
    // @org.hibernate.annotations.Type( type = "varbinary" )
    public byte[] getHash() {
        return hash;
    }

    /**
     * Set the MD5 hash of associated file
     * @param hash Value of hash.
     */
    public void setHash(byte[] hash) {
        this.hash = hash;
    }

    Set<FileLocation> locations = new HashSet<FileLocation>();

    /**
     * Get the known locations in which this file is stored
     * @return Set of known locations
     */
    @org.hibernate.annotations.CollectionOfElements
    @JoinTable(name = "pv_image_locations", joinColumns = @JoinColumn(name = "id"))
    public Set<FileLocation> getLocations() {
        return locations;
    }

    /**
     * Set the known locations of the file. Used by persistence layer only.
     * @param locations New value for locations.
     */
    protected void setLocations(Set<FileLocation> locations) {
        this.locations = locations;
    }

    /**
     * Add a new location for the image.
     * @param newLocation FileLocation object that describes the location.
     */
    public void addLocation(FileLocation newLocation) {
        newLocation.file = this;
        locations.add(newLocation);
    }

    /**
     * remove a location from the file.
     * @param location Location to remove.
     */
    public void removeLocation(FileLocation location) {
        locations.remove(location);
    }

    /**
     Find a copy of this file that can be used
     @return File that is described by this object or <code>null</code> if
     no copy is available.
     */
    public File findAvailableCopy() {
        for (FileLocation loc : locations) {
            if (loc.getVolume().isAvailable()) {
                try {
                    File f = loc.getVolume().mapFileName(loc.getFname());
                    if (f.exists()) {
                        return f;
                    }
                } catch (FileNotFoundException ex) {
                    log.warn("Exception while looking for available file: " + ex.getMessage());
                }
            }
        }
        return null;
    }

    /**
     Images contained in this file
     */
    Map<String, ImageDescriptorBase> images = new HashMap<String, ImageDescriptorBase>();

    /**
     * Get all images stored in this file
     * @return Map that maps image location descriptor to the image descriptor object
     */
    @MapKey(name = "locator")
    @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, mappedBy = "file")
    @org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE,
            org.hibernate.annotations.CascadeType.DELETE })
    public Map<String, ImageDescriptorBase> getImages() {
        return images;
    }

    /**
     * Set the images stored in this file. USed by persistence layer only.
     * @param images New set of images.
     */
    protected void setImages(Map<String, ImageDescriptorBase> images) {
        this.images = images;
    }

    /**
     *     Opens the image file specified by fname & dirname properties, read
     *     the rest of fields from that and create the instances
     * @param f File from which to read the information
     * @throws org.photovault.common.PhotovaultException If the file is not of recognized type
     * @throws IOException if the image cannot be read.
     */
    protected void readImageFile(File f) throws PhotovaultException, IOException {

        String fname = f.getName();
        int lastDotPos = fname.lastIndexOf(".");
        if (lastDotPos <= 0 || lastDotPos >= fname.length() - 1) {
            throw new PhotovaultException("Cannot determine file type extension of " + f.getAbsolutePath());
        }
        String suffix = fname.substring(lastDotPos + 1);
        Iterator readers = ImageIO.getImageReadersBySuffix(suffix);
        if (readers.hasNext()) {
            ImageReader reader = (ImageReader) readers.next();
            ImageInputStream iis = null;
            try {
                iis = ImageIO.createImageInputStream(f);
                if (iis != null) {
                    reader.setInput(iis, true);
                    // int imageCount = reader.getNumImages( true );
                    // for now...
                    int imageCount = 1;
                    for (int n = 0; n < imageCount; n++) {
                        ImageDescriptorBase img = new OriginalImageDescriptor(this, "image#" + n);
                        img.setWidth(reader.getWidth(n));
                        img.setHeight(reader.getHeight(n));
                        // img.setInstanceType( ImageInstance.INSTANCE_TYPE_ORIGINAL );
                        img.setFile(this);
                    }
                    reader.dispose();
                }
            } catch (IOException ex) {
                log.debug("Exception in readImageFile: " + ex.getMessage());
                throw ex;
            } finally {
                if (iis != null) {
                    try {
                        iis.close();
                    } catch (IOException ex) {
                        log.warn("Cannot close image stream: " + ex.getMessage());
                    }
                }
            }
        } else {
            RawImage ri = new RawImage(f);
            if (ri.isValidRawFile()) {
                // PlanarImage img = ri.getCorrectedImage();
                ImageDescriptorBase img = new OriginalImageDescriptor(this, "image#0");
                img.setWidth(ri.getWidth());
                img.setHeight(ri.getHeight());
            } else {
                throw new PhotovaultException(
                        "Unknown image file extension " + suffix + "\nwhile reading " + f.getAbsolutePath());
            }
        }

    }

    /**
     *     Utility function to calculate the hash of a specific file
     * @param f The file
     * @return Hash of f
     */
    public static byte[] calcHash(File f) {
        FileInputStream is = null;
        byte hash[] = null;
        try {
            is = new FileInputStream(f);
            byte readBuffer[] = new byte[4096];
            MessageDigest md = MessageDigest.getInstance("MD5");
            int bytesRead = -1;
            while ((bytesRead = is.read(readBuffer)) > 0) {
                md.update(readBuffer, 0, bytesRead);
            }
            hash = md.digest();
        } catch (NoSuchAlgorithmException ex) {
            log.error("MD5 algorithm not found");
        } catch (FileNotFoundException ex) {
            log.error(f.getAbsolutePath() + "not found");
        } catch (IOException ex) {
            log.error("IOException while calculating hash: " + ex.getMessage());
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException ex) {
                log.error("Cannot close stream after calculating hash");
            }
        }
        return hash;
    }

    /**
     Utility method to get an imge by its location in file
     @param string locator string of the image
     */
    public ImageDescriptorBase getImage(String string) {
        return images.get(string);
    }

}