Index: trunk/data/maps.xsd
===================================================================
--- trunk/data/maps.xsd	(revision 8343)
+++ trunk/data/maps.xsd	(revision 8344)
@@ -671,4 +671,10 @@
 								</xs:complexType>
 							</xs:element>
+							<xs:element name="no-tile-header" minOccurs="0" maxOccurs="unbounded">
+								<xs:complexType>
+									<xs:attribute name="name" type="xs:string" />
+									<xs:attribute name="value" type="xs:string" />
+								</xs:complexType>
+							</xs:element>
 						</xs:choice>
 					</xs:sequence>
Index: trunk/src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 8344)
@@ -14,4 +14,6 @@
 import java.io.PrintWriter;
 import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -22,4 +24,5 @@
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -38,4 +41,10 @@
 import java.util.regex.Pattern;
 
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
+import javax.json.JsonValue;
+import javax.json.JsonWriter;
 import javax.swing.JOptionPane;
 import javax.swing.UIManager;
@@ -1266,4 +1275,31 @@
     }
 
+    @SuppressWarnings("rawtypes")
+    private static String mapToJson(Map map) {
+        StringWriter stringWriter = new StringWriter();
+        try (JsonWriter writer = Json.createWriter(stringWriter)) {
+            JsonObjectBuilder object = Json.createObjectBuilder();
+            for(Object o: map.entrySet()) {
+                Entry e = (Entry) o;
+                object.add(e.getKey().toString(), e.getValue().toString());
+            }
+            writer.writeObject(object.build());
+        }
+        return stringWriter.toString();
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private static Map mapFromJson(String s) {
+        Map ret = null;
+        try (JsonReader reader = Json.createReader(new StringReader(s))) {
+            JsonObject object = reader.readObject();
+            ret = new HashMap(object.size());
+            for (Entry<String, JsonValue> e: object.entrySet()) {
+                ret.put(e.getKey(), e.getValue().toString());
+            }
+        }
+        return ret;
+    }
+
     public static <T> Map<String,String> serializeStruct(T struct, Class<T> klass) {
         T structPrototype;
@@ -1285,5 +1321,10 @@
                 if (fieldValue != null) {
                     if (f.getAnnotation(writeExplicitly.class) != null || !Objects.equals(fieldValue, defaultFieldValue)) {
-                        hash.put(f.getName().replace("_", "-"), fieldValue.toString());
+                        String key = f.getName().replace("_", "-");
+                        if (fieldValue instanceof Map) {
+                            hash.put(key, mapToJson((Map) fieldValue));
+                        } else {
+                            hash.put(key, fieldValue.toString());
+                        }
                     }
                 }
@@ -1332,5 +1373,8 @@
             } else  if (f.getType() == String.class) {
                 value = key_value.getValue();
-            } else
+            } else if (f.getType().isAssignableFrom(Map.class)) {
+                value = mapFromJson(key_value.getValue());
+            }
+            else
                 throw new RuntimeException("unsupported preference primitive type");
 
Index: trunk/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java	(revision 8344)
@@ -43,5 +43,5 @@
                 return img;
             byte[] content = getContent();
