Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java	(revision 9846)
@@ -16,4 +16,5 @@
 import javax.swing.JPanel;
 
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 
@@ -34,5 +35,5 @@
 		final JMapViewer map = new JMapViewer();
 		// final JMapViewer map = new JMapViewer(new MemoryTileCache(),4);
-		map.setTileLoader(new OsmFileCacheTileLoader(map));
+		// map.setTileLoader(new OsmFileCacheTileLoader(map));
 		// new DefaultMapController(map);
 		setLayout(new BorderLayout());
@@ -55,5 +56,5 @@
 		});
 		JComboBox tileSourceSelector =
-				new JComboBox(new Object[] { new OsmTileSource.Mapnik(),
+				new JComboBox(new TileSource[] { new OsmTileSource.Mapnik(),
 						new OsmTileSource.TilesAtHome(), new OsmTileSource.CycleMap() });
 		tileSourceSelector.addItemListener(new ItemListener() {
@@ -62,5 +63,15 @@
 			}
 		});
+		JComboBox tileLoaderSelector =
+				new JComboBox(new TileLoader[] { new OsmFileCacheTileLoader(map),
+						new OsmTileLoader(map) });
+		tileLoaderSelector.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				map.setTileLoader((TileLoader) e.getItem());
+			}
+		});
+		map.setTileLoader((TileLoader) tileLoaderSelector.getSelectedItem());
 		panel.add(tileSourceSelector);
+		panel.add(tileLoaderSelector);
 		final JCheckBox showMapMarker = new JCheckBox("Map markers visible");
 		showMapMarker.setSelected(map.getMapMarkersVisible());
@@ -108,7 +119,7 @@
 	 */
 	public static void main(String[] args) {
-		// Properties systemProperties = System.getProperties();
-		// systemProperties.setProperty("http.proxyHost","localhost");
-		// systemProperties.setProperty("http.proxyPort","8008");
+		// java.util.Properties systemProperties = System.getProperties();
+		// systemProperties.setProperty("http.proxyHost", "localhost");
+		// systemProperties.setProperty("http.proxyPort", "8008");
 		new Demo().setVisible(true);
 	}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9846)
@@ -26,6 +26,7 @@
 import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
 
 /**
@@ -37,5 +38,5 @@
  * 
  */
