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}