EMMA Coverage Report (generated Sun Apr 20 22:38:01 CEST 2008)
[all classes][net.sf.jomic.tools]

COVERAGE SUMMARY FOR SOURCE FILE [ImageCache.java]

nameclass, %method, %block, %line, %
ImageCache.java100% (1/1)78%  (14/18)66%  (499/756)71%  (91.2/128)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ImageCache100% (1/1)78%  (14/18)66%  (499/756)71%  (91.2/128)
createBrokenImage (): RenderedImage 0%   (0/1)0%   (0/26)0%   (0/4)
dispose (): void 0%   (0/1)0%   (0/24)0%   (0/7)
getDefaultImageDimension (): Dimension 0%   (0/1)0%   (0/15)0%   (0/2)
getDimension (File): Dimension 0%   (0/1)0%   (0/38)0%   (0/7)
obtainImageDimension (File): Dimension 100% (1/1)52%  (28/54)54%  (5.4/10)
has (File): boolean 100% (1/1)64%  (14/22)69%  (2.1/3)
obtainImage (File): RenderedImage 100% (1/1)64%  (14/22)69%  (2.1/3)
getMaxSize (): long 100% (1/1)67%  (8/12)78%  (1.6/2)
getUsedSize (): long 100% (1/1)67%  (8/12)78%  (1.6/2)
clear (): void 100% (1/1)69%  (20/29)88%  (5.3/6)
getEntryCount (): int 100% (1/1)69%  (9/13)78%  (1.6/2)
remove (ImageCacheEntry): void 100% (1/1)71%  (62/87)88%  (10.5/12)
get (File, ImageInCacheListener): RenderedImage 100% (1/1)76%  (54/71)81%  (8.9/11)
ImageCache (String, long): void 100% (1/1)78%  (76/97)89%  (15.2/17)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
findLeastRecentlyAccessedEntry (): ImageCacheEntry 100% (1/1)83%  (44/53)92%  (11.9/13)
get (File): RenderedImage 100% (1/1)90%  (147/163)93%  (23.2/25)
getName (): String 100% (1/1)100% (3/3)100% (1/1)

