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                if (writtenToDisk)
052                    content = null;
053            }
054
055        }
056        return img;
057    }
058
059    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
060        /*
061         * This method below will be needed, if Apache Commons JCS (or any other caching system), will update
062         * disk representation of object from memory, once it is put into the cache (for example - at closing the cache)
063         *
064         * For now it is not the case, as we use DiskUsagePattern.UPDATE, which on JCS shutdown doesn't write again memory
065         * contents to file, so the fact, that we've cleared never gets saved to the disk
066         *
067         * This method is commented out, as it will convert all cache entries to PNG files regardless of what was returned.
068         * It might cause recompression/change of format which may result in decreased quality of imagery
069         */
070        /* synchronized (this) {
071            if (content == null && img != null) {
072                ByteArrayOutputStream restoredData = new ByteArrayOutputStream();
073                ImageIO.write(img, "png", restoredData);
074                content = restoredData.toByteArray();
075            }
076            out.writeObject(this);
077        }
078         */
079        synchronized (this) {
080            if (content == null && img != null) {
081                throw new AssertionError("Trying to serialize (save to disk?) an BufferedImageCacheEntry " +
082                        "that was converted to BufferedImage and no raw data is present anymore");
083            }
084            out.writeObject(this);
085            // ugly hack to wait till element will get to disk to clean the memory
086            writtenToDisk = true;
087
088            if (img != null) {
089                content = null;
090            }
091        }
092    }
093}