/* * @(#)CachedPainter.java 1.2 04/02/15 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.swing.plaf.metal; import java.awt.*; import java.awt.image.*; import javax.swing.Icon; import java.lang.ref.SoftReference; import java.util.*; /** * A base class used for icons or images that are expensive to paint. * A subclass will do the following: *
    *
  1. Invoke paint when you want to paint the image, * if you are implementing Icon you'll invoke this from * paintIcon. * The args argument is useful when additional state is needed. *
  2. Override paintToImage to render the image. The code that * lives here is equivalent to what previously would go in * paintIcon, for an Icon. *
* * @version @(#)CachedPainter.java 1.2 04/02/15 */ abstract class CachedPainter { // CacheMap maps from class to Cache. private static final Map cacheMap = new HashMap(); private static Cache getCache(Object key) { synchronized(cacheMap) { Cache cache = cacheMap.get(key); if (cache == null) { cache = new Cache(1); cacheMap.put(key, cache); } return cache; } } /** * Creates an instance of CachedPainter that will cache up * to cacheCount images of this class. * * @param cacheCount Max number of images to cache */ public CachedPainter(int cacheCount) { getCache(getClass()).setMaxCount(cacheCount); } /** * Renders the cached image to the the passed in Graphic. * If there is no cached image paintToImage will be invoked. * paintImage is invoked to paint the cached image. */ protected void paint(Component c, Graphics g, int x, int y, int w, int h, Object... args) { if (w <= 0 || h <= 0) { return; } Object key = getClass(); GraphicsConfiguration config = c.getGraphicsConfiguration(); Cache cache = getCache(key); Image image = cache.getImage(key, config, w, h, args); int attempts = 0; do { boolean draw = false; if (image instanceof VolatileImage) { // See if we need to recreate the image switch (((VolatileImage)image).validate(config)) { case VolatileImage.IMAGE_INCOMPATIBLE: ((VolatileImage)image).flush(); image = null; break; case VolatileImage.IMAGE_RESTORED: draw = true; break; } } if (image == null) { // Recreate the image image = createImage(c, w, h, config); cache.setImage(key, config, w, h, args, image); draw = true; } if (draw) { // Render to the Image Graphics g2 = image.getGraphics(); paintToImage(c, g2, w, h, args); g2.dispose(); } // Render to the passed in Graphics paintImage(c, g, x, y, w, h, image, args); // If we did this 3 times and the contents are still lost // assume we're painting to a VolatileImage that is bogus and // give up. Presumably we'll be called again to paint. } while ((image instanceof VolatileImage) && ((VolatileImage)image).contentsLost() && ++attempts < 3); } /** * Paints the representation to cache to the supplied Graphics. * * @param c Component painting to * @param g Graphics to paint to * @param w Width to paint to * @param h Height to paint to * @param args Arguments supplied to paint */ protected abstract void paintToImage(Component c, Graphics g, int w, int h, Object[] args); /** * Paints the image to the specified location. * * @param c Component painting to * @param g Graphics to paint to * @param x X coordinate to paint to * @param y Y coordinate to paint to * @param w Width to paint to * @param h Height to paint to * @param image Image to paint * @param args Arguments supplied to paint */ protected void paintImage(Component c, Graphics g, int x, int y, int w, int h, Image image, Object[] args) { g.drawImage(image, x, y, null); } /** * Creates the image to cache. This returns an opaque image, subclasses * that require translucency or transparency will need to override this * method. * * @param c Component painting to * @param w Width of image to create * @param h Height to image to create * @param config GraphicsConfiguration that will be * rendered to, this may be null. */ protected Image createImage(Component c, int w, int h, GraphicsConfiguration config) { if (config == null) { return new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); } return config.createCompatibleVolatileImage(w, h); } /** * Cache is used to cache an image based on a set of arguments. */ private static class Cache { // Maximum number of entries to cache private int maxCount; // The entries. private java.util.List> entries; Cache(int maxCount) { this.maxCount = maxCount; entries = new ArrayList>(maxCount); } void setMaxCount(int maxCount) { this.maxCount = maxCount; } private Entry getEntry(Object key, GraphicsConfiguration config, int w, int h, Object[] args) { synchronized(this) { Entry entry; for (int counter = entries.size() - 1; counter >= 0;counter--){ entry = entries.get(counter).get(); if (entry == null) { // SoftReference was invalidated, remove the entry entries.remove(counter); } else if (entry.equals(config, w, h, args)) { // Found the entry, return it. return entry; } } // Entry doesn't exist entry = new Entry(config, w, h, args); if (entries.size() == maxCount) { entries.remove(0); } entries.add(new SoftReference(entry)); return entry; } } /** * Returns the cached Image, or null, for the specified arguments. */ public Image getImage(Object key, GraphicsConfiguration config, int w, int h, Object[] args) { Entry entry = getEntry(key, config, w, h, args); return entry.getImage(); } /** * Sets the cached image for the specified constraints. */ public void setImage(Object key, GraphicsConfiguration config, int w, int h, Object[] args, Image image) { Entry entry = getEntry(key, config, w, h, args); entry.setImage(image); } /** * Caches set of arguments and Image. */ private static class Entry { private GraphicsConfiguration config; private Object[] args; private Image image; private int w; private int h; Entry(GraphicsConfiguration config, int w, int h, Object[] args) { this.config = config; this.args = args; this.w = w; this.h = h; } public void setImage(Image image) { this.image = image; } public Image getImage() { return image; } public String toString() { String value = super.toString() + "[ graphicsConfig=" + config + ", image=" + image + ", w=" + w + ", h=" + h; if (args != null) { for (int counter = 0; counter < args.length; counter++) { value += ", " + args[counter]; } } value += "]"; return value; } public boolean equals(GraphicsConfiguration config, int w, int h, Object[] args){ if (this.w == w && this.h == h && ((this.config != null && this.config.equals(config)) || (this.config == null && config == null))) { if (this.args == null && args == null) { return true; } if (this.args != null && args != null && this.args.length == args.length) { for (int counter = args.length - 1; counter >= 0; counter--) { if (!this.args[counter].equals(args[counter])) { return false; } } return true; } } return false; } } } }