-public class JMapViewer extends JPanel {
+public class JMapViewer extends JPanel implements TileLoaderListener {
 
 	private static final long serialVersionUID = 1L;
@@ -97,5 +98,5 @@
 		tileLoader = new OsmTileLoader(this);
 		this.tileCache = tileCache;
-		jobDispatcher = new JobDispatcher(downloadThreadCount);
+		jobDispatcher = JobDispatcher.getInstance();
 		mapMarkerList = new LinkedList<MapMarker>();
 		mapMarkersVisible = true;
@@ -367,5 +368,5 @@
 		int mapSize = Tile.SIZE << zoom;
 		g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
-		
+
 		// g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
 		if (!mapMarkersVisible || mapMarkerList == null)
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 9846)
@@ -5,10 +5,12 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.Job;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A generic class that processes a list of {@link Runnable} one-by-one using
- * one or more {@link Thread}-instances.
+ * one or more {@link Thread}-instances. The number of instances varies between
+ * 1 and {@link #WORKER_THREAD_MAX_COUNT} (default: 8). If an instance is idle
+ * more than {@link #WORKER_THREAD_TIMEOUT} seconds (default: 30), the instance
+ * ends itself.
  * 
  * @author Jan Peter Stotz
@@ -16,30 +18,51 @@
 public class JobDispatcher {
 
+	private static JobDispatcher instance;
+
+	/**
+	 * @return the singelton instance of the {@link JobDispatcher}
+	 */
+	public static JobDispatcher getInstance() {
+		if (instance == null)
+			instance = new JobDispatcher();
+		return instance;
+	}
+
+	private JobDispatcher() {
+		addWorkerThread().firstThread = true;
+	}
+
 	protected BlockingQueue<Runnable> jobQueue = new LinkedBlockingQueue<Runnable>();
 
-	JobThread[] threads;
-
-	public JobDispatcher(int threadCound) {
-		threads = new JobThread[threadCound];
-		for (int i = 0; i < threadCound; i++) {
-			threads[i] = new JobThread(i + 1);
-		}
-	}
+	public static int WORKER_THREAD_MAX_COUNT = 8;
 
 	/**
-	 * Removes all jobs from the queue that are currently not being processed
-	 * and stops those currently being processed.
+	 * Specifies the time span in seconds that a worker thread waits for new
+	 * jobs to perform. If the time span has elapsed the worker thread
+	 * terminates itself. Only the first worker thread works differently, it
+	 * ignores the timeout and will never terminate itself.
+	 */
+	public static int WORKER_THREAD_TIMEOUT = 30;
+
+	/**
+	 * Total number of worker threads currently idle or active
+	 */
+	protected int workerThreadCount = 0;
+
+	/**
+	 * Number of worker threads currently idle
+	 */
+	protected int workerThreadIdleCount = 0;
+
+	/**
+	 * Just an id for identifying an worker thread instance
+	 */
+	protected int workerThreadId = 0;
+
+	/**
+	 * Removes all jobs from the queue that are currently not being processed.
 	 */
 	public void cancelOutstandingJobs() {
 		jobQueue.clear();
-		for (int i = 0; i < threads.length; i++) {
-			try {
-				Runnable job = threads[i].getJob();
-				if ((job != null) && (job instanceof Job))
-					((Job) job).stop();
-			} catch (Exception e) {
-				e.printStackTrace();
-			}
-		}
 	}
 
@@ -47,6 +70,16 @@
 		try {
 			jobQueue.put(job);
+			if (workerThreadIdleCount == 0 && workerThreadCount < WORKER_THREAD_MAX_COUNT)
+				addWorkerThread();
 		} catch (InterruptedException e) {
 		}
+	}
+
+	protected JobThread addWorkerThread() {
+		JobThread jobThread = new JobThread(++workerThreadId);
+		synchronized (this) {
+			workerThreadCount++;
+		}
+		return jobThread;
 	}
 
@@ -54,4 +87,5 @@
 
 		Runnable job;
+		boolean firstThread = false;
 
 		public JobThread(int threadId) {
@@ -64,10 +98,29 @@
 		@Override
 		public void run() {
+			executeJobs();
+			synchronized (instance) {
+				workerThreadCount--;
+			}
+		}
+
+		protected void executeJobs() {
 			while (!isInterrupted()) {
 				try {
-					job = jobQueue.take();
+					synchronized (instance) {
+						workerThreadIdleCount++;
+					}
+					if (firstThread)
+						job = jobQueue.take();
+					else
+						job = jobQueue.poll(WORKER_THREAD_TIMEOUT, TimeUnit.SECONDS);
 				} catch (InterruptedException e1) {
 					return;
+				} finally {
+					synchronized (instance) {
+						workerThreadIdleCount--;
+					}
 				}
+				if (job == null)
+					return;
 				try {
 					job.run();
@@ -78,13 +131,4 @@
 			}
 		}
-
-		/**
-		 * @return the job being executed at the moment or <code>null</code> if
-		 *         the thread is idle.
-		 */
-		public Runnable getJob() {
-			return job;
-		}
-
 	}
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 9846)
@@ -13,9 +13,11 @@
 import java.net.URL;
 import java.net.URLConnection;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.Job;
+import java.nio.charset.Charset;
+
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource.TileUpdate;
 
 /**
@@ -28,5 +30,8 @@
 public class OsmFileCacheTileLoader extends OsmTileLoader {
 
-	private static final String FILE_EXT = ".png";
+	private static final String TILE_FILE_EXT = ".png";
+	private static final String ETAG_FILE_EXT = ".etag";
+
+	private static final Charset ETAG_CHARSET = Charset.forName("UTF-8");
 
 	public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
@@ -35,7 +40,8 @@
 	protected String cacheDirBase;
 
-	protected long maxFileAge = FILE_AGE_ONE_WEEK;
-
-	public OsmFileCacheTileLoader(JMapViewer map) {
+	protected long maxCacheFileAge = FILE_AGE_ONE_WEEK;
+	protected long recheckAfter = FILE_AGE_ONE_DAY;
+
+	public OsmFileCacheTileLoader(TileLoaderListener map) {
 		super(map);
 		String tempDir = System.getProperty("java.io.tmpdir");
@@ -53,15 +59,19 @@
 	}
 
-	public Job createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
+	public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
 			final int zoom) {
 		return new FileLoadJob(source, tilex, tiley, zoom);
 	}
 
-	protected class FileLoadJob implements Job {
+	protected class FileLoadJob implements Runnable {
 		InputStream input = null;
 
 		int tilex, tiley, zoom;
+		Tile tile;
 		TileSource source;
 		File tileCacheDir;
+		File tileFile = null;
+		long fileAge = 0;
+		boolean fileTilePainted = false;
 
 		public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
@@ -74,6 +84,5 @@
 
 		public void run() {
-			TileCache cache = map.getTileCache();
-			Tile tile;
+			TileCache cache = listener.getTileCache();
 			synchronized (cache) {
 				tile = cache.getTile(source, tilex, tiley, zoom);
@@ -85,55 +94,83 @@
 			if (!tileCacheDir.exists())
 				tileCacheDir.mkdirs();
-			try {
-				long fileAge = 0;
-				FileInputStream fin = null;
-				File f = null;
-				try {
-					f = getTileFile(tile);
-					fin = new FileInputStream(f);
-					tile.loadImage(fin);
-					fin.close();
-					fileAge = f.lastModified();
-					boolean oldTile = System.currentTimeMillis() - fileAge > maxFileAge;
-					System.out.println("Loaded from file: " + tile);
-					if (!oldTile) {
-						tile.setLoaded(true);
-						map.repaint();
-						return;
+			if (loadTileFromFile())
+				return;
+			if (fileTilePainted) {
+				Runnable job = new Runnable() {
+
+					public void run() {
+						loadorUpdateTile();
 					}
-					// System.out.println("Cache hit for " + tile +
-					// " but file age is high: "
-					// + new Date(fileAge));
-					map.repaint();
-					// if (!isOsmTileNewer(tile, fileAge)) {
-					// tile.setLoaded(true);
-					// return;
-					// }
-				} catch (Exception e) {
-					try {
-						if (fin != null) {
-							fin.close();
-							f.delete();
-						}
-					} catch (Exception e1) {
-					}
-				}
-				// Thread.sleep(500);
+				};
+				JobDispatcher.getInstance().addJob(job);
+			} else {
+				loadorUpdateTile();
+			}
+		}
+
+		protected void loadorUpdateTile() {
+
+			try {
 				// System.out.println("Loading tile from OSM: " + tile);
 				HttpURLConnection urlConn = loadTileFromOsm(tile);
-				// if (fileAge > 0)
-				// urlConn.setIfModifiedSince(fileAge);
-				//
-				// if (urlConn.getResponseCode() == 304) {
-				// System.out.println("Local version is up to date");
-				// tile.setLoaded(true);
-				// return;
-				// }
+				if (tileFile != null) {
+					switch (source.getTileUpdate()) {
+					case IfModifiedSince:
+						urlConn.setIfModifiedSince(fileAge);
+						break;
+					case LastModified:
+						if (!isOsmTileNewer(fileAge)) {
+							System.out
+									.println("LastModified: Local version is up to date: " + tile);
+							tile.setLoaded(true);
+							tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge
+									+ recheckAfter);
+							return;
+						}
+						break;
+					}
+				}
+				if (source.getTileUpdate() == TileUpdate.ETag
+						|| source.getTileUpdate() == TileUpdate.IfNoneMatch) {
+					if (tileFile != null) {
+						String fileETag = loadETagfromFile();
+						if (fileETag != null) {
+							switch (source.getTileUpdate()) {
+							case IfNoneMatch:
+								urlConn.addRequestProperty("If-None-Match", fileETag);
+								break;
+							case ETag:
+								if (hasOsmTileETag(fileETag)) {
+									tile.setLoaded(true);
+									tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge
+											+ recheckAfter);
+									return;
+								}
+							}
+						}
+					}
+
+					String eTag = urlConn.getHeaderField("ETag");
+					saveETagToFile(eTag);
+				}
+				if (urlConn.getResponseCode() == 304) {
+					// If we are isModifiedSince or If-None-Match has been set
+					// and the server answers with a HTTP 304 = "Not Modified"
+					System.out.println("Local version is up to date: " + tile);
+					tile.setLoaded(true);
+					tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge
+							+ recheckAfter);
+					return;
+				}
+
 				byte[] buffer = loadTileInBuffer(urlConn);
-				tile.loadImage(new ByteArrayInputStream(buffer));
-				tile.setLoaded(true);
-				map.repaint();
-				input = null;
-				saveTileToFile(tile, buffer);
+				if (buffer != null) {
+					tile.loadImage(new ByteArrayInputStream(buffer));
+					tile.setLoaded(true);
+					listener.repaint();
+					saveTileToFile(buffer);
+				} else {
+					tile.setLoaded(true);
+				}
 			} catch (Exception e) {
 				if (input == null /* || !input.isStopped() */)
@@ -143,4 +180,35 @@
 				tile.loading = false;
 			}
+		}
+
+		protected boolean loadTileFromFile() {
+			FileInputStream fin = null;
+			try {
+				tileFile = getTileFile();
+				fin = new FileInputStream(tileFile);
+				if (fin.available() == 0)
+					throw new IOException("File empty");
+				tile.loadImage(fin);
+				fin.close();
+				fileAge = tileFile.lastModified();
+				boolean oldTile = System.currentTimeMillis() - fileAge > maxCacheFileAge;
+				// System.out.println("Loaded from file: " + tile);
+				if (!oldTile) {
+					tile.setLoaded(true);
+				}
+				listener.repaint();
+				fileTilePainted = true;
+			} catch (Exception e) {
+				try {
+					if (fin != null) {
+						fin.close();
+						tileFile.delete();
+					}
+				} catch (Exception e1) {
+				}
+				tileFile = null;
+				fileAge = 0;
+			}
+			return false;
 		}
 
