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

COVERAGE SUMMARY FOR SOURCE FILE [ComicCache.java]

nameclass, %method, %block, %line, %
ComicCache.java100% (3/3)57%  (20/35)48%  (425/884)46%  (85.2/187)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ComicCache$TitleImageCache100% (1/1)29%  (2/7)10%  (31/316)11%  (7.3/67)
getCbrOrCbzTitleImage (FileArchive): RenderedImage 0%   (0/1)0%   (0/76)0%   (0/17)
getPdfTitleImage (File): RenderedImage 0%   (0/1)0%   (0/59)0%   (0/18)
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)85%  (23/27)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)75%  (3/4)83%  (57/69)91%  (10/11)
getDefaultImageDimension (): Dimension 0%   (0/1)0%   (0/12)0%   (0/1)
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)
obtainImageDimension (File): Dimension 100% (1/1)100% (22/22)100% (3/3)

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

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