EMMA Coverage Report (generated Sat Oct 08 11:41:37 CEST 2011)
[all classes][net.sf.jomic.tools]

COVERAGE SUMMARY FOR SOURCE FILE [ImageCache.java]

nameclass, %method, %block, %line, %
ImageCache.java100% (1/1)89%  (16/18)72%  (559/779)77%  (102.6/134)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ImageCache100% (1/1)89%  (16/18)72%  (559/779)77%  (102.6/134)
dispose (): void 0%   (0/1)0%   (0/24)0%   (0/7)
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)
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)
obtainImage (File): RenderedImage 100% (1/1)72%  (21/29)87%  (6.1/7)
getDefaultImageDimension (): Dimension 100% (1/1)73%  (11/15)78%  (1.6/2)
remove (ImageCacheEntry): void 100% (1/1)74%  (72/97)88%  (11.4/13)
get (File, ImageInCacheListener): RenderedImage 100% (1/1)76%  (54/71)81%  (8.9/11)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
ImageCache (String, long): void 100% (1/1)82%  (76/93)92%  (15.7/17)
findLeastRecentlyAccessedEntry (): ImageCacheEntry 100% (1/1)83%  (44/53)92%  (11.9/13)
createBrokenImage (): RenderedImage 100% (1/1)85%  (22/26)89%  (3.6/4)
get (File): RenderedImage 100% (1/1)91%  (157/173)93%  (24.2/26)
getName (): String 100% (1/1)100% (3/3)100% (1/1)