@@ -157,4 +225,6 @@
 					finished = true;
 			} while (!finished);
+			if (bout.size() == 0)
+				return null;
 			return bout.toByteArray();
 		}
@@ -171,5 +241,4 @@
 		 * </ul>
 		 * 
-		 * @param tile
 		 * @param fileAge
 		 * @return <code>true</code> if the tile on the server is newer than the
@@ -177,10 +246,10 @@
 		 * @throws IOException
 		 */
-		protected boolean isOsmTileNewer(Tile tile, long fileAge) throws IOException {
+		protected boolean isOsmTileNewer(long fileAge) throws IOException {
 			URL url;
 			url = new URL(tile.getUrl());
 			HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
 			urlConn.setRequestMethod("HEAD");
-			urlConn.setReadTimeout(30000); // 30 seconds read
+			urlConn.setReadTimeout(30000); // 30 seconds read timeout
 			// System.out.println("Tile age: " + new
 			// Date(urlConn.getLastModified()) + " / "
@@ -188,18 +257,33 @@
 			long lastModified = urlConn.getLastModified();
 			if (lastModified == 0)
+				return true; // no LastModified time returned
+			return (lastModified > fileAge);
+		}
+
+		protected boolean hasOsmTileETag(String eTag) throws IOException {
+			URL url;
+			url = new URL(tile.getUrl());
+			HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+			urlConn.setRequestMethod("HEAD");
+			urlConn.setReadTimeout(30000); // 30 seconds read timeout
+			// System.out.println("Tile age: " + new
+			// Date(urlConn.getLastModified()) + " / "
+			// + new Date(fileAge));
+			String osmETag = urlConn.getHeaderField("ETag");
+			if (osmETag == null)
 				return true;
-			return (lastModified > fileAge);
-		}
-
-		protected File getTileFile(Tile tile) throws IOException {
+			return (osmETag.equals(eTag));
+		}
+
+		protected File getTileFile() throws IOException {
 			return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_"
-					+ tile.getYtile() + FILE_EXT);
-		}
-
-		protected void saveTileToFile(Tile tile, byte[] rawData) {
+					+ tile.getYtile() + TILE_FILE_EXT);
+		}
+
+		protected void saveTileToFile(byte[] rawData) {
 			try {
 				FileOutputStream f =
 						new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_"
-								+ tile.getXtile() + "_" + tile.getYtile() + FILE_EXT);
+								+ tile.getXtile() + "_" + tile.getYtile() + TILE_FILE_EXT);
 				f.write(rawData);
 				f.close();
