package org.mozilla.osmdroid.tileprovider.modules; import org.mozilla.mozstumbler.service.core.logging.ClientLog; import org.mozilla.mozstumbler.svclocator.services.log.LoggerUtil; import org.mozilla.osmdroid.tileprovider.MapTile; import org.mozilla.osmdroid.tileprovider.MapTileRequestState; import org.mozilla.osmdroid.tileprovider.constants.OSMConstants; import org.mozilla.osmdroid.tileprovider.tilesource.ITileSource; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; /** * An abstract base class for modular tile providers * * @author Marc Kurtz * @author Neil Boyd */ public abstract class MapTileModuleProviderBase implements OSMConstants { private static final String LOG_TAG = LoggerUtil.makeLogTag(MapTileModuleProviderBase.class); protected final Object mQueueLockObject = new Object(); protected final HashMap<MapTile, MapTileRequestState> mWorking; protected final LinkedHashMap<MapTile, MapTileRequestState> mPending; private final ExecutorService mExecutor; public MapTileModuleProviderBase(int pThreadPoolSize, final int pPendingQueueSize) { if (pPendingQueueSize < pThreadPoolSize) { ClientLog.w(LOG_TAG, "The pending queue size is smaller than the thread pool size. Automatically reducing the thread pool size."); pThreadPoolSize = pPendingQueueSize; } mExecutor = Executors.newFixedThreadPool(pThreadPoolSize, new ConfigurablePriorityThreadFactory(Thread.NORM_PRIORITY, getThreadGroupName())); mWorking = new HashMap<MapTile, MapTileRequestState>(); mPending = new LinkedHashMap<MapTile, MapTileRequestState>(pPendingQueueSize + 2, 0.1f, true) { private static final long serialVersionUID = 6455337315681858866L; @Override protected boolean removeEldestEntry( final Map.Entry<MapTile, MapTileRequestState> pEldest) { if (size() > pPendingQueueSize) { MapTile result = null; // get the oldest tile that isn't in the mWorking queue Iterator<MapTile> iterator = mPending.keySet().iterator(); while (result == null && iterator.hasNext()) { final MapTile tile = iterator.next(); if (!mWorking.containsKey(tile)) { result = tile; } } if (result != null) { MapTileRequestState state = mPending.get(result); removeTileFromQueues(result); state.getCallback().mapTileRequestFailed(state); } } return false; } }; } /** * Gets the human-friendly name assigned to this tile provider. * * @return the thread name */ protected abstract String getName(); /** * Gets the name assigned to the thread for this provider. * * @return the thread name */ protected abstract String getThreadGroupName(); /** * It is expected that the implementation will construct an internal member which internally * implements a {@link AbstractTileLoader}. This method is expected to return a that internal member to * methods of the parent methods. * * @return the internal member of this tile provider. */ protected abstract Runnable getTileLoader(); /** * Returns true if implementation uses a data connection, false otherwise. This value is used to * determine if this provider should be skipped if there is no data connection. * * @return true if implementation uses a data connection, false otherwise */ public abstract boolean getUsesDataConnection(); /** * Gets the minimum zoom level this tile provider can provide * * @return the minimum zoom level */ public abstract int getMinimumZoomLevel(); /** * Gets the maximum zoom level this tile provider can provide * * @return the maximum zoom level */ public abstract int getMaximumZoomLevel(); /** * Sets the tile source for this tile provider. * * @param tileSource the tile source */ public abstract void setTileSource(ITileSource tileSource); public void loadMapTileAsync(final MapTileRequestState pState) { synchronized (mQueueLockObject) { if (DEBUG_TILE_PROVIDERS) { ClientLog.d(LOG_TAG, "MapTileModuleProviderBase.loadMaptileAsync() on provider: " + getName() + " for tile: " + pState.getMapTile()); if (mPending.containsKey(pState.getMapTile())) ClientLog.d(LOG_TAG, "MapTileModuleProviderBase.loadMaptileAsync() tile already exists in request queue for modular provider. Moving to front of queue."); else ClientLog.d(LOG_TAG, "MapTileModuleProviderBase.loadMaptileAsync() adding tile to request queue for modular provider."); } // this will put the tile in the queue, or move it to the front of // the queue if it's already present mPending.put(pState.getMapTile(), pState); } try { mExecutor.execute(getTileLoader()); } catch (final RejectedExecutionException e) { ClientLog.e(LOG_TAG, "RejectedExecutionException", e); } } void clearQueue() { synchronized (mQueueLockObject) { mPending.clear(); mWorking.clear(); } } /** * Detach, we're shutting down - Stops all workers. */ public void detach() { this.clearQueue(); this.mExecutor.shutdown(); } void removeTileFromQueues(final MapTile mapTile) { synchronized (mQueueLockObject) { if (DEBUG_TILE_PROVIDERS) { ClientLog.d(LOG_TAG, "MapTileModuleProviderBase.removeTileFromQueues() on provider: " + getName() + " for tile: " + mapTile); } mPending.remove(mapTile); mWorking.remove(mapTile); } } }