1// Jomic - a viewer for comic book archives.
2// Copyright (C) 2004-2008 Thomas Aglassinger
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16package net.sf.jomic.tools;
17 
18import java.awt.Color;
19import java.awt.Dimension;
20import java.awt.image.RenderedImage;
21import java.io.File;
22import java.io.IOException;
23import java.util.HashMap;
24import java.util.Iterator;
25import java.util.Map;
26 
27import net.sf.wraplog.Logger;
28 
29/**
30 *  Cache to store RenderedImages in memory. When the memory usage exeeds a maximum size, the least
31 *  recent images are removed. Note that the cache holds at least one image, so if this is bigger
32 *  than the maximum size, so is the whole cache.
33 */
34public class ImageCache implements CacheInfo
35{
36    private static final int BROKEN_IMAGE_HEIGHT = 800;
37    private static final int BROKEN_IMAGE_WIDTH = 600;
38    private static final int DEFAULT_BROKEN_BACKGROUND = 0xf0f0f0;
39    private static final int DEFAULT_BROKEN_FOREGROUND = 0xe04040;
40    private static final int DEFAULT_BUSY_BACKGROUND = 0xf0f0f0;
41    private static final int DEFAULT_BUSY_FOREGROUND = 0xd0d0d0;
42    private static final double ONE_HUNDRED = 100.0;
43    private Color brokenBackgroundColor;
44    private Color brokenForegroundColor;
45    private Color busyBackgroundColor;
46    private Color busyForegroundColor;
47    private boolean disposed;
48    private Map entries;
49    private ImageTools imageTools;
50    private Logger logger;
51    private long maxMemorySize;
52    private long memorySize;
53    private String name;
54    private ImageCacheRenderThread renderer;
55 
56    public ImageCache(String newName, long newMaxMemorySize) {
57        assert !disposed;
58        assert newName != null;
59        assert newName.length() > 0;
60        assert newMaxMemorySize >= 0;
61        logger = Logger.getLogger(ImageCache.class);
62        imageTools = ImageTools.instance();
63        name = newName;
64        entries = new HashMap();
65        maxMemorySize = newMaxMemorySize;
66        brokenBackgroundColor = new Color(DEFAULT_BROKEN_BACKGROUND);
67        brokenForegroundColor = new Color(DEFAULT_BROKEN_FOREGROUND);
68        busyBackgroundColor = new Color(DEFAULT_BUSY_BACKGROUND);
69        busyForegroundColor = new Color(DEFAULT_BUSY_FOREGROUND);
70        renderer = new ImageCacheRenderThread(this);
71        renderer.start();
72    }
73 
74    public RenderedImage get(File imageFile)
75        throws IOException {
76        assert !disposed;
77        assert imageFile != null;
78        ImageCacheEntry entry = (ImageCacheEntry) entries.get(imageFile);
79 
80        if (entry == null) {
81            RenderedImage image = obtainImage(imageFile);
82 
83            entry = new ImageCacheEntry(imageFile, image);
84 
85            long entryMemorySize = entry.getImageSize();
86 
87            memorySize += entryMemorySize;
88            if (logger.isInfoEnabled()) {
89                logger.info("memory size increased by " + entryMemorySize + " bytes to " + memorySize);
90            }
91            while ((memorySize > maxMemorySize) && (entries.size() > 0)) {
92                ImageCacheEntry leastRecentlyAccessedEntry = findLeastRecentlyAccessedEntry();
93 
94                logger.info("removing least recently used image to meet memory size requirement");
95                remove(leastRecentlyAccessedEntry);
96            }
97            entries.put(imageFile, entry);
98            if (logger.isInfoEnabled()) {
99                double percentUsed = (ONE_HUNDRED * getUsedSize() / getMaxSize());
100 
101                logger.info("entry added: {}", imageFile);
102                logger.info("current cache statistics: " + getEntryCount() + " entries use "
103                        + getUsedSize() + " of " + getMaxSize() + " bytes ("
104                        + StringTools.instance().getPercentText(percentUsed) + "%)");
105            }
106        }
107 
108        assert entry != null;
109        entry.updateLastAccessed();
110 
111        RenderedImage result = entry.getImage();
112 
113        assert result != null;
114        return result;
115    }
116 
117    public RenderedImage get(File imageFile, ImageInCacheListener listener)
118        throws IOException {
119        assert !disposed;
120        assert imageFile != null;
121        assert listener != null;
122        RenderedImage result;
123 
124        synchronized (entries) {
125            if (has(imageFile)) {
126                result = get(imageFile);
127            } else {
128                Dimension imageDimension = obtainImageDimension(imageFile);
129 
130                result = imageTools.createBusyImage(imageDimension.width, imageDimension.height,
131                        busyBackgroundColor, busyForegroundColor);
132 
133                renderer.addTask(imageFile, listener, ImageCacheRenderThread.PRIORITY_SOON);
134            }
135        }
136        return result;
137    }
138 
139    public Dimension getDimension(File imageFile) {
140        assert !disposed;
141        Dimension result;
142 
143        try {
144            result = obtainImageDimension(imageFile);
145        } catch (Exception error) {
146            result = getDefaultImageDimension();
147            logger.warn("cannot get dimension of image " + StringTools.instance().sourced(imageFile)
148                    + ", using default: " + result, error);
149        }
150        return result;
151    }
152 
153    public int getEntryCount() {
154        assert !disposed;
155        return entries.size();
156    }
157 
158    /**
159     *  Get number of bytes the cache may fill up before entries get thrown out. Note that the cache
160     *  will hold at least 1 image, so if this is bigger than the maximum size, the actual size may
161     *  exceed the maximum in this case.
162     */
163    public long getMaxSize() {
164        assert !disposed;
165        return maxMemorySize;
166    }
167 
168    /**
169     *  Get the approximate number of bytes currently used by all images in the cache.
170     */
171    public long getUsedSize() {
172        assert !disposed;
173        return memorySize;
174    }
175 
176    protected Dimension getDefaultImageDimension() {
177        assert !disposed;
178 
179        return new Dimension(BROKEN_IMAGE_WIDTH, BROKEN_IMAGE_HEIGHT);
180    }
181 
182    String getName() {
183        return name;
184    }
185 
186    /**
187     *  Remove all entries from cache.
188     */
189    public void clear() {
190        assert !disposed;
191        synchronized (entries) {
192            entries.clear();
193            memorySize = 0;
194        }
195    }
196 
197    /**
198     *  Create a broken image using the cache's default dimension.
199     */
200    public RenderedImage createBrokenImage() {
201        assert !disposed;
202        Dimension brokenImageDimension = getDefaultImageDimension();
203        RenderedImage result = imageTools.createBrokenImage(
204                brokenImageDimension.width, brokenImageDimension.height,
205                brokenBackgroundColor, brokenForegroundColor);
206 
207        return result;
208    }
209 
210    public void dispose() {
211        assert !disposed;
212 
213        if (entries != null) {
214            if (renderer != null) {
215                renderer.dispose();
216            }
217            clear();
218        }
219        disposed = true;
220    }
221 
222    /**
223     *  Is the image for <code>imageFile</code> already stored in the cache?
224     */
225    public boolean has(File imageFile) {
226        assert !disposed;
227 
228        assert imageFile != null;
229        return entries.containsKey(imageFile);
230    }
231 
232    protected RenderedImage obtainImage(File imageFile)
233        throws IOException {
234        assert !disposed;
235        assert imageFile != null;
236        return imageTools.readImage(imageFile);
237    }
238 
239    protected Dimension obtainImageDimension(File imageFile)
240        throws IOException {
241        assert !disposed;
242        assert imageFile != null;
243        Dimension result;
244 
245        synchronized (entries) {
246            if (has(imageFile)) {
247                RenderedImage image = get(imageFile);
248 
249                result = new Dimension(image.getWidth(), image.getHeight());
250            } else {
251                result = imageTools.getImageDimension(imageFile);
252            }
253        }
254        return result;
255    }
256 
257    private ImageCacheEntry findLeastRecentlyAccessedEntry() {
258        assert !disposed;
259 
260        ImageCacheEntry result;
261 
262        synchronized (entries) {
263            Iterator rider = entries.values().iterator();
264 
265            result = (ImageCacheEntry) rider.next();
266 
267            while (rider.hasNext()) {
268                ImageCacheEntry nextEntry = (ImageCacheEntry) rider.next();
269                long leastRecentlyAccessed = result.getLastAccessed();
270                long nextAccessed = nextEntry.getLastAccessed();
271 
272                if (leastRecentlyAccessed > nextAccessed) {
273                    result = nextEntry;
274                }
275            }
276        }
277        return result;
278    }
279 
280    /**
281     *  Remove entry and update the memory size.
282     */
283    private void remove(ImageCacheEntry entryToRemove) {
284        assert !disposed;
285 
286        synchronized (entries) {
287            logger.info("removing entry: {}", entryToRemove.getImageFile());
288 
289            long entryMemorySize = entryToRemove.getImageSize();
290            Object removedEntry = entries.remove(entryToRemove.getImageFile());
291 
292            assert entryToRemove == removedEntry
293                    : "entryToRemove=" + entryToRemove + ", removedEntry=" + removedEntry;
294 
295            entryToRemove.dispose();
296            memorySize -= entryMemorySize;
297            if (logger.isInfoEnabled()) {
298                logger.info("memory size reduced by " + entryMemorySize + " bytes to " + memorySize);
299            }
300        }
301    }
302}

[all classes][net.sf.jomic.tools]
EMMA 2.0.4217 (C) Vladimir Roubtsov