@@ -210,14 +294,42 @@
 		}
 
-		public void stop() {
-		}
+		protected void saveETagToFile(String eTag) {
+			try {
+				FileOutputStream f =
+						new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_"
+								+ tile.getXtile() + "_" + tile.getYtile() + ETAG_FILE_EXT);
+				f.write(eTag.getBytes(ETAG_CHARSET));
+				f.close();
+			} catch (Exception e) {
+				System.err.println("Failed to save ETag: " + e.getLocalizedMessage());
+			}
+		}
+
+		protected String loadETagfromFile() {
+			try {
+				FileInputStream f =
+						new FileInputStream(tileCacheDir + "/" + tile.getZoom() + "_"
+								+ tile.getXtile() + "_" + tile.getYtile() + ETAG_FILE_EXT);
+				byte[] buf = new byte[f.available()];
+				f.read(buf);
+				f.close();
+				return new String(buf, ETAG_CHARSET);
+			} catch (Exception e) {
+				return null;
+			}
+		}
+
 	}
 
 	public long getMaxFileAge() {
-		return maxFileAge;
+		return maxCacheFileAge;
 	}
 
 	/**
-	 * Sets the maximum age of the local cached tile in the file system.
+	 * Sets the maximum age of the local cached tile in the file system. If a
+	 * local tile is older than the specified file age
+	 * {@link OsmFileCacheTileLoader} will connect to the tile server and check
+	 * if a newer tile is available using the mechanism specified for the
+	 * selected tile source/server.
 	 * 
 	 * @param maxFileAge
@@ -225,7 +337,8 @@
 	 * @see #FILE_AGE_ONE_DAY
 	 * @see #FILE_AGE_ONE_WEEK
+	 * @see TileSource#getTileUpdate()
 	 */
