Index: trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 12823)
+++ trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 12824)
@@ -1143,4 +1143,6 @@
     protected class TileSet extends TileRange {
 
+        private volatile TileSetInfo info;
+
         protected TileSet(TileXY t1, TileXY t2, int zoom) {
             super(t1, t2, zoom);
@@ -1269,8 +1271,90 @@
         }
 
+        /**
+         * Check if there is any tile fully loaded without error.
+         * @return true if there is any tile fully loaded without error
+         */
+        public boolean hasVisibleTiles() {
+            return getTileSetInfo().hasVisibleTiles;
+        }
+
+        /**
+         * Check if there there is a tile that is overzoomed.
+         * <p>
+         * I.e. the server response for one tile was "there is no tile here".
+         * This usually happens when zoomed in too much. The limit depends on
+         * the region, so at the edge of such a region, some tiles may be
+         * available and some not.
+         * @return true if there there is a tile that is overzoomed
+         */
+        public boolean hasOverzoomedTiles() {
+            return getTileSetInfo().hasOverzoomedTiles;
+        }
+
+        /**
+         * Check if there are tiles still loading.
+         * <p>
+         * This is the case if there is a tile not yet in the cache, or in the
+         * cache but marked as loading ({@link Tile#isLoading()}.
+         * @return true if there are tiles still loading
+         */
+        public boolean hasLoadingTiles() {
+            return getTileSetInfo().hasLoadingTiles;
+        }
+
+        /**
+         * Check if all tiles in the range are fully loaded.
+         * <p>
+         * A tile is considered to be fully loaded even if the result of loading
+         * the tile was an error.
+         * @return true if all tiles in the range are fully loaded
+         */
+        public boolean hasAllLoadedTiles() {
+            return getTileSetInfo().hasAllLoadedTiles;
+        }
+
+        private TileSetInfo getTileSetInfo() {
+            if (info == null) {
+                synchronized (this) {
+                    if (info == null) {
+                        List<Tile> allTiles = this.allExistingTiles();
+                        info = new TileSetInfo();
+                        info.hasLoadingTiles = allTiles.size() < this.size();
+                        info.hasAllLoadedTiles = true;
+                        for (Tile t : allTiles) {
+                            if ("no-tile".equals(t.getValue("tile-info"))) {
+                                info.hasOverzoomedTiles = true;
+                            }
+                            if (t.isLoaded()) {
+                                if (!t.hasError()) {
+                                    info.hasVisibleTiles = true;
+                                }
+                            } else {
+                                info.hasAllLoadedTiles = false;
+                                if (t.isLoading()) {
+                                    info.hasLoadingTiles = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            return info;
+        }
+
         @Override
         public String toString() {
             return getClass().getName() + ": zoom: " + zoom + " X(" + minX + ", " + maxX + ") Y(" + minY + ", " + maxY + ") size: " + size();
         }
+    }
+
+    /**
+     * Data container to hold information about a {@link TileSet} class.
+     */
+    private static class TileSetInfo {
+        boolean hasVisibleTiles;
+        boolean hasOverzoomedTiles;
+        boolean hasLoadingTiles;
+        boolean hasAllLoadedTiles;
     }
 
@@ -1302,39 +1386,8 @@
     }
 
-    private static class TileSetInfo {
-        boolean hasVisibleTiles;
-        boolean hasOverzoomedTiles;
-        boolean hasLoadingTiles;
-        boolean hasAllLoadedTiles;
-    }
-
-    private static <S extends AbstractTMSTileSource> TileSetInfo getTileSetInfo(AbstractTileSourceLayer<S>.TileSet ts) {
-        List<Tile> allTiles = ts.allExistingTiles();
-        TileSetInfo result = new TileSetInfo();
-        result.hasLoadingTiles = allTiles.size() < ts.size();
-        result.hasAllLoadedTiles = true;
-        for (Tile t : allTiles) {
-            if ("no-tile".equals(t.getValue("tile-info"))) {
-                result.hasOverzoomedTiles = true;
-            }
-            if (t.isLoaded()) {
-                if (!t.hasError()) {
-                    result.hasVisibleTiles = true;
-                }
-            } else {
-                result.hasAllLoadedTiles = false;
-                if (t.isLoading()) {
-                    result.hasLoadingTiles = true;
-                }
-            }
-        }
-        return result;
-    }
-
     private class DeepTileSet {
         private final ProjectionBounds bounds;
         private final int minZoom, maxZoom;
         private final TileSet[] tileSets;
-        private final TileSetInfo[] tileSetInfos;
 
         @SuppressWarnings("unchecked")
@@ -1344,5 +1397,4 @@
             this.maxZoom = maxZoom;
             this.tileSets = new AbstractTileSourceLayer.TileSet[maxZoom - minZoom + 1];
-            this.tileSetInfos = new TileSetInfo[maxZoom - minZoom + 1];
         }
 
@@ -1359,17 +1411,4 @@
             }
         }
-
-        public TileSetInfo getTileSetInfo(int zoom) {
-            if (zoom < minZoom)
-                return new TileSetInfo();
-            synchronized (tileSetInfos) {
-                TileSetInfo tsi = tileSetInfos[zoom-minZoom];
-                if (tsi == null) {
-                    tsi = AbstractTileSourceLayer.getTileSetInfo(getTileSet(zoom));
-                    tileSetInfos[zoom-minZoom] = tsi;
-                }
-                return tsi;
-            }
-        }
     }
 
@@ -1386,5 +1425,4 @@
 
         DeepTileSet dts = new DeepTileSet(pb, getMinZoomLvl(), zoom);
-        TileSet ts = dts.getTileSet(zoom);
 
         int displayZoomLevel = zoom;
@@ -1393,11 +1431,11 @@
         if (getDisplaySettings().isAutoZoom() && getDisplaySettings().isAutoLoad()) {
             // Auto-detection of tilesource maxzoom (currently fully works only for Bing)
-            TileSetInfo tsi = dts.getTileSetInfo(zoom);
-            if (!tsi.hasVisibleTiles && (!tsi.hasLoadingTiles || tsi.hasOverzoomedTiles)) {
+            TileSet ts0 = dts.getTileSet(zoom);
+            if (!ts0.hasVisibleTiles() && (!ts0.hasLoadingTiles() || ts0.hasOverzoomedTiles())) {
                 noTilesAtZoom = true;
             }
             // Find highest zoom level with at least one visible tile
             for (int tmpZoom = zoom; tmpZoom > dts.minZoom; tmpZoom--) {
-                if (dts.getTileSetInfo(tmpZoom).hasVisibleTiles) {
+                if (dts.getTileSet(tmpZoom).hasVisibleTiles()) {
                     displayZoomLevel = tmpZoom;
                     break;
@@ -1405,7 +1443,7 @@
             }
             // Do binary search between currentZoomLevel and displayZoomLevel
-            while (zoom > displayZoomLevel && !tsi.hasVisibleTiles && tsi.hasOverzoomedTiles) {
+            while (zoom > displayZoomLevel && !ts0.hasVisibleTiles() && ts0.hasOverzoomedTiles()) {
                 zoom = (zoom + displayZoomLevel)/2;
-                tsi = dts.getTileSetInfo(zoom);
+                ts0 = dts.getTileSet(zoom);
             }
 
@@ -1415,19 +1453,19 @@
             // to make sure there're really no more zoom levels
             // loading is done in the next if section
-            if (zoom == displayZoomLevel && !tsi.hasLoadingTiles && zoom < dts.maxZoom) {
+            if (zoom == displayZoomLevel && !ts0.hasLoadingTiles() && zoom < dts.maxZoom) {
                 zoom++;
-                tsi = dts.getTileSetInfo(zoom);
+                ts0 = dts.getTileSet(zoom);
             }
             // When we have overzoomed tiles and all tiles at current zoomlevel is loaded,
             // load tiles at previovus zoomlevels until we have all tiles on screen is loaded.
             // loading is done in the next if section
-            while (zoom > dts.minZoom && tsi.hasOverzoomedTiles && !tsi.hasLoadingTiles) {
+            while (zoom > dts.minZoom && ts0.hasOverzoomedTiles() && !ts0.hasLoadingTiles()) {
                 zoom--;
-                tsi = dts.getTileSetInfo(zoom);
-            }
-            ts = dts.getTileSet(zoom);
+                ts0 = dts.getTileSet(zoom);
+            }
         } else if (getDisplaySettings().isAutoZoom()) {
             setZoomLevel(zoom, false);
         }
+        TileSet ts = dts.getTileSet(zoom);
 
         // Too many tiles... refuse to download
@@ -1440,5 +1478,5 @@
         if (displayZoomLevel != zoom) {
             ts = dts.getTileSet(displayZoomLevel);
-            if (!dts.getTileSetInfo(displayZoomLevel).hasAllLoadedTiles && displayZoomLevel < zoom) {
+            if (!dts.getTileSet(displayZoomLevel).hasAllLoadedTiles() && displayZoomLevel < zoom) {
                 // if we are showing tiles from lower zoom level, ensure that all tiles are loaded as they are few,
                 // and should not trash the tile cache
