001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.cache;
003
004import java.awt.image.BufferedImage;
005import java.io.ByteArrayInputStream;
006import java.io.IOException;
007
008import javax.imageio.ImageIO;
009
010/**
011 * Cache Entry that has methods to get the BufferedImage, that will be cached along in memory
012 * but will be not serialized when saved to the disk (to avoid duplication of data)
013 * @author Wiktor Niesiobędzki
014 *
015 */
016public class BufferedImageCacheEntry extends CacheEntry {
017    private static final long serialVersionUID = 1L; //version
018    // transient to avoid serialization, volatile to avoid synchronization of whole getImage() method
019    private transient volatile BufferedImage img;
020    private transient volatile boolean writtenToDisk;
021    // we need to have separate control variable, to know, if we already tried to load the image, as img might be null
022    // after we loaded image, as for example, when image file is malformed (eg. HTML file)
023    private transient volatile boolean imageLoaded;
024
025    /**
026     *
027     * @param content byte array containing image
028     */
029    public BufferedImageCacheEntry(byte[] content) {
030        super(content);
031    }
032
033    /**
034     * Returns BufferedImage from for the content. Subsequent calls will return the same instance,
035     * to reduce overhead of ImageIO
036     *
037     * @return BufferedImage of cache entry content
038     * @throws IOException if an error occurs during reading.
039     */
040    public BufferedImage getImage() throws IOException {
041        if (imageLoaded)
042            return img;
043        synchronized (this) {
044            if (imageLoaded)
045                return img;
046            byte[] content = getContent();
047            if (content != null && content.length > 0) {
048                img = ImageIO.read(new ByteArrayInputStream(content));
049                imageLoaded = true;
050            }
051        }
052        return img;
053    }
054
055    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
056        /*
057         * This method below will be needed, if Apache Commons JCS (or any other caching system), will update
058         * disk representation of object from memory, once it is put into the cache (for example - at closing the cache)
059         *
060         * For now it is not the case, as we use DiskUsagePattern.UPDATE, which on JCS shutdown doesn't write again memory
061         * contents to file, so the fact, that we've cleared never gets saved to the disk
062         *
063         * This method is commented out, as it will convert all cache entries to PNG files regardless of what was returned.
064         * It might cause recompression/change of format which may result in decreased quality of imagery
065         */
066        /* synchronized (this) {
067            if (content == null && img != null) {
068                ByteArrayOutputStream restoredData = new ByteArrayOutputStream();
069                ImageIO.write(img, "png", restoredData);
070                content = restoredData.toByteArray();
071            }
072            out.writeObject(this);
073        }
074         */
075        synchronized (this) {
076            if (content == null && img != null) {
077                throw new AssertionError("Trying to serialize (save to disk?) an BufferedImageCacheEntry " +
078                        "that was converted to BufferedImage and no raw data is present anymore");
079            }
080            out.writeObject(this);
081            // ugly hack to wait till element will get to disk to clean the memory
082            writtenToDisk = true;
083
084            if (img != null) {
085                content = null;
086            }
087        }
088    }
089}