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

COVERAGE SUMMARY FOR SOURCE FILE [ComicCache.java]

nameclass, %method, %block, %line, %
ComicCache.java100% (3/3)58%  (21/36)49%  (442/904)46%  (87.2/188)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ComicCache$TitleImageCache100% (1/1)29%  (2/7)11%  (34/308)11%  (7.3/64)
getCbrOrCbzTitleImage (FileArchive): RenderedImage 0%   (0/1)0%   (0/70)0%   (0/15)
getPdfTitleImage (File): RenderedImage 0%   (0/1)0%   (0/54)0%   (0/17)
getTitleImage (File): RenderedImage 0%   (0/1)0%   (0/65)0%   (0/11)
obtainImage (File): RenderedImage 0%   (0/1)0%   (0/40)0%   (0/7)
obtainImageDimension (File): Dimension 0%   (0/1)0%   (0/34)0%   (0/6)
<static initializer> 100% (1/1)53%  (8/15)53%  (0.5/1)
ComicCache$TitleImageCache (ComicCache, String, File, long): void 100% (1/1)87%  (26/30)96%  (6.7/7)
     
class ComicCache100% (1/1)62%  (15/24)68%  (337/499)62%  (67.9/109)
geTitleImage (File): RenderedImage 0%   (0/1)0%   (0/31)0%   (0/8)
getApplicationMemory (): long 0%   (0/1)0%   (0/5)0%   (0/2)
getAvailableMemory (): long 0%   (0/1)0%   (0/5)0%   (0/2)
getCacheDir (): File 0%   (0/1)0%   (0/5)0%   (0/2)
getImageCacheMemory (): long 0%   (0/1)0%   (0/5)0%   (0/2)
getThumbCacheMemory (): long 0%   (0/1)0%   (0/5)0%   (0/2)
getTitleCache (): ComicCache$TitleImageCache 0%   (0/1)0%   (0/5)0%   (0/2)
getTitleCacheMemory (): long 0%   (0/1)0%   (0/5)0%   (0/2)
getTitleImage (File, ImageInCacheListener): RenderedImage 0%   (0/1)0%   (0/40)0%   (0/9)
assertSetUpCalled (): void 100% (1/1)55%  (6/11)75%  (1.5/2)
getThumbnail (File, ImageInCacheListener): RenderedImage 100% (1/1)57%  (23/40)62%  (5.6/9)
getThumbnail (File): RenderedImage 100% (1/1)58%  (18/31)64%  (5.1/8)
dispose (): void 100% (1/1)77%  (17/22)78%  (7/9)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
ComicCache (): void 100% (1/1)84%  (26/31)98%  (8.8/9)
setUp (File, long): void 100% (1/1)96%  (190/198)96%  (22.1/23)
getArchiveCache (): ArchiveCache 100% (1/1)100% (5/5)100% (2/2)
getImage (File): RenderedImage 100% (1/1)100% (7/7)100% (2/2)
getImageCache (): ImageCache 100% (1/1)100% (5/5)100% (2/2)
getThumbCache (): ComicCache$ThumbImageCache 100% (1/1)100% (5/5)100% (2/2)
getThumbHeight (): int 100% (1/1)100% (5/5)100% (2/2)
getThumbWidth (): int 100% (1/1)100% (5/5)100% (2/2)
instance (): ComicCache 100% (1/1)100% (8/8)100% (3/3)
setUp (File): void 100% (1/1)100% (5/5)100% (2/2)
     
