001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.imagery;
003
004import java.util.concurrent.ThreadPoolExecutor;
005import java.util.concurrent.TimeUnit;
006
007import org.apache.commons.jcs.access.behavior.ICacheAccess;
008import org.openstreetmap.gui.jmapviewer.Tile;
009import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader;
010import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
011import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
012import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
013import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
014import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
015import org.openstreetmap.josm.data.cache.HostLimitQueue;
016import org.openstreetmap.josm.data.preferences.IntegerProperty;
017import org.openstreetmap.josm.tools.CheckParameterUtil;
018import org.openstreetmap.josm.tools.Utils;
019
020/**
021 * Wrapper class that bridges between JCS cache and Tile Loaders
022 *
023 * @author Wiktor Niesiobędzki
024 */
025public class TMSCachedTileLoader implements TileLoader, CachedTileLoader {
026
027    protected final ICacheAccess<String, BufferedImageCacheEntry> cache;
028    protected final TileLoaderListener listener;
029
030    /**
031     * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS
032     */
033
034    public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25);
035
036    /**
037     * Limit definition for per host concurrent connections
038     */
039    public static final IntegerProperty HOST_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobsperhost", 6);
040
041    /**
042     * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS
043     * and for TMS imagery
044     */
045    private static final ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getNewThreadPoolExecutor("TMS-downloader-%d");
046
047    private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER;
048    protected final TileJobOptions options;
049
050    /**
051     * Constructor
052     * @param listener          called when tile loading has finished
053     * @param cache             of the cache
054     * @param options           tile job options
055     */
056    public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache,
057           TileJobOptions options) {
058        CheckParameterUtil.ensureParameterNotNull(cache, "cache");
059        this.cache = cache;
060        this.options = options;
061        this.listener = listener;
062    }
063
064    /**
065     * @param nameFormat see {@link Utils#newThreadFactory(String, int)}
066     * @param workers number of worker thread to keep
067     * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue
068     */
069    public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers) {
070        return getNewThreadPoolExecutor(nameFormat, workers, HOST_LIMIT.get().intValue());
071    }
072
073    /**
074     * @param nameFormat see {@link Utils#newThreadFactory(String, int)}
075     * @param workers number of worker thread to keep
076     * @param hostLimit number of concurrent downloads per host allowed
077     * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue
078     */
079    public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers, int hostLimit) {
080        return new ThreadPoolExecutor(
081                workers, // keep core pool the same size as max, as we use unbounded queue so there will
082                workers, // be never more threads than corePoolSize
083                300, // keep alive for thread
084                TimeUnit.SECONDS,
085                new HostLimitQueue(hostLimit),
086                Utils.newThreadFactory(nameFormat, Thread.NORM_PRIORITY)
087                );
088    }
089
090    /**
091     * @param name name of threads
092     * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue, with default number of threads
093     */
094    public static ThreadPoolExecutor getNewThreadPoolExecutor(String name) {
095        return getNewThreadPoolExecutor(name, THREAD_LIMIT.get().intValue());
096    }
097
098    @Override
099    public TileJob createTileLoaderJob(Tile tile) {
100        return new TMSCachedTileLoaderJob(
101                listener,
102                tile,
103                cache,
104                options,
105                getDownloadExecutor());
106    }
107
108    @Override
109    public void clearCache(TileSource source) {
110        this.cache.remove(source.getName() + ':');
111    }
112
113    /**
114     * @return cache statistics as string
115     */
116    public String getStats() {
117        return cache.getStats();
118    }
119
120    /**
121     * cancels all outstanding tasks in the queue. This rollbacks the state of the tiles in the queue
122     * to loading = false / loaded = false
123     */
124    @Override
125    public void cancelOutstandingTasks() {
126        for (Runnable r: downloadExecutor.getQueue()) {
127            if (downloadExecutor.remove(r) && r instanceof TMSCachedTileLoaderJob) {
128                ((TMSCachedTileLoaderJob) r).handleJobCancellation();
129            }
130        }
131    }
132
133    @Override
134    public boolean hasOutstandingTasks() {
135        return downloadExecutor.getTaskCount() > downloadExecutor.getCompletedTaskCount();
136    }
137
138    /**
139     * Sets the download executor that will be used to download tiles instead of default one.
140     * You can use {@link #getNewThreadPoolExecutor} to create a new download executor with separate
141     * queue from default.
142     *
143     * @param downloadExecutor download executor that will be used to download tiles
144     */
145    public void setDownloadExecutor(ThreadPoolExecutor downloadExecutor) {
146        this.downloadExecutor = downloadExecutor;
147    }
148
149    /**
150     * @return download executor that is used by this factory
151     */
152    public ThreadPoolExecutor getDownloadExecutor() {
153        return downloadExecutor;
154    }
155}