-            if (content != null) {
+            if (content != null && content.length > 0) {
                 img = ImageIO.read(new ByteArrayInputStream(content));
 
Index: trunk/src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java	(revision 8344)
@@ -20,7 +20,8 @@
      *
      * @param data
+     * @param attributes
      * @param result
      */
-    public void loadingFinished(CacheEntry data, LoadResult result);
+    public void loadingFinished(CacheEntry data, CacheEntryAttributes attributes, LoadResult result);
 
 }
Index: trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java	(revision 8344)
@@ -10,4 +10,5 @@
 import java.net.URLConnection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
@@ -224,5 +225,5 @@
      * @return cache object as empty, regardless of what remote resource has returned (ex. based on headers)
      */
-    protected boolean cacheAsEmpty() {
+    protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) {
         return false;
     }
@@ -280,5 +281,5 @@
         try {
             for (ICachedLoaderListener l: listeners) {
-                l.loadingFinished(cacheData, result);
+                l.loadingFinished(cacheData, attributes, result);
             }
         } catch (Exception e) {
@@ -286,5 +287,5 @@
             Main.warn(e);
             for (ICachedLoaderListener l: listeners) {
-                l.loadingFinished(cacheData, LoadResult.FAILURE);
+                l.loadingFinished(cacheData, attributes, LoadResult.FAILURE);
             }
 
@@ -329,5 +330,5 @@
                 return true;
             }
-            URLConnection urlConn = getURLConnection();
+            HttpURLConnection urlConn = getURLConnection();
 
             if (isObjectLoadable()  &&
@@ -338,5 +339,5 @@
                 urlConn.addRequestProperty("If-None-Match", attributes.getEtag());
             }
-            if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) {
+            if (urlConn.getResponseCode() == 304) {
                 // If isModifiedSince or If-None-Match has been set
                 // and the server answers with a HTTP 304 = "Not Modified"
@@ -359,5 +360,5 @@
 
             for (int i = 0; i < 5; ++i) {
-                if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) {
+                if (urlConn.getResponseCode() == 503) {
                     Thread.sleep(5000+(new Random()).nextInt(5000));
                     continue;
@@ -365,5 +366,6 @@
                 byte[] raw = read(urlConn);
 
-                if (!cacheAsEmpty() && raw != null && raw.length > 0) {
+                if (!cacheAsEmpty(urlConn.getHeaderFields(), urlConn.getResponseCode(), raw) &&
+                        raw != null && raw.length > 0) {
                     cacheData = createCacheEntry(raw);
                     cache.put(getCacheKey(), cacheData, attributes);
@@ -400,5 +402,4 @@
     private CacheEntryAttributes parseHeaders(URLConnection urlConn) {
         CacheEntryAttributes ret = new CacheEntryAttributes();
-        ret.setNoTileAtZoom("no-tile".equals(urlConn.getHeaderField("X-VE-Tile-Info")));
 
         Long lng = urlConn.getExpiration();
Index: trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java	(revision 8344)
@@ -10,4 +10,5 @@
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.TreeSet;
@@ -21,4 +22,5 @@
 import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
 import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
+import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
@@ -35,5 +37,5 @@
  * @author Frederik Ramm
  */
-public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
+public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed {
 
     /**
@@ -53,4 +55,5 @@
         /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
         WMS_ENDPOINT("wms_endpoint");
+
 
         private final String typeString;
@@ -151,14 +154,9 @@
     }
 
-    /** name of the imagery entry (gets translated by josm usually) */
-    private String name;
+
     /** original name of the imagery entry in case of translation call, for multiple languages English when possible */
     private String origName;
     /** (original) language of the translated name entry */
     private String langName;
-    /** id for this imagery entry, optional at the moment */
-    private String id;
-    /** URL of the imagery service */
-    private String url = null;
     /** whether this is a entry activated by default or not */
     private boolean defaultEntry = false;
@@ -199,4 +197,5 @@
     private String icon;
     // when adding a field, also adapt the ImageryInfo(ImageryInfo) constructor
+    private Map<String, String> noTileHeaders;
 
     /**
@@ -225,4 +224,5 @@
         @pref String icon;
         @pref String description;
+        @pref Map<String, String> noTileHeaders;
 
         /**
@@ -278,4 +278,7 @@
                 projections = val.toString();
             }
+            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
+                noTileHeaders = i.noTileHeaders;
+            }
         }
 
@@ -295,4 +298,5 @@
      */
     public ImageryInfo() {
+        super();
     }
 
@@ -302,5 +306,5 @@
      */
     public ImageryInfo(String name) {
-        this.name=name;
+        super(name);
     }
 
@@ -311,5 +315,5 @@
      */
     public ImageryInfo(String name, String url) {
-        this.name=name;
+        this(name);
         setExtendedUrl(url);
     }
@@ -322,5 +326,5 @@
      */
     public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
-        this.name=name;
+        this(name);
         setExtendedUrl(url);
         this.eulaAcceptanceRequired = eulaAcceptanceRequired;
@@ -337,5 +341,5 @@
      */
     public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
-        this.name=name;
+        this(name);
         setExtendedUrl(url);
         ImageryType t = ImageryType.fromString(type);
@@ -347,4 +351,9 @@
             throw new IllegalArgumentException("unknown type: "+type);
         }
+    }
+
+    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
+        this(name, url, type, eulaAcceptanceRequired, cookies);
+        setId(id);
     }
 
@@ -390,4 +399,7 @@
         countryCode = e.country_code;
         icon = e.icon;
+        if (e.noTileHeaders != null) {
+            noTileHeaders = e.noTileHeaders;
+        }
     }
 
@@ -688,4 +700,5 @@
      * @return The entry name
      */
+    @Override
     public String getName() {
         return this.name;
@@ -759,4 +772,5 @@
      * @return The entry URL
      */
+    @Override
     public String getUrl() {
         return this.url;
@@ -791,4 +805,5 @@
      * @return the cookie data part
      */
+    @Override
     public String getCookies() {
         return this.cookies;
@@ -803,4 +818,5 @@
      * @return The maximum zoom level
      */
+    @Override
     public int getMaxZoom() {
         return this.defaultMaxZoom;
@@ -811,4 +827,5 @@
      * @return The minimum zoom level
      */
+    @Override
     public int getMinZoom() {
         return this.defaultMinZoom;
@@ -1026,3 +1043,12 @@
         return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
     }
+
+    public void setNoTileHeaders(Map<String, String> noTileHeaders) {
+       this.noTileHeaders = noTileHeaders;
+    }
+
+    @Override
+    public Map<String, String> getNoTileHeaders() {
+        return noTileHeaders;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 8344)
@@ -149,4 +149,14 @@
         Collection<String> newKnownDefaults = new TreeSet<>(knownDefaults);
         for (ImageryInfo def : defaultLayers) {
+            // temporary migration code, so all user preferences will get updated with new settings from JOSM site (can be removed ~Dez. 2015)
+            if (def.getNoTileHeaders() != null) {
+                for (ImageryInfo i: layers) {
+                    if (isSimilar(def,  i)) {
+                        i.setNoTileHeaders(def.getNoTileHeaders());
+                        changed = true;
+                    }
+                }
+            }
+
             if (def.isDefaultEntry()) {
                 boolean isKnownDefault = false;
Index: trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java	(revision 8344)
@@ -6,4 +6,5 @@
 import java.net.URL;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -27,4 +28,5 @@
 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
 import org.openstreetmap.josm.data.cache.CacheEntry;
+import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
 import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
 import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob;
@@ -194,5 +196,5 @@
             byte[] content = cacheData.getContent();
             try {
-                return content != null  || cacheData.getImage() != null || cacheAsEmpty();
+                return content != null  || cacheData.getImage() != null || isNoTileAtZoom();
             } catch (IOException e) {
                 log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
@@ -203,10 +205,17 @@
 
     private boolean isNoTileAtZoom() {
+        if (attributes == null) {
+            log.warning("Cache attributes are null");
+        }
         return attributes != null && attributes.isNoTileAtZoom();
     }
 
     @Override
-    protected boolean cacheAsEmpty() {
-        return isNoTileAtZoom();
+    protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) {
+        if (tile.getTileSource().isNoTileAtZoom(headers, statusCode, content)) {
+            attributes.setNoTileAtZoom(true);
+            return true;
+        }
+        return false;
     }
 
@@ -242,5 +251,6 @@
 
     @Override
-    public void loadingFinished(CacheEntry object, LoadResult result) {
+    public void loadingFinished(CacheEntry object, CacheEntryAttributes attributes, LoadResult result) {
+        this.attributes = attributes; // as we might get notification from other object than our selfs, pass attributes along
         Set<TileLoaderListener> listeners;
         synchronized (inProgress) {
@@ -316,7 +326,10 @@
     @Override
     protected boolean handleNotFound() {
-        tile.setError("No tile at this zoom level");
-        tile.putValue("tile-info", "no-tile");
-        return true;
+        if (tile.getSource().isNoTileAtZoom(null, 404, null)) {
+            tile.setError("No tile at this zoom level");
+            tile.putValue("tile-info", "no-tile");
+            return true;
+        }
+        return false;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java	(revision 8344)
@@ -278,6 +278,6 @@
     private static class CachedAttributionBingAerialTileSource extends BingAerialTileSource {
 
-        public CachedAttributionBingAerialTileSource(String id) {
-            super(id);
+        public CachedAttributionBingAerialTileSource(ImageryInfo info) {
+            super(info);
         }
 
@@ -337,12 +337,13 @@
         if (info.getImageryType() == ImageryType.TMS) {
             checkUrl(info.getUrl());
-            TMSTileSource t = new TemplatedTMSTileSource(info.getName(), info.getUrl(), info.getId(), info.getMinZoom(), info.getMaxZoom(),
-                    info.getCookies());
+            TMSTileSource t = new TemplatedTMSTileSource(info);
             info.setAttribution(t);
             return t;
-        } else if (info.getImageryType() == ImageryType.BING)
-            return new CachedAttributionBingAerialTileSource(info.getId());
-        else if (info.getImageryType() == ImageryType.SCANEX) {
-            return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom());
+        } else if (info.getImageryType() == ImageryType.BING) {
+            //return new CachedAttributionBingAerialTileSource(info.getId());
+            return new CachedAttributionBingAerialTileSource(info);
+        } else if (info.getImageryType() == ImageryType.SCANEX) {
+            //return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom());
+            return new ScanexTileSource(info);
         }
         return null;
Index: trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java	(revision 8343)
+++ trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java	(revision 8344)
@@ -6,5 +6,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Stack;
@@ -39,4 +41,5 @@
         BOUNDS,
         SHAPE,
+        NO_TILE,
         UNKNOWN,            // element is not recognized in the current context
     }
@@ -84,4 +87,5 @@
         private String lang;
         private List<String> projections;
+        private Map<String, String> noTileHeaders;
 
         @Override
@@ -95,4 +99,5 @@
             bounds = null;
             projections = null;
+            noTileHeaders = null;
         }
 
@@ -150,4 +155,8 @@
                     projections = new ArrayList<>();
                     newState = State.PROJECTIONS;
+                } else if ("no-tile-header".equals(qName)) {
+                    noTileHeaders = new HashMap<>();
+                    noTileHeaders.put(atts.getValue("name"), atts.getValue("value"));
+                    newState = State.NO_TILE;
                 }
                 break;
@@ -308,4 +317,9 @@
                 projections = null;
                 break;
+            case NO_TILE:
+                entry.setNoTileHeaders(noTileHeaders);
+                noTileHeaders = null;
+                break;
+
             }
         }