-	public void setMaxFileAge(long maxFileAge) {
-		this.maxFileAge = maxFileAge;
+	public void setCacheMaxFileAge(long maxFileAge) {
+		this.maxCacheFileAge = maxFileAge;
 	}
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 9846)
@@ -8,8 +8,8 @@
 import java.net.URL;
 
-import org.openstreetmap.gui.jmapviewer.interfaces.Job;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
 
 /**
@@ -20,18 +20,18 @@
 public class OsmTileLoader implements TileLoader {
 
-	protected JMapViewer map;
+	protected TileLoaderListener listener;
 
-	public OsmTileLoader(JMapViewer map) {
-		this.map = map;
+	public OsmTileLoader(TileLoaderListener listener) {
+		this.listener = listener;
 	}
 
-	public Job createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
+	public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
 			final int zoom) {
-		return new Job() {
+		return new Runnable() {
 
 			InputStream input = null;
 
 			public void run() {
-				TileCache cache = map.getTileCache();
+				TileCache cache = listener.getTileCache();
 				Tile tile;
 				synchronized (cache) {
@@ -46,11 +46,11 @@
 					tile.loadImage(input);
 					tile.setLoaded(true);
-					map.repaint();
+					listener.repaint();
 					input.close();
 					input = null;
 				} catch (Exception e) {
 					if (input == null /* || !input.isStopped() */)
