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

COVERAGE SUMMARY FOR SOURCE FILE [ImageCacheRenderThread.java]

nameclass, %method, %block, %line, %
ImageCacheRenderThread.java100% (2/2)92%  (11/12)74%  (310/418)80%  (78/97)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ImageCacheRenderThread$ImageRenderTask100% (1/1)100% (5/5)59%  (50/85)81%  (10.5/13)
<static initializer> 100% (1/1)53%  (8/15)53%  (0.5/1)
ImageCacheRenderThread$ImageRenderTask (ImageCacheRenderThread, File, ImageIn... 100% (1/1)54%  (33/61)78%  (7/9)
getImageFile (): File 100% (1/1)100% (3/3)100% (1/1)
getListener (): ImageInCacheListener 100% (1/1)100% (3/3)100% (1/1)
getPriority (): int 100% (1/1)100% (3/3)100% (1/1)
     
class ImageCacheRenderThread100% (1/1)86%  (6/7)78%  (260/333)80%  (67.5/84)
clear (): void 0%   (0/1)0%   (0/17)0%   (0/4)
dispose (): void 100% (1/1)58%  (14/24)61%  (5.5/9)
ImageCacheRenderThread (ImageCache): void 100% (1/1)79%  (33/42)92%  (6.4/7)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
run (): void 100% (1/1)82%  (77/94)83%  (22.4/27)
addTask (File, ImageInCacheListener, int): void 100% (1/1)84%  (89/106)86%  (22.4/26)
findIndexOfExistingSpecification (File, ImageInCacheListener): int 100% (1/1)100% (35/35)100% (10/10)

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.io.File;
19import java.util.Iterator;
20import java.util.LinkedList;
21import java.util.List;
22 
23import org.apache.commons.logging.Log;
24import org.apache.commons.logging.LogFactory;
25 
26/**
27 *  Thread to render images in the background, put them in a cache and notify a listener when done.
28 *
29 * @author    Thomas Aglassinger
30 * @see       net.sf.jomic.tools.ImageInCacheListener
31 * @see       net.sf.jomic.tools.ImageCache
32 */
33class ImageCacheRenderThread extends Thread
34{
35    /**
36     *  Priority indicating an image should be rendered as soon as possible.
37     */
38    static final int PRIORITY_NOW = Thread.NORM_PRIORITY;
39 
40    /**
41     *  Priority indicating an image should be rendered when there are no other images left.
42     */
43    static final int PRIORITY_SOMETIMES = Thread.MIN_PRIORITY;
44 
45    /**
46     *  Priority indicating an image should be rendered once all urgent requests have been
47     *  processed.
48     */
49    static final int PRIORITY_SOON = Thread.NORM_PRIORITY - 1;
50 
51    /**
52     *  Tick for (ugly) polling loops.
53     */
54    private static final int CACHE_TICK = 10;
55    private static final int NO_INDEX = -1;
56 
57    private ImageCache cache;
58    private boolean disposed;
59    private Log logger;
60    private List openTasks;
61 
62    ImageCacheRenderThread(ImageCache newCache) {
63        super();
64        assert newCache != null;
65        logger = LogFactory.getLog(ImageCacheRenderThread.class);
66        openTasks = new LinkedList();
67        cache = newCache;
68        setName("renderer." + cache.getName());
69    }
70 
71    public void run() {
72        while (!disposed) {
73            ImageRenderTask specificationToRender = null;
74 
75            synchronized (openTasks) {
76                int indexOfNextSpecification = openTasks.size() - 1;
77 
78                if (indexOfNextSpecification >= 0) {
79                    // Schedule and remove the last specification in the list.
80                    specificationToRender = (ImageRenderTask)
81                            openTasks.get(indexOfNextSpecification);
82                    openTasks.remove(indexOfNextSpecification);
83                    if (logger.isInfoEnabled()) {
84                        logger.info("scheduling image to be rendered: " + specificationToRender.getImageFile());
85                    }
86                }
87            }
88            if (specificationToRender != null) {
89                File imageFile = specificationToRender.getImageFile();
90                int priority = specificationToRender.getPriority();
91                ImageInCacheListener listener = specificationToRender.getListener();
92 
93                setPriority(priority);
94                try {
95                    cache.get(imageFile);
96                } catch (Throwable error) {
97                    logger.warn("cannot render image, notifying listener anyway", error);
98                }
99                listener.imageCached(imageFile);
100            } else {
101                try {
102                    Thread.sleep(CACHE_TICK);
103                } catch (InterruptedException error) {
104                    logger.warn("sleep interrupted, continuing", error);
105                }
106            }
107        }
108    }
109 
110    /**
111     *  Schedule image to be rendered, and notify the specified listener when ready.
112     *
113     * @see                       #PRIORITY_NOW
114     * @see                       #PRIORITY_SOMETIMES
115     * @see                       #PRIORITY_SOON
116     * @param  imageFileToRender  the image to be scheduled for rendering
117     * @param  listener           ImageCacheListener to be notified once the image is rendered and
118     *      ready to be displayed
119     * @param  priority           one of: PRIORITY_*
120     */
121    void addTask(File imageFileToRender, ImageInCacheListener listener, int priority) {
122        if (logger.isInfoEnabled()) {
123            logger.info("add task for image: " + imageFileToRender);
124        }
125        if (!disposed) {
126            synchronized (openTasks) {
127                boolean addSpecification = true;
128                int indexOfExistingSpecification = findIndexOfExistingSpecification(imageFileToRender, listener);
129 
130                if (indexOfExistingSpecification != NO_INDEX) {
131                    ImageRenderTask existingSpecification = (ImageRenderTask)
132                            openTasks.get(indexOfExistingSpecification);
133 
134                    if (existingSpecification.getPriority() >= priority) {
135                        // No need to add specification, we already render this file with a higher priority
136                        addSpecification = false;
137                    } else {
138                        // The new specification has a higher priority, so remove the old one before adding the new.
139                        openTasks.remove(indexOfExistingSpecification);
140                    }
141                }
142 
143                if (addSpecification) {
144                    boolean indexFound = false;
145                    int index = 0;
146                    Iterator rider = openTasks.iterator();
147 
148                    while (rider.hasNext() && !indexFound) {
149                        ImageRenderTask specification = (ImageRenderTask) rider.next();
150 
151                        if (priority < specification.getPriority()) {
152                            indexFound = true;
153                        } else {
154                            index += 1;
155                        }
156                    }
157 
158                    ImageRenderTask newSpecification =
159                            new ImageRenderTask(imageFileToRender, listener, priority);
160 
161                    openTasks.add(index, newSpecification);
162                }
163            }
164        } else {
165            logger.warn("refusing to add task because thread is already in process of being disposed");
166        }
167    }
168 
169    /**
170     *  Remove all scheduled tasks. The task currently processed will still be finished, though.
171     */
172    void clear() {
173        synchronized (openTasks) {
174            openTasks.clear();
175        }
176    }
177 
178    void dispose() {
179        // TODO: assert that dispose is not called by ImageCacheRenderThread.
180        logger.info("disposing");
181        if (openTasks != null) {
182            disposed = true;
183            try {
184                this.join();
185            } catch (InterruptedException error) {
186                // There is nothing really bad about that, but it might cause a few
187                // AssertionErrors if a task is still processed.
188                logger.error("dipose interrupted", error);
189            }
190        } else {
191            disposed = true;
192        }
193    }
194 
195    private int findIndexOfExistingSpecification(File imageFile, ImageInCacheListener listener) {
196        int result = NO_INDEX;
197        int index = 0;
198        Iterator rider = openTasks.iterator();
199 
200        while (rider.hasNext() && (result == NO_INDEX)) {
201            ImageRenderTask specification = (ImageRenderTask) rider.next();
202 
203            if (specification.getImageFile().equals(imageFile) && (specification.listener.equals(listener))) {
204                result = index;
205            } else {
206                index += 1;
207            }
208        }
209        return result;
210    }
211 
212    /**
213     *  Specifies a task to be scheduled for the render thread.
214     *
215     * @author    Thomas Aglassinger
216     * @see       ImageCacheRenderThread
217     */
218    private class ImageRenderTask
219    {
220        private File imageFile;
221        private ImageInCacheListener listener;
222        private int priority;
223 
224        /**
225         *  Create a new task for the render thread.
226         *
227         * @param  newImageFile  the file that contains the image to be rendered
228         * @param  newListener   the listener to be notified when the image is rendered
229         * @param  newPriority   the priority to render with (one of <code>PRIORITY_xxx</code>)
230         */
231        ImageRenderTask(File newImageFile, ImageInCacheListener newListener, int newPriority) {
232            assert newImageFile != null;
233            assert newListener != null;
234            assert newPriority <= PRIORITY_NOW;
235            assert newPriority >= PRIORITY_SOMETIMES
236                    : "newPriority=" + newPriority + " must be at least PRIORITY_SOMETIMES=" + PRIORITY_SOMETIMES;
237 
238            imageFile = newImageFile;
239            listener = newListener;
240            priority = newPriority;
241        }
242 
243        File getImageFile() {
244            return imageFile;
245        }
246 
247        ImageInCacheListener getListener() {
248            return listener;
249        }
250 
251        int getPriority() {
252            return priority;
253        }
254    }
255}

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