1// Jomic - a viewer for comic book archives.
2// Copyright (C) 2004-2011 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.HashSet;
25import java.util.Iterator;
26import java.util.Map;
27import java.util.Set;
28 
29import javax.imageio.IIOException;
30 
31import org.apache.commons.logging.Log;
32import org.apache.commons.logging.LogFactory;
33 
34/**
35 *  Cache to store RenderedImages in memory. When the memory usage exceeds a maximum size, the least
36 *  recent images are removed. Note that the cache holds at least one image, so if this is bigger
37 *  than the maximum size, so is the whole cache.
38 */
39public class ImageCache implements CacheInfo
40{
41    private static final int BROKEN_IMAGE_HEIGHT = 800;
42    private static final int BROKEN_IMAGE_WIDTH = 600;
43    private static final int DEFAULT_BROKEN_BACKGROUND = 0xf0f0f0;
44    private static final int DEFAULT_BROKEN_FOREGROUND = 0xe04040;
45    private static final int DEFAULT_BUSY_BACKGROUND = 0xf0f0f0;
46    private static final int DEFAULT_BUSY_FOREGROUND = 0xd0d0d0;
47    private static final double ONE_HUNDRED = 100.0;
48    private Color brokenBackgroundColor;
49    private Color brokenForegroundColor;
50    private Color busyBackgroundColor;
51    private Color busyForegroundColor;
52    private boolean disposed;
53    private Map entries;
54    private ImageTools imageTools;
55    private Log logger;
56    private long maxMemorySize;
57    private long memorySize;
58    private String name;
59    private ImageCacheRenderThread renderer;
60    private Set unrenderableImageFiles;
61 
62    public ImageCache(String newName, long newMaxMemorySize) {
63        assert newName != null;
64        assert newName.length() > 0;
65        assert newMaxMemorySize >= 0;
66        logger = LogFactory.getLog(ImageCache.class);
67        imageTools = ImageTools.instance();
68        name = newName;
69        entries = new HashMap();
70        // TODO: Collect paths of unrenderable images so they won't be attempted to be rendered again.
71        unrenderableImageFiles = new HashSet();
72        maxMemorySize = newMaxMemorySize;
73        brokenBackgroundColor = new Color(DEFAULT_BROKEN_BACKGROUND);
74        brokenForegroundColor = new Color(DEFAULT_BROKEN_FOREGROUND);
75        busyBackgroundColor = new Color(DEFAULT_BUSY_BACKGROUND);
76        busyForegroundColor = new Color(DEFAULT_BUSY_FOREGROUND);
77        renderer = new ImageCacheRenderThread(this);
78        renderer.start();
79    }
80 
81    public RenderedImage get(File imageFile)
82        throws IOException {
83        assert !disposed;
84        assert imageFile != null;
85        ImageCacheEntry entry = (ImageCacheEntry) entries.get(imageFile);
86 
87        if (entry == null) {
88            RenderedImage image = obtainImage(imageFile);
89 
90            entry = new ImageCacheEntry(imageFile, image);
91 
92            long entryMemorySize = entry.getImageSize();
93 
94            memorySize += entryMemorySize;
95            if (logger.isInfoEnabled()) {
96                logger.info("memory size increased by " + entryMemorySize + " bytes to " + memorySize);
97            }
98            while ((memorySize > maxMemorySize) && (entries.size() > 0)) {
99                ImageCacheEntry leastRecentlyAccessedEntry = findLeastRecentlyAccessedEntry();
100 
101                logger.info("removing least recently used image to meet memory size requirement");
102                remove(leastRecentlyAccessedEntry);
103            }
104            entries.put(imageFile, entry);
105            if (logger.isInfoEnabled()) {
106                double percentUsed = (ONE_HUNDRED * getUsedSize() / getMaxSize());
107 
108                if (logger.isInfoEnabled()) {
109                    logger.info("entry added: " + imageFile);
110                    logger.info("current cache statistics: " + getEntryCount() + " entries use "
111                            + getUsedSize() + " of " + getMaxSize() + " bytes ("
112                            + StringTools.instance().getPercentText(percentUsed) + "%)");
113                }
114            }
115        }
116 
117        assert entry != null;
118        entry.updateLastAccessed();
119 
120        RenderedImage result = entry.getImage();
121 
122        assert result != null;
123        return result;
124    }
125 
126    public RenderedImage get(File imageFile, ImageInCacheListener listener)
127        throws IOException {
128        assert !disposed;
129        assert imageFile != null;
130        assert listener != null;
131        RenderedImage result;
132 
133        synchronized (entries) {
134            if (has(imageFile)) {
135                result = get(imageFile);
136            } else {
137                Dimension imageDimension = obtainImageDimension(imageFile);
138 
139                result = imageTools.createBusyImage(imageDimension.width, imageDimension.height,
140                        busyBackgroundColor, busyForegroundColor);
141 
142                renderer.addTask(imageFile, listener, ImageCacheRenderThread.PRIORITY_SOON);
143            }
144        }
145        return result;
146    }
147 
148    public Dimension getDimension(File imageFile) {
149        assert !disposed;
150        Dimension result;
151 
152        try {
153            result = obtainImageDimension(imageFile);
154        } catch (Exception error) {
155            result = getDefaultImageDimension();
156            logger.warn("cannot get dimension of image " + StringTools.instance().sourced(imageFile)
157                    + ", using default: " + result, error);
158        }
159        return result;
160    }
161 
162    public int getEntryCount() {
163        assert !disposed;
164        return entries.size();
165    }
166 
167    /**
168     *  Get number of bytes the cache may fill up before entries get thrown out. Note that the cache
169     *  will hold at least 1 image, so if this is bigger than the maximum size, the actual size may
170     *  exceed the maximum in this case.
171     */
172    public long getMaxSize() {
173        assert !disposed;
174        return maxMemorySize;
175    }
176 
177    /**
178     *  Get the approximate number of bytes currently used by all images in the cache.
179     */
180    public long getUsedSize() {
181        assert !disposed;
182        return memorySize;
183    }
184 
185    protected Dimension getDefaultImageDimension() {
186        assert !disposed;
187 
188        return new Dimension(BROKEN_IMAGE_WIDTH, BROKEN_IMAGE_HEIGHT);
189    }
190 
191    String getName() {
192        return name;
193    }
194 
195    /**
196     *  Remove all entries from cache.
197     */
198    public void clear() {
199        assert !disposed;
200        synchronized (entries) {
201            entries.clear();
202            memorySize = 0;
203        }
204    }
205 
206    /**
207     *  Create a broken image using the cache's default dimension.
208     */
209    public RenderedImage createBrokenImage() {
210        assert !disposed;
211        Dimension brokenImageDimension = getDefaultImageDimension();
212        RenderedImage result = imageTools.createBrokenImage(
213                brokenImageDimension.width, brokenImageDimension.height,
214                brokenBackgroundColor, brokenForegroundColor);
215 
216        return result;
217    }
218 
219    public void dispose() {
220        assert !disposed;
221 
222        if (entries != null) {
223            if (renderer != null) {
224                renderer.dispose();
225            }
226            clear();
227        }
228        disposed = true;
229    }
230 
231    /**
232     *  Is the image for <code>imageFile</code> already stored in the cache?
233     */
234    public boolean has(File imageFile) {
235        assert !disposed;
236 
237        assert imageFile != null;
238        return entries.containsKey(imageFile);
239    }
240 
241    protected RenderedImage obtainImage(File imageFile)
242        throws IOException {
243        assert !disposed;
244        assert imageFile != null;
245        RenderedImage result;
246 
247        try {
248            result = imageTools.readImage(imageFile);
249        } catch (IIOException error) {
250            result = createBrokenImage();
251        }
252        return result;
253    }
254 
255    protected Dimension obtainImageDimension(File imageFile)
256        throws IOException {
257        assert !disposed;
258        assert imageFile != null;
259        Dimension result;
260 
261        synchronized (entries) {
262            if (has(imageFile)) {
263                RenderedImage image = get(imageFile);
264 
265                result = new Dimension(image.getWidth(), image.getHeight());
266            } else {
267                result = imageTools.getImageDimension(imageFile);
268            }
269        }
270        return result;
271    }
272 
273    private ImageCacheEntry findLeastRecentlyAccessedEntry() {
274        assert !disposed;
275 
276        ImageCacheEntry result;
277 
278        synchronized (entries) {
279            Iterator rider = entries.values().iterator();
280 
281            result = (ImageCacheEntry) rider.next();
282 
283            while (rider.hasNext()) {
284                ImageCacheEntry nextEntry = (ImageCacheEntry) rider.next();
285                long leastRecentlyAccessed = result.getLastAccessed();
286                long nextAccessed = nextEntry.getLastAccessed();
287 
288                if (leastRecentlyAccessed > nextAccessed) {
289                    result = nextEntry;
290                }
291            }
292        }
293        return result;
294    }
295 
296    /**
297     *  Remove entry and update the memory size.
298     */
299    private void remove(ImageCacheEntry entryToRemove) {
300        assert !disposed;
301 
302        synchronized (entries) {
303            if (logger.isInfoEnabled()) {
304                logger.info("removing entry: " + entryToRemove.getImageFile());
305            }
306 
307            long entryMemorySize = entryToRemove.getImageSize();
308            Object removedEntry = entries.remove(entryToRemove.getImageFile());
309 
310            assert entryToRemove == removedEntry
311                    : "entryToRemove=" + entryToRemove + ", removedEntry=" + removedEntry;
312 
313            entryToRemove.dispose();
314            memorySize -= entryMemorySize;
315            if (logger.isInfoEnabled()) {
316                logger.info("memory size reduced by " + entryMemorySize + " bytes to " + memorySize);
317            }
318        }
319    }
320}

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