-						System.err.println("failed loading " + zoom + "/"
-								+ tilex + "/" + tiley + " " + e.getMessage());
+						System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
+								+ " " + e.getMessage());
 				} finally {
 					tile.loading = false;
@@ -58,15 +58,4 @@
 			}
 
-			/**
-			 * Terminating all transfers that are currently in progress
-			 */
-			public void stop() {
-
-				try {
-					// if (input != null)
-					// input.stop();
-				} catch (Exception e) {
-				}
-			}
 		};
 	}
@@ -80,3 +69,9 @@
 		return urlConn;
 	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName();
+	}
+	
 }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java	(revision 9846)
@@ -36,4 +36,8 @@
 		}
 
+		public TileUpdate getTileUpdate() {
+			return TileUpdate.IfNoneMatch;
+		}
+
 	}
 
@@ -48,4 +52,9 @@
 			return MAP_CYCLE + super.getTileUrl(zoom, tilex, tiley);
 		}
+
+		public TileUpdate getTileUpdate() {
+			return TileUpdate.LastModified;
+		}
+
 	}
 
@@ -65,4 +74,7 @@
 		}
 
+		public TileUpdate getTileUpdate() {
+			return TileUpdate.IfModifiedSince;
+		}
 	}
 }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/Job.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/Job.java	(revision 9785)
+++ 	(revision )
@@ -1,18 +1,0 @@
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-//License: GPL. Copyright 2008 by Jan Peter Stotz
-
-/**
- * An extension to the {@link Runnable} interface adding the possibility to
- * {@link #stop()} it.
- * 
- * @author Jan Peter Stotz
- */
-public interface Job extends Runnable {
-
-	/**
-	 * Allows to stop / cancel a job without having to interrupt the executing
-	 * {@link Thread}.
-	 */
-	public void stop();
-}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java	(revision 9846)
@@ -20,7 +20,7 @@
 	 * @param tiley
 	 * @param zoom
-	 * @returns {@link Job} implementation that performs the desired load
+	 * @returns {@link Runnable} implementation that performs the desired load
 	 *          action.
 	 */
-	public Job createTileLoaderJob(TileSource tileLayerSource, int tilex, int tiley, int zoom);
+	public Runnable createTileLoaderJob(TileSource tileLayerSource, int tilex, int tiley, int zoom);
 }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java	(revision 9846)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java	(revision 9846)
@@ -0,0 +1,10 @@
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+public interface TileLoaderListener {
+	
+	public void repaint();
+
+	public TileCache getTileCache();
+}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 9785)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 9846)
@@ -28,9 +28,11 @@
 	 * all tiles but <b>does not support</b> conditional download via
 	 * <code>If-Modified-Since</code> header entry.</li>
+	 * <li>{@link #None} The server does not support any of the listed
+	 * mechanisms.</li>
 	 * </ul>
 	 * 
 	 */
-	public enum TileUpdateDetection {
-		IfNoneMatch, ETag, IfModifiedSince, LastModified
+	public enum TileUpdate {
+		IfNoneMatch, ETag, IfModifiedSince, LastModified, None
 	};
 
@@ -43,4 +45,10 @@
 	 */
 	public int getMaxZoom();
+
+	/**
+	 * @return The supported tile update mechanism
+	 * @see TileUpdate
+	 */
+	public TileUpdate getTileUpdate();
 
 	/**
