1/* 2 * Copyright (c) 2005, 2006, 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 */ 25package javax.swing.plaf.nimbus; 26 27import java.awt.GraphicsConfiguration; 28import java.awt.Image; 29import java.lang.ref.ReferenceQueue; 30import java.lang.ref.SoftReference; 31import java.util.Arrays; 32import java.util.Iterator; 33import java.util.LinkedHashMap; 34import java.util.Map; 35import java.util.concurrent.locks.ReadWriteLock; 36import java.util.concurrent.locks.ReentrantReadWriteLock; 37 38/** 39 * ImageCache - A fixed pixel count sized cache of Images keyed by arbitrary set of arguments. All images are held with 40 * SoftReferences so they will be dropped by the GC if heap memory gets tight. When our size hits max pixel count least 41 * recently requested images are removed first. 42 * 43 * @author Created by Jasper Potts (Aug 7, 2007) 44 */ 45class ImageCache { 46 // Ordered Map keyed by args hash, ordered by most recent accessed entry. 47 private final LinkedHashMap<Integer, PixelCountSoftReference> map = 48 new LinkedHashMap<Integer, PixelCountSoftReference>(16, 0.75f, true); 49 // Maximum number of pixels to cache, this is used if maxCount 50 private final int maxPixelCount; 51 // Maximum cached image size in pxiels 52 private final int maxSingleImagePixelSize; 53 // The current number of pixels stored in the cache 54 private int currentPixelCount = 0; 55 // Lock for concurrent access to map 56 private ReadWriteLock lock = new ReentrantReadWriteLock(); 57 // Reference queue for tracking lost softreferences to images in the cache 58 private ReferenceQueue<Image> referenceQueue = new ReferenceQueue<Image>(); 59 // Singleton Instance 60 private static final ImageCache instance = new ImageCache(); 61 62 63 /** Get static singleton instance */ 64 static ImageCache getInstance() { 65 return instance; 66 } 67 68 public ImageCache() { 69 this.maxPixelCount = (8 * 1024 * 1024) / 4; // 8Mb of pixels 70 this.maxSingleImagePixelSize = 300 * 300; 71 } 72 73 public ImageCache(int maxPixelCount, int maxSingleImagePixelSize) { 74 this.maxPixelCount = maxPixelCount; 75 this.maxSingleImagePixelSize = maxSingleImagePixelSize; 76 } 77 78 /** Clear the cache */ 79 public void flush() { 80 lock.readLock().lock(); 81 try { 82 map.clear(); 83 } finally { 84 lock.readLock().unlock(); 85 } 86 } 87 88 /** 89 * Check if the image size is to big to be stored in the cache 90 * 91 * @param w The image width 92 * @param h The image height 93 * @return True if the image size is less than max 94 */ 95 public boolean isImageCachable(int w, int h) { 96 return (w * h) < maxSingleImagePixelSize; 97 } 98 99 /** 100 * Get the cached image for given keys 101 * 102 * @param config The graphics configuration, needed if cached image is a Volatile Image. Used as part of cache key 103 * @param w The image width, used as part of cache key 104 * @param h The image height, used as part of cache key 105 * @param args Other arguments to use as part of the cache key 106 * @return Returns the cached Image, or null there is no cached image for key 107 */ 108 public Image getImage(GraphicsConfiguration config, int w, int h, Object... args) { 109 lock.readLock().lock(); 110 try { 111 PixelCountSoftReference ref = map.get(hash(config, w, h, args)); 112 // check reference has not been lost and the key truly matches, in case of false positive hash match 113 if (ref != null && ref.equals(config,w, h, args)) { 114 return ref.get(); 115 } else { 116 return null; 117 } 118 } finally { 119 lock.readLock().unlock(); 120 } 121 } 122 123 /** 124 * Sets the cached image for the specified constraints. 125 * 126 * @param image The image to store in cache 127 * @param config The graphics configuration, needed if cached image is a Volatile Image. Used as part of cache key 128 * @param w The image width, used as part of cache key 129 * @param h The image height, used as part of cache key 130 * @param args Other arguments to use as part of the cache key 131 * @return true if the image could be cached or false if the image is too big 132 */ 133 public boolean setImage(Image image, GraphicsConfiguration config, int w, int h, Object... args) { 134 if (!isImageCachable(w, h)) return false; 135 int hash = hash(config, w, h, args); 136 lock.writeLock().lock(); 137 try { 138 PixelCountSoftReference ref = map.get(hash); 139 // check if currently in map 140 if (ref != null && ref.get() == image) { 141 return true; 142 } 143 // clear out old 144 if (ref != null) { 145 currentPixelCount -= ref.pixelCount; 146 map.remove(hash); 147 } 148 // add new image to pixel count 149 int newPixelCount = image.getWidth(null) * image.getHeight(null); 150 currentPixelCount += newPixelCount; 151 // clean out lost references if not enough space 152 if (currentPixelCount > maxPixelCount) { 153 while ((ref = (PixelCountSoftReference)referenceQueue.poll()) != null){ 154 //reference lost 155 map.remove(ref.hash); 156 currentPixelCount -= ref.pixelCount; 157 } 158 } 159 // remove old items till there is enough free space 160 if (currentPixelCount > maxPixelCount) { 161 Iterator<Map.Entry<Integer, PixelCountSoftReference>> mapIter = map.entrySet().iterator(); 162 while ((currentPixelCount > maxPixelCount) && mapIter.hasNext()) { 163 Map.Entry<Integer, PixelCountSoftReference> entry = mapIter.next(); 164 mapIter.remove(); 165 Image img = entry.getValue().get(); 166 if (img != null) img.flush(); 167 currentPixelCount -= entry.getValue().pixelCount; 168 } 169 } 170 // finaly put new in map 171 map.put(hash, new PixelCountSoftReference(image, referenceQueue, newPixelCount,hash, config, w, h, args)); 172 return true; 173 } finally { 174 lock.writeLock().unlock(); 175 } 176 } 177 178 /** Create a unique hash from all the input */ 179 private int hash(GraphicsConfiguration config, int w, int h, Object ... args) { 180 int hash; 181 hash = (config != null ? config.hashCode() : 0); 182 hash = 31 * hash + w; 183 hash = 31 * hash + h; 184 hash = 31 * hash + Arrays.deepHashCode(args); 185 return hash; 186 } 187 188 189 /** Extended SoftReference that stores the pixel count even after the image is lost */ 190 private static class PixelCountSoftReference extends SoftReference<Image> { 191 private final int pixelCount; 192 private final int hash; 193 // key parts 194 private final GraphicsConfiguration config; 195 private final int w; 196 private final int h; 197 private final Object[] args; 198 199 public PixelCountSoftReference(Image referent, ReferenceQueue<? super Image> q, int pixelCount, int hash, 200 GraphicsConfiguration config, int w, int h, Object[] args) { 201 super(referent, q); 202 this.pixelCount = pixelCount; 203 this.hash = hash; 204 this.config = config; 205 this.w = w; 206 this.h = h; 207 this.args = args; 208 } 209 210 public boolean equals (GraphicsConfiguration config, int w, int h, Object[] args){ 211 return config == this.config && 212 w == this.w && 213 h == this.h && 214 Arrays.equals(args, this.args); 215 } 216 } 217} 218