001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import java.io.IOException;
005import java.util.Map;
006import java.util.concurrent.ConcurrentHashMap;
007
008import org.apache.commons.jcs.access.CacheAccess;
009import org.apache.commons.jcs.access.behavior.ICacheAccess;
010import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
011import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
012import org.openstreetmap.josm.Main;
013import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
014import org.openstreetmap.josm.data.cache.JCSCacheManager;
015import org.openstreetmap.josm.data.imagery.CachedTileLoaderFactory;
016import org.openstreetmap.josm.data.imagery.ImageryInfo;
017import org.openstreetmap.josm.data.imagery.TileLoaderFactory;
018import org.openstreetmap.josm.data.preferences.IntegerProperty;
019
020/**
021 *
022 * Class providing cache to other layers
023 *
024 * @author Wiktor Niesiobędzki
025 * @param <T> Tile Source class used by this Imagery Layer
026 *
027 */
028public abstract class AbstractCachedTileSourceLayer<T extends AbstractTMSTileSource> extends AbstractTileSourceLayer<T> {
029    /** loader factory responsible for loading tiles for all layers */
030    private static Map<String, TileLoaderFactory> loaderFactories = new ConcurrentHashMap<>();
031
032    private static final String PREFERENCE_PREFIX = "imagery.cache.";
033
034    private static volatile TileLoaderFactory loaderFactoryOverride;
035
036    /**
037     * how many object on disk should be stored for TMS region in MB. 500 MB is default value
038     */
039    public static final IntegerProperty MAX_DISK_CACHE_SIZE = new IntegerProperty(PREFERENCE_PREFIX + "max_disk_size", 512);
040
041    private ICacheAccess<String, BufferedImageCacheEntry> cache;
042    private volatile TileLoaderFactory loaderFactory;
043
044
045    /**
046     * Creates an instance of class based on InageryInfo
047     *
048     * @param info ImageryInfo describing the layer
049     */
050    public AbstractCachedTileSourceLayer(ImageryInfo info) {
051        super(info);
052
053        if (loaderFactoryOverride != null) {
054            loaderFactory = loaderFactoryOverride;
055        } else {
056            String key = this.getClass().getCanonicalName();
057            loaderFactory = loaderFactories.get(key);
058            if (loaderFactory == null) {
059                synchronized (AbstractCachedTileSourceLayer.class) {
060                    // check again, maybe another thread initialized factory
061                    loaderFactory = loaderFactories.get(key);
062                    if (loaderFactory == null) {
063                        loaderFactory = new CachedTileLoaderFactory(getCache(), getTileLoaderClass());
064                        loaderFactories.put(key, loaderFactory);
065                    }
066                }
067            }
068        }
069    }
070
071    @Override
072    protected synchronized TileLoaderFactory getTileLoaderFactory() {
073        if (loaderFactory == null) {
074            loaderFactory = new CachedTileLoaderFactory(getCache(), getTileLoaderClass());
075        }
076        return loaderFactory;
077    }
078
079    /**
080     * @return cache used by this layer
081     */
082    private synchronized ICacheAccess<String, BufferedImageCacheEntry> getCache() {
083        if (cache != null) {
084            return cache;
085        }
086        try {
087            cache = JCSCacheManager.getCache(getCacheName(),
088                    0,
089                    getDiskCacheSize(),
090                    CachedTileLoaderFactory.PROP_TILECACHE_DIR.get());
091            return cache;
092        } catch (IOException e) {
093            Main.warn(e);
094            return null;
095        }
096    }
097
098
099    /**
100     * Plugins that wish to set custom tile loader should call this method
101     * @param newLoaderFactory that will be used to load tiles
102     */
103
104    public static synchronized void setTileLoaderFactory(TileLoaderFactory newLoaderFactory) {
105        loaderFactoryOverride = newLoaderFactory;
106    }
107
108    /**
109     * Returns tile loader factory for cache region and specified TileLoader class
110     * @param name of the cache region
111     * @param klazz type of the TileLoader
112     * @return factory returning cached tile loaders using specified cache and TileLoaders
113     */
114    public static TileLoaderFactory getTileLoaderFactory(String name, Class<? extends TileLoader> klazz) {
115        return new CachedTileLoaderFactory(getCache(name), klazz);
116    }
117
118    /**
119     * @param name of cache region
120     * @return cache configured object for specified cache region
121     */
122    public static CacheAccess<String, BufferedImageCacheEntry> getCache(String name) {
123            try {
124                return JCSCacheManager.getCache(name,
125                        0,
126                        MAX_DISK_CACHE_SIZE.get() * 1024, // MAX_DISK_CACHE_SIZE is in MB, needs to by in sync with getDiskCacheSize
127                        CachedTileLoaderFactory.PROP_TILECACHE_DIR.get());
128            } catch (IOException e) {
129                Main.warn(e);
130                return null;
131            }
132    }
133
134    protected abstract Class<? extends TileLoader> getTileLoaderClass();
135
136    protected int getDiskCacheSize() {
137        return MAX_DISK_CACHE_SIZE.get() * 1024;
138    }
139
140    protected abstract String getCacheName();
141}