1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.image;
  27 
  28 import java.awt.*;
  29 import java.lang.ref.*;
  30 import java.util.*;
  31 import java.util.concurrent.locks.*;
  32 
  33 import sun.awt.AppContext;
  34 /**
  35  * ImageCache - A fixed pixel count sized cache of Images keyed by arbitrary set of arguments. All images are held with
  36  * SoftReferences so they will be dropped by the GC if heap memory gets tight. When our size hits max pixel count least
  37  * recently requested images are removed first.
  38  */
  39 final public class ImageCache {
  40 
  41     abstract static class RecyclableSingleton<T> {
  42         final T get() {
  43             final AppContext appContext = AppContext.getAppContext();
  44             SoftReference<T> ref = (SoftReference<T>) appContext.get(this);
  45             if (ref != null) {
  46                 final T object = ref.get();
  47                 if (object != null) return object;
  48             }
  49             final T object = getInstance();
  50             ref = new SoftReference<T>(object);
  51             appContext.put(this, ref);
  52             return object;
  53         }
  54 
  55         void reset() {
  56             AppContext.getAppContext().remove(this);
  57         }
  58 
  59         abstract T getInstance();
  60     }
  61 
  62     // Ordered Map keyed by args hash, ordered by most recent accessed entry.
  63     private final LinkedHashMap<PixelsKey, ImageSoftReference> map = new LinkedHashMap<>(16, 0.75f, true);
  64 
  65     // Maximum number of pixels to cache, this is used if maxCount
  66     private final int maxPixelCount;
  67     // The current number of pixels stored in the cache
  68     private int currentPixelCount = 0;
  69 
  70     // Lock for concurrent access to map
  71     private final ReadWriteLock lock = new ReentrantReadWriteLock();
  72     // Reference queue for tracking lost softreferences to images in the cache
  73     private final ReferenceQueue<Image> referenceQueue = new ReferenceQueue<>();
  74 
  75     // Singleton Instance
  76     private static final RecyclableSingleton<ImageCache> instance = new RecyclableSingleton<ImageCache>() {
  77         @Override
  78         protected ImageCache getInstance() {
  79             return new ImageCache();
  80         }
  81     };
  82     public static ImageCache getInstance() {
  83         return instance.get();
  84     }
  85 
  86     ImageCache(final int maxPixelCount) {
  87         this.maxPixelCount = maxPixelCount;
  88     }
  89 
  90     ImageCache() {
  91         this((8 * 1024 * 1024) / 4); // 8Mb of pixels
  92     }
  93 
  94     public void flush() {
  95         lock.writeLock().lock();
  96         try {
  97             map.clear();
  98         } finally {
  99             lock.writeLock().unlock();
 100         }
 101     }
 102 
 103     public Image getImage(final PixelsKey key){
 104         final ImageSoftReference ref;
 105         lock.readLock().lock();
 106         try {
 107             ref = map.get(key);
 108         } finally {
 109             lock.readLock().unlock();
 110         }
 111         return ref == null ? null : ref.get();
 112     }
 113 
 114     /**
 115      * Sets the cached image for the specified constraints.
 116      *
 117      * @param key The key with which the specified image is to be associated
 118      * @param image  The image to store in cache
 119      * @return true if the image could be cached, false otherwise.
 120      */
 121     public boolean setImage(final PixelsKey key, final Image image) {
 122 
 123         lock.writeLock().lock();
 124         try {
 125             ImageSoftReference ref = map.get(key);
 126 
 127             // check if currently in map
 128             if (ref != null) {
 129                 if (ref.get() != null) {
 130                     return true;
 131                 }
 132                 // soft image has been removed
 133                 currentPixelCount -= key.getPixelCount();
 134                 map.remove(key);
 135             };
 136 
 137 
 138             // add new image to pixel count
 139             final int newPixelCount = key.getPixelCount();
 140             currentPixelCount += newPixelCount;
 141             // clean out lost references if not enough space
 142             if (currentPixelCount > maxPixelCount) {
 143                 while ((ref = (ImageSoftReference)referenceQueue.poll()) != null) {
 144                     //reference lost
 145                     map.remove(ref.key);
 146                     currentPixelCount -= ref.key.getPixelCount();
 147                 }
 148             }
 149 
 150             // remove old items till there is enough free space
 151             if (currentPixelCount > maxPixelCount) {
 152                 final Iterator<Map.Entry<PixelsKey, ImageSoftReference>> mapIter = map.entrySet().iterator();
 153                 while ((currentPixelCount > maxPixelCount) && mapIter.hasNext()) {
 154                     final Map.Entry<PixelsKey, ImageSoftReference> entry = mapIter.next();
 155                     mapIter.remove();
 156                     final Image img = entry.getValue().get();
 157                     if (img != null) img.flush();
 158                     currentPixelCount -= entry.getValue().key.getPixelCount();
 159                 }
 160             }
 161 
 162             // finally put new in map
 163             map.put(key, new ImageSoftReference(key, image, referenceQueue));
 164             return true;
 165         } finally {
 166             lock.writeLock().unlock();
 167         }
 168     }
 169 
 170     public interface PixelsKey {
 171 
 172         int getPixelCount();
 173     }
 174 
 175     private static class ImageSoftReference extends SoftReference<Image> {
 176 
 177         final PixelsKey key;
 178 
 179         ImageSoftReference(final PixelsKey key, final Image referent,
 180                 final ReferenceQueue<? super Image> q) {
 181             super(referent, q);
 182             this.key = key;
 183         }
 184     }
 185 }