class ComicCache$ThumbImageCache100% (1/1)80%  (4/5)73%  (71/97)80%  (12/15)
getDefaultImageDimension (): Dimension 0%   (0/1)0%   (0/12)0%   (0/1)
<static initializer> 100% (1/1)53%  (8/15)53%  (0.5/1)
obtainImageDimension (File): Dimension 100% (1/1)80%  (28/35)75%  (4.5/6)
ComicCache$ThumbImageCache (ComicCache, String, long): void 100% (1/1)100% (8/8)100% (3/3)
obtainImage (File): RenderedImage 100% (1/1)100% (27/27)100% (4/4)

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.comic;
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.Arrays;
24import java.util.Comparator;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Map;
28import java.util.NoSuchElementException;
29 
30import net.sf.jomic.common.PropertyConstants;
31import net.sf.jomic.tools.ArchiveCache;
32import net.sf.jomic.tools.FileArchive;
33import net.sf.jomic.tools.FileTools;
34import net.sf.jomic.tools.ImageCache;
35import net.sf.jomic.tools.ImageInCacheListener;
36import net.sf.jomic.tools.ImageTools;
37import net.sf.jomic.tools.LocaleTools;
38import net.sf.jomic.tools.MutexLock;
39import net.sf.jomic.tools.NaturalCaseInsensitiveOrderComparator;
40import net.sf.jomic.tools.StandardConstants;
41import net.sf.jomic.tools.StringTools;
42 
43import org.apache.commons.logging.Log;
44import org.apache.commons.logging.LogFactory;
45import org.apache.pdfbox.pdmodel.PDDocument;
46import org.apache.pdfbox.pdmodel.PDPage;
47import org.apache.pdfbox.pdmodel.PDResources;
48import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
49 
50/**
51 *  Cache for comic related extracted archives, images, thumbnails.
52 *
53 * @author    Thomas Aglassinger
54 */
55public final class ComicCache implements StandardConstants
56{
57    public static final int MAX_THUMB_HEIGHT = 128;
58    public static final int MAX_THUMB_WIDTH = 160;
59    private static final long DEFAULT_MINIMUM_APPLICATION_MEMORY = 128 * MEGA_BYTE;
60    private static final long DEFAULT_MINIMUM_IMAGE_CACHE = MEGA_BYTE;
61    private static final long DEFAULT_MINIMUM_THUMB_CACHE = MEGA_BYTE;
62    private static final long DEFAULT_MINIMUM_TITLE_THUMB_CACHE = MEGA_BYTE;
63    private static final double IMAGE_CACHE_WEIGHT = 0.80;
64    private static final double THUMB_CACHE_WEIGHT = 0.15;
65    private static final double TITLE_THUMB_CACHE_WEIGHT = 0.05;
66    private static final double TOTAL_WEIGHT = IMAGE_CACHE_WEIGHT + THUMB_CACHE_WEIGHT + TITLE_THUMB_CACHE_WEIGHT;
67 
68    private static ComicCache instance;
69    private static Log logger;
70    private long applicationMemory;
71    private ArchiveCache archiveCache;
72    private File cacheDir;
73    private FileTools fileTools;
74    private ImageCache imageCache;
75    private long imageCacheMemory;
76    private ImageTools imageTools;
77    private LocaleTools localeTools;
78    private long maxMemory;
79    private boolean setUpCalled;
80    private StringTools stringTools;
81    private ThumbImageCache thumbCache;
82    private long thumbCacheMemory;
83    private int thumbHeight;
84    private int thumbWidth;
85    private TitleImageCache titleCache;
86    private long titleCacheMemory;
87 
88    private ComicCache() {
89        logger = LogFactory.getLog(ComicCache.class);
90        fileTools = FileTools.instance();
91        imageTools = ImageTools.instance();
92        localeTools = LocaleTools.instance();
93        stringTools = StringTools.instance();
94        thumbWidth = MAX_THUMB_WIDTH;
95        thumbHeight = MAX_THUMB_HEIGHT;
96    }
97 
98    /**
99     *  Setup the cache. This must be called before accessing it.
100     *
101     * @throws  IOException
102     */
103    public void setUp(File newCacheDir)
104        throws IOException {
105        setUp(newCacheDir, PropertyConstants.DEFAULT_ARCHIVE_CACHE_SIZE_IN_MB * MEGA_BYTE);
106    }
107 
108    /**
109     *  Setup the cache. This must be called before accessing it.
110     */
111    public void setUp(File newCacheDir, long newMaxArchiveCacheSize)
112        throws IOException {
113        assert newCacheDir != null;
114        assert newMaxArchiveCacheSize > 0;
115 
116        if (logger.isInfoEnabled()) {
117            logger.info("setup comic cache in " + stringTools.sourced(newCacheDir)
118                    + " with size " + newMaxArchiveCacheSize);
119        }
120        cacheDir = newCacheDir;
121        fileTools.mkdirs(cacheDir);
122 
123        File archiveCacheDir = new File(cacheDir, "archives");
124 
125        archiveCache = new ArchiveCache(archiveCacheDir, newMaxArchiveCacheSize);
126        archiveCache.attemptToReadEntries();
127 
128        maxMemory = Runtime.getRuntime().maxMemory();
129 
130        long minApplicationMemory = DEFAULT_MINIMUM_APPLICATION_MEMORY;
131        long cachableMemory = maxMemory - minApplicationMemory;
132 
133        imageCacheMemory = (long) Math.max(
134                cachableMemory * IMAGE_CACHE_WEIGHT / TOTAL_WEIGHT,
135                DEFAULT_MINIMUM_IMAGE_CACHE);
136        thumbCacheMemory = (long) Math.max(
137                cachableMemory * THUMB_CACHE_WEIGHT / TOTAL_WEIGHT,
138                DEFAULT_MINIMUM_THUMB_CACHE);
139        titleCacheMemory = (long) Math.max(
140                cachableMemory * TITLE_THUMB_CACHE_WEIGHT / TOTAL_WEIGHT,
141                DEFAULT_MINIMUM_TITLE_THUMB_CACHE);
142        applicationMemory = maxMemory - imageCacheMemory - thumbCacheMemory - titleCacheMemory;
143 
144        if (logger.isInfoEnabled()) {
145            logger.info("cache size in MB: maximum memory=" + localeTools.asByteText(maxMemory)
146                    + ", image cache=" + localeTools.asByteText(imageCacheMemory)
147                    + ", title cache=" + localeTools.asByteText(titleCacheMemory)
148                    + ", thumb cache=" + localeTools.asByteText(thumbCacheMemory));
149        }
150        imageCache = new ImageCache("images", imageCacheMemory);
151        thumbCache = new ThumbImageCache("thumbs", thumbCacheMemory);
152        titleCache = new TitleImageCache("titles", new File(cacheDir, "titles"), titleCacheMemory);
153 
154        setUpCalled = true;
155    }
156 
157    /**
158     *  Get thumbnail for title image of <code>comicFile</code>. If there is no such thumbnail in
159     *  the cache, compute a new one and put it in the cache.
160     */
161    public RenderedImage geTitleImage(File comicFile) {
162        assertSetUpCalled();
163        assert comicFile != null;
164        RenderedImage result;
165 
166        try {
167            result = titleCache.get(comicFile);
168        } catch (IOException error) {
169            result = titleCache.createBrokenImage();
170        }
171 
172        assert result != null;
173        return result;
174    }
175 
176    /**
177     *  Approximate number of bytes available for application and unavailable for caches.
178     */
179    public long getApplicationMemory() {
180        assertSetUpCalled();
181        return applicationMemory;
182    }
183 
184    public ArchiveCache getArchiveCache() {
185        assertSetUpCalled();
186        return archiveCache;
187    }
188 
189    /**
190     *  Total memory available to application and caches.
191     */
192    public long getAvailableMemory() {
193        assertSetUpCalled();
194        return maxMemory;
195    }
196 
197    public File getCacheDir() {
198        assertSetUpCalled();
199        return cacheDir;
200    }
201 
202    public RenderedImage getImage(File imageFile)
203        throws IOException {
204        assertSetUpCalled();
205        return imageCache.get(imageFile);
206    }
207 
208    /**
209     * @return    Returns the imageCache.
210     */
211    public ImageCache getImageCache() {
212        assertSetUpCalled();
213        return imageCache;
214    }
215 
216    /**
217     *  Approximate number of bytes available for image cache.
218     */
219    public long getImageCacheMemory() {
220        assertSetUpCalled();
221        return imageCacheMemory;
222    }
223 
224    /**
225     * @return    Returns the thumbCache.
226     */
227    public ThumbImageCache getThumbCache() {
228        assertSetUpCalled();
229        return thumbCache;
230    }
231 
232    /**
233     *  Approximate number of bytes available for thumbnail cache.
234     */
235    public long getThumbCacheMemory() {
236        assertSetUpCalled();
237        return thumbCacheMemory;
238    }
239 
240    public int getThumbHeight() {
241        assertSetUpCalled();
242        return thumbHeight;
243    }
244 
245    public int getThumbWidth() {
246        assertSetUpCalled();
247        return thumbWidth;
248    }
249 
250    /**
251     *  Get thumbnail for <code>imageFile</code>. If there is no such thumbnail in the cache,
252     *  compute a new one and put it in the cache.
253     */
254    public RenderedImage getThumbnail(File imageFile) {
255        assertSetUpCalled();
256        assert imageFile != null;
257        RenderedImage result;
258 
259        try {
260            result = thumbCache.get(imageFile);
261        } catch (IOException error) {
262            result = thumbCache.createBrokenImage();
263        }
264 
265        assert result != null;
266        return result;
267    }
268 
269    /**
270     *  Get thumbnail for <code>imageFile</code>. If there is no such thumbnail in the cache, return
271     *  a placeholder image immediately, and notify <code>listener</code> when the actual thumbnail
272     *  is ready.
273     */
274    public RenderedImage getThumbnail(File imageFile, ImageInCacheListener listener) {
275        assertSetUpCalled();
276        assert imageFile != null;
277        assert listener != null;
278        RenderedImage result;
279 
280        try {
281            result = thumbCache.get(imageFile, listener);
282        } catch (IOException error) {
283            result = thumbCache.createBrokenImage();
284        }
285 
286        assert result != null;
287        return result;
288    }
289 
290    public TitleImageCache getTitleCache() {
291        assertSetUpCalled();
292        return titleCache;
293    }
294 
295    /**
296     *  Approximate number of bytes available for title thumbnail cache.
297     */
298    public long getTitleCacheMemory() {
299        assertSetUpCalled();
300        return titleCacheMemory;
301    }
302 
303    /**
304     *  Get thumbnail for title image for <code>comicFile</code>. If there is no such thumbnail in
305     *  the cache, return a placeholder image immediately, and notify <code>listener</code> when the
306     *  actual thumbnail is ready.
307     */
308    public RenderedImage getTitleImage(File comicFile, ImageInCacheListener listener) {
309        assertSetUpCalled();
310        assert comicFile != null;
311        assert listener != null;
312        RenderedImage result;
313 
314        try {
315            result = titleCache.get(comicFile, listener);
316        } catch (IOException error) {
317            result = titleCache.createBrokenImage();
318        }
319 
320        assert result != null;
321        return result;
322    }
323 
324    public static synchronized ComicCache instance() {
325        if (instance == null) {
326            instance = new ComicCache();
327        }
328        return instance;
329    }
330 
331    public void dispose() {
332        if (imageCache != null) {
333            logger.info("clear image cache");
334            imageCache.clear();
335        }
336        if (setUpCalled) {
337            try {
338                getArchiveCache().writeEntries();
339            } catch (IOException error) {
340                logger.warn("cannot store archive cache map, ignoring error", error);
341            }
342        }
343    }
344 
345    private void assertSetUpCalled() {
346        assert setUpCalled : "setUp() must be called first";
347    }
348 
349    /**
350     *  ImageCache for comic title pages.
351     *
352     * @author    Thomas Aglassinger
353     */
354    public class TitleImageCache extends ImageCache
355    {
356        private File lastObtainedComicFile;
357        private Dimension lastObtainedTitleDimension;
358        private RenderedImage lastObtainedTitleImage;
359        private Comparator naturalOrderComparator;
360        private MutexLock obtainImageLock;
361        private File titleCacheDir;
362 
363        public TitleImageCache(String newName, File newTitleCacheDir, long newMaxMemorySize) {
364            super(newName, newMaxMemorySize);
365            assert newTitleCacheDir != null;
366 
367            titleCacheDir = newTitleCacheDir;
368            naturalOrderComparator = new NaturalCaseInsensitiveOrderComparator();
369            obtainImageLock = new MutexLock("obtainImage");
370        }
371 
372        private RenderedImage getCbrOrCbzTitleImage(FileArchive comicArchive)
373            throws IOException {
374            RenderedImage result;
375 
376            String[] names = comicArchive.list();
377            String titleImageName = null;
378 
379            Arrays.sort(names, naturalOrderComparator);
380 
381            for (int nameIndex = 0; (nameIndex < names.length)
382                    && (titleImageName == null); nameIndex += 1) {
383                String possibleImageName = names[nameIndex];
384 
385                if (imageTools.isImageFile(possibleImageName)) {
386                    titleImageName = possibleImageName;
387                }
388            }
389            if (titleImageName == null) {
390                throw new NoSuchElementException(
391                        "archive must contain at least one image");
392            }
393            comicArchive.extract(titleCacheDir,
394                    new String[]{titleImageName});
395 
396            File extractedTitleImageFile = new File(titleCacheDir,
397                    titleImageName);
398 
399            result = imageTools.readImage(extractedTitleImageFile);
400            fileTools.deleteOrWarn(extractedTitleImageFile, logger);
401            return result;
402        }
403 
404        private RenderedImage getPdfTitleImage(File comicFile)
405            throws IOException {
406            RenderedImage result = null;
407            PDDocument pdf = PDDocument.load(comicFile);
408 
409            try {
410                List pages = pdf.getDocumentCatalog().getAllPages();
411                Iterator pageRider = pages.iterator();
412 
413                while (pageRider.hasNext() && (result == null)) {
414                    PDPage page = (PDPage) pageRider.next();
415                    PDResources resources = page.getResources();
416                    Map images = resources.getImages();
417 
418                    if (images != null) {
419                        Iterator imageRider = images.values().iterator();
420 
421                        if (imageRider.hasNext()) {
422                            PDXObjectImage image = (PDXObjectImage) imageRider.next();
423 
424                            result = image.getRGBImage();
425                        }
426                    }
427                }
428            } finally {
429                pdf.close();
430            }
431            return result;
432        }
433 
434        /**
435         *  Extract title image from <code>comicFile</code> and return a thumbnail version of it.
436         */
437        private RenderedImage getTitleImage(File comicFile) {
438            RenderedImage result;
439 
440            try {
441                FileArchive comicArchive = new FileArchive(comicFile);
442 
443                if (comicArchive.getFileType() == FileTools.FORMAT_PDF) {
444                    result = getPdfTitleImage(comicFile);
445                } else {
446                    result = getCbrOrCbzTitleImage(comicArchive);
447                }
448                result = imageTools.getSqueezed(result, getThumbWidth(),
449                        getThumbHeight(), ImageTools.SCALE_FIT);
450                // HACK: Prevent OutOfMemoryError by avoiding RenderedImage,
451                // which seems to keep some evil references in the background.
452                result = imageTools.getAsBufferedImage(result);
453            } catch (Exception error) {
454                logger.warn("cannot get title image, using default image instead for: " + comicFile,
455                        error);
456                result = imageTools.createBrokenImage(lastObtainedTitleDimension.width,
457                        lastObtainedTitleDimension.height, Color.WHITE, Color.RED);
458            }
459            return result;
460        }
461 
462        protected RenderedImage obtainImage(File comicFile)
463            throws IOException {
464            if (!comicFile.equals(lastObtainedComicFile)) {
465                synchronized (obtainImageLock) {
466                    lastObtainedTitleImage = getTitleImage(comicFile);
467                    lastObtainedComicFile = comicFile;
468                    lastObtainedTitleDimension = new Dimension(
469                            lastObtainedTitleImage.getWidth(), lastObtainedTitleImage.getHeight());
470                }
471            }
472            return lastObtainedTitleImage;
473        }
474 
475        protected Dimension obtainImageDimension(File comicFile)
476            throws IOException {
477            Dimension result;
478 
479            synchronized (obtainImageLock) {
480                if (lastObtainedTitleDimension == null) {
481                    lastObtainedTitleDimension = new Dimension(getThumbWidth() / 2, getThumbHeight());
482                }
483                result = lastObtainedTitleDimension;
484            }
485            return result;
486        }
487    }
488 
489    /**
490     *  Cache for thumbnails for comic images. The thumbnail image is derived by scaling the
491     *  original comic image to a defined thumbnail size.
492     *
493     * @author    Thomas Aglassinger
494     */
495    class ThumbImageCache extends ImageCache
496    {
497        ThumbImageCache(String newName, long newMaxMemorySize) {
498            super(newName, newMaxMemorySize);
499        }
500 
501        protected Dimension getDefaultImageDimension() {
502            return new Dimension(getThumbWidth() / 2, getThumbHeight());
503        }
504 
505        protected RenderedImage obtainImage(File imageFile)
506            throws IOException {
507            RenderedImage thumbImage;
508            RenderedImage originalImage = imageCache.get(imageFile);
509 
510            thumbImage = imageTools.getSqueezed(originalImage, getThumbWidth(),
511                    getThumbHeight(), ImageTools.SCALE_FIT);
512            thumbImage = imageTools.getAsBufferedImage(thumbImage);
513            return thumbImage;
514        }
515 
516        protected Dimension obtainImageDimension(File imageFile)
517            throws IOException {
518            Dimension result;
519            Dimension sourceDimension = super.obtainImageDimension(imageFile);
520 
521            if (sourceDimension == null) {
522                sourceDimension = super.getDefaultImageDimension();
523            }
524 
525            assert sourceDimension != null;
526 
527            result = imageTools.getSqueezedDimension(getThumbWidth(), getThumbHeight(),
528                    sourceDimension.width, sourceDimension.height, ImageTools.SCALE_FIT);
529            return result;
530        }
531    }
532}

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