Index: trunk/src/org/openstreetmap/josm/data/CustomConfigurator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/CustomConfigurator.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/data/CustomConfigurator.java	(revision 6578)
@@ -39,5 +39,9 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences.ListListSetting;
+import org.openstreetmap.josm.data.Preferences.ListSetting;
+import org.openstreetmap.josm.data.Preferences.MapListSetting;
 import org.openstreetmap.josm.data.Preferences.Setting;
+import org.openstreetmap.josm.data.Preferences.StringSetting;
 import org.openstreetmap.josm.gui.io.DownloadFileTask;
 import org.openstreetmap.josm.plugins.PluginDownloadTask;
@@ -276,5 +280,5 @@
 
 
-        public static void deleteFile(String path, String base) {
+    public static void deleteFile(String path, String base) {
         String dir = getDirectoryByAbbr(base);
         if (dir==null) {
@@ -290,5 +294,4 @@
             deleteFileOrDirectory(fOut);
         }
-        return;
     }
 
@@ -401,12 +404,6 @@
     public static Preferences clonePreferences(Preferences pref) {
         Preferences tmp = new Preferences();
-        tmp.defaults.putAll(   pref.defaults );
-        tmp.properties.putAll( pref.properties );
-        tmp.arrayDefaults.putAll(   pref.arrayDefaults );
-        tmp.arrayProperties.putAll( pref.arrayProperties );
-        tmp.collectionDefaults.putAll(   pref.collectionDefaults );
-        tmp.collectionProperties.putAll( pref.collectionProperties );
-        tmp.listOfStructsDefaults.putAll(   pref.listOfStructsDefaults );
-        tmp.listOfStructsProperties.putAll( pref.listOfStructsProperties );
+        tmp.settingsMap.putAll(pref.settingsMap);
+        tmp.defaultsMap.putAll(pref.defaultsMap);
         tmp.colornames.putAll( pref.colornames );
 
@@ -738,5 +735,5 @@
 
     /**
-     * Helper class to do specific Prefrences operation - appending, replacing,
+     * Helper class to do specific Preferences operation - appending, replacing,
      * deletion by key and by value
      * Also contains functions that convert preferences object to JavaScript object and back
@@ -745,156 +742,118 @@
 
         private static void replacePreferences(Preferences fragment, Preferences mainpref) {
-            // normal prefs
-            for (Entry<String, String> entry : fragment.properties.entrySet()) {
-                mainpref.put(entry.getKey(), entry.getValue());
-            }
-            // "list"
-            for (Entry<String, List<String>> entry : fragment.collectionProperties.entrySet()) {
-                mainpref.putCollection(entry.getKey(), entry.getValue());
-            }
-            // "lists"
-            for (Entry<String, List<List<String>>> entry : fragment.arrayProperties.entrySet()) {
-                List<Collection<String>> array = new ArrayList<Collection<String>>();
-                array.addAll(entry.getValue());
-                mainpref.putArray(entry.getKey(), array);
-            }
-            /// "maps"
-            for (Entry<String, List<Map<String, String>>> entry : fragment.listOfStructsProperties.entrySet()) {
-                mainpref.putListOfStructs(entry.getKey(), entry.getValue());
-            }
-
+            for (Entry<String, Setting> entry: fragment.settingsMap.entrySet()) {
+                mainpref.putSetting(entry.getKey(), entry.getValue());
+            }
         }
 
         private static void appendPreferences(Preferences fragment, Preferences mainpref) {
-            // normal prefs
-            for (Entry<String, String> entry : fragment.properties.entrySet()) {
-                mainpref.put(entry.getKey(), entry.getValue());
-            }
-
-            // "list"
-            for (Entry<String, List<String>> entry : fragment.collectionProperties.entrySet()) {
+            for (Entry<String, Setting> entry: fragment.settingsMap.entrySet()) {
                 String key = entry.getKey();
-
-                Collection<String> newItems = getCollection(mainpref, key, true);
-                if (newItems == null) continue;
-
-                for (String item : entry.getValue()) {
-                    // add nonexisting elements to then list
-                    if (!newItems.contains(item)) {
-                        newItems.add(item);
+                if (entry.getValue() instanceof StringSetting) {
+                    mainpref.putSetting(key, entry.getValue());
+                } else if (entry.getValue() instanceof ListSetting) {
+                    ListSetting lSetting = (ListSetting) entry.getValue();
+                    Collection<String> newItems = getCollection(mainpref, key, true);
+                    if (newItems == null) continue;
+                    for (String item : lSetting.getValue()) {
+                        // add nonexisting elements to then list
+                        if (!newItems.contains(item)) {
+                            newItems.add(item);
+                        }
                     }
+                    mainpref.putCollection(key, newItems);
+                } else if (entry.getValue() instanceof ListListSetting) {
+                    ListListSetting llSetting = (ListListSetting) entry.getValue();
+                    Collection<Collection<String>> newLists = getArray(mainpref, key, true);
+                    if (newLists == null) continue;
+
+                    for (Collection<String> list : llSetting.getValue()) {
+                        // add nonexisting list (equals comparison for lists is used implicitly)
+                        if (!newLists.contains(list)) {
+                            newLists.add(list);
+                        }
+                    }
+                    mainpref.putArray(key, newLists);
+                } else if (entry.getValue() instanceof MapListSetting) {
+                    MapListSetting mlSetting = (MapListSetting) entry.getValue();
+                    List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
+                    if (newMaps == null) continue;
+
+                    // get existing properties as list of maps
+
+                    for (Map<String, String> map : mlSetting.getValue()) {
+                        // add nonexisting map (equals comparison for maps is used implicitly)
+                        if (!newMaps.contains(map)) {
+                            newMaps.add(map);
+                        }
+                    }
+                    mainpref.putListOfStructs(entry.getKey(), newMaps);
                 }
-                mainpref.putCollection(entry.getKey(), newItems);
-            }
-
-            // "lists"
-            for (Entry<String, List<List<String>>> entry : fragment.arrayProperties.entrySet()) {
+            }
+        }
+
+        /**
+        * Delete items from @param mainpref collections that match items from @param fragment collections
+        */
+        private static void deletePreferenceValues(Preferences fragment, Preferences mainpref) {
+
+            for (Entry<String, Setting> entry : fragment.settingsMap.entrySet()) {
                 String key = entry.getKey();
-
-                Collection<Collection<String>> newLists = getArray(mainpref, key, true);
-                if (newLists == null) continue;
-
-                for (Collection<String> list : entry.getValue()) {
-                    // add nonexisting list (equals comparison for lists is used implicitly)
-                    if (!newLists.contains(list)) {
-                        newLists.add(list);
+                if (entry.getValue() instanceof StringSetting) {
+                    StringSetting sSetting = (StringSetting) entry.getValue();
+                    // if mentioned value found, delete it
+                    if (sSetting.equals(mainpref.settingsMap.get(key))) {
+                        mainpref.put(key, null);
                     }
+                } else if (entry.getValue() instanceof ListSetting) {
+                    ListSetting lSetting = (ListSetting) entry.getValue();
+                    Collection<String> newItems = getCollection(mainpref, key, true);
+                    if (newItems == null) continue;
+
+                    // remove mentioned items from collection
+                    for (String item : lSetting.getValue()) {
+                        log("Deleting preferences: from list %s: %s\n", key, item);
+                        newItems.remove(item);
+                    }
+                    mainpref.putCollection(entry.getKey(), newItems);
+                } else if (entry.getValue() instanceof ListListSetting) {
+                    ListListSetting llSetting = (ListListSetting) entry.getValue();
+                    Collection<Collection<String>> newLists = getArray(mainpref, key, true);
+                    if (newLists == null) continue;
+
+                    // if items are found in one of lists, remove that list!
+                    Iterator<Collection<String>> listIterator = newLists.iterator();
+                    while (listIterator.hasNext()) {
+                        Collection<String> list = listIterator.next();
+                        for (Collection<String> removeList : llSetting.getValue()) {
+                            if (list.containsAll(removeList)) {
+                                // remove current list, because it matches search criteria
+                                log("Deleting preferences: list from lists %s: %s\n", key, list);
+                                listIterator.remove();
+                            }
+                        }
+                    }
+
+                    mainpref.putArray(key, newLists);
+                } else if (entry.getValue() instanceof MapListSetting) {
+                    MapListSetting mlSetting = (MapListSetting) entry.getValue();
+                    List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
+                    if (newMaps == null) continue;
+
+                    Iterator<Map<String, String>> mapIterator = newMaps.iterator();
+                    while (mapIterator.hasNext()) {
+                        Map<String, String> map = mapIterator.next();
+                        for (Map<String, String> removeMap : mlSetting.getValue()) {
+                            if (map.entrySet().containsAll(removeMap.entrySet())) {
+                                // the map contain all mentioned key-value pair, so it should be deleted from "maps"
+                                log("Deleting preferences: deleting map from maps %s: %s\n", key, map);
+                                mapIterator.remove();
+                            }
+                        }
+                    }
+                    mainpref.putListOfStructs(entry.getKey(), newMaps);
                 }
-                mainpref.putArray(entry.getKey(), newLists);
-            }
-
-            /// "maps"
-            for (Entry<String, List<Map<String, String>>> entry : fragment.listOfStructsProperties.entrySet()) {
-                String key = entry.getKey();
-
-                List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
-                if (newMaps == null) continue;
-
-                // get existing properties as list of maps
-
-                for (Map<String, String> map : entry.getValue()) {
-                    // add nonexisting map (equals comparison for maps is used implicitly)
-                    if (!newMaps.contains(map)) {
-                        newMaps.add(map);
-                    }
-                }
-                mainpref.putListOfStructs(entry.getKey(), newMaps);
-            }
-        }
-
-        /**
-     * Delete items from @param mainpref collections that match items from @param fragment collections
-     */
-    private static void deletePreferenceValues(Preferences fragment, Preferences mainpref) {
-
-
-        // normal prefs
-        for (Entry<String, String> entry : fragment.properties.entrySet()) {
-            // if mentioned value found, delete it
-            if (entry.getValue().equals(mainpref.properties.get(entry.getKey()))) {
-                mainpref.put(entry.getKey(), null);
-            }
-        }
-
-        // "list"
-        for (Entry<String, List<String>> entry : fragment.collectionProperties.entrySet()) {
-            String key = entry.getKey();
-
-            Collection<String> newItems = getCollection(mainpref, key, true);
-            if (newItems == null) continue;
-
-            // remove mentioned items from collection
-            for (String item : entry.getValue()) {
-                log("Deleting preferences: from list %s: %s\n", key, item);
-                newItems.remove(item);
-            }
-            mainpref.putCollection(entry.getKey(), newItems);
-        }
-
-        // "lists"
-        for (Entry<String, List<List<String>>> entry : fragment.arrayProperties.entrySet()) {
-            String key = entry.getKey();
-
-
-            Collection<Collection<String>> newLists = getArray(mainpref, key, true);
-            if (newLists == null) continue;
-
-            // if items are found in one of lists, remove that list!
-            Iterator<Collection<String>> listIterator = newLists.iterator();
-            while (listIterator.hasNext()) {
-                Collection<String> list = listIterator.next();
-                for (Collection<String> removeList : entry.getValue()) {
-                    if (list.containsAll(removeList)) {
-                        // remove current list, because it matches search criteria
-                        log("Deleting preferences: list from lists %s: %s\n", key, list);
-                        listIterator.remove();
-                    }
-                }
-            }
-
-            mainpref.putArray(entry.getKey(), newLists);
-        }
-
-        /// "maps"
-        for (Entry<String, List<Map<String, String>>> entry : fragment.listOfStructsProperties.entrySet()) {
-            String key = entry.getKey();
-
-            List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
-            if (newMaps == null) continue;
-
-            Iterator<Map<String, String>> mapIterator = newMaps.iterator();
-            while (mapIterator.hasNext()) {
-                Map<String, String> map = mapIterator.next();
-                for (Map<String, String> removeMap : entry.getValue()) {
-                    if (map.entrySet().containsAll(removeMap.entrySet())) {
-                        // the map contain all mentioned key-value pair, so it should be deleted from "maps"
-                        log("Deleting preferences: deleting map from maps %s: %s\n", key, map);
-                        mapIterator.remove();
-                    }
-                }
-            }
-            mainpref.putListOfStructs(entry.getKey(), newMaps);
-        }
-    }
+            }
+        }
 
     private static void deletePreferenceKeyByPattern(String pattern, Preferences pref) {
@@ -904,5 +863,5 @@
             if (key.matches(pattern)) {
                 log("Deleting preferences: deleting key from preferences: " + key);
-                pref.putSetting(key, entry.getValue().getNullInstance());
+                pref.putSetting(key, null);
             }
         }
@@ -913,23 +872,24 @@
         if (allSettings.containsKey(key)) {
             log("Deleting preferences: deleting key from preferences: " + key);
-            pref.putSetting(key, allSettings.get(key).getNullInstance());
+            pref.putSetting(key, null);
         }
     }
 
     private static Collection<String> getCollection(Preferences mainpref, String key, boolean warnUnknownDefault)  {
-        Collection<String> existing = mainpref.collectionProperties.get(key);
-        Collection<String> defaults = mainpref.collectionDefaults.get(key);
-
+        ListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListSetting.class);
+        ListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListSetting.class);
         if (existing == null && defaults == null) {
             if (warnUnknownDefault) defaultUnknownWarning(key);
             return null;
         }
-        return  (existing != null)
-                ? new ArrayList<String>(existing) : new ArrayList<String>(defaults);
+        if (existing != null)
+            return new ArrayList<String>(existing.getValue());
+        else
+            return defaults.getValue() == null ? null : new ArrayList<String>(defaults.getValue());
     }
 
     private static Collection<Collection<String>> getArray(Preferences mainpref, String key, boolean warnUnknownDefault)  {
-        Collection<List<String>> existing = mainpref.arrayProperties.get(key);
-        Collection<List<String>> defaults = mainpref.arrayDefaults.get(key);
+        ListListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListListSetting.class);
+        ListListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListListSetting.class);
 
         if (existing == null && defaults == null) {
@@ -937,12 +897,13 @@
             return null;
         }
-
-        return  (existing != null)
-                ? new ArrayList<Collection<String>>(existing) : new ArrayList<Collection<String>>(defaults);
+        if (existing != null)
+            return new ArrayList<Collection<String>>(existing.getValue());
+        else
+            return defaults.getValue() == null ? null : new ArrayList<Collection<String>>(defaults.getValue());
     }
 
     private static List<Map<String, String>> getListOfStructs(Preferences mainpref, String key, boolean warnUnknownDefault)  {
-        Collection<Map<String, String>> existing = mainpref.listOfStructsProperties.get(key);
-        Collection<Map<String, String>> defaults = mainpref.listOfStructsDefaults.get(key);
+        MapListSetting existing = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class);
+        MapListSetting defaults = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class);
 
         if (existing == null && defaults == null) {
@@ -951,6 +912,8 @@
         }
 
-        return (existing != null)
-                ? new ArrayList<Map<String, String>>(existing) : new ArrayList<Map<String, String>>(defaults);
+        if (existing != null)
+            return new ArrayList<Map<String, String>>(existing.getValue());
+        else
+            return defaults.getValue() == null ? null : new ArrayList<Map<String, String>>(defaults.getValue());
     }
 
@@ -965,8 +928,5 @@
 
     private static void showPrefs(Preferences tmpPref) {
-        Main.info("properties: " + tmpPref.properties);
-        Main.info("collections: " + tmpPref.collectionProperties);
-        Main.info("arrays: " + tmpPref.arrayProperties);
-        Main.info("maps: " + tmpPref.listOfStructsProperties);
+        Main.info("properties: " + tmpPref.settingsMap);
     }
 
@@ -1037,28 +997,24 @@
         Map<String, List<Map<String, String>>> listmapMap = (SortedMap<String, List<Map<String,String>>>) engine.get("listmapMap");
 
-        tmpPref.properties.clear();
-        tmpPref.collectionProperties.clear();
-        tmpPref.arrayProperties.clear();
-        tmpPref.listOfStructsProperties.clear();
-
+        tmpPref.settingsMap.clear();
+
+        Map<String, Setting> tmp = new HashMap<String, Setting>();
         for (Entry<String, String> e : stringMap.entrySet()) {
-            if (e.getValue().equals( tmpPref.defaults.get(e.getKey())) ) continue;
-            tmpPref.properties.put(e.getKey(), e.getValue());
-        }
-
+            tmp.put(e.getKey(), new StringSetting(e.getValue()));
+        }
         for (Entry<String, List<String>> e : listMap.entrySet()) {
-            if (Preferences.equalCollection(e.getValue(), tmpPref.collectionDefaults.get(e.getKey()))) continue;
-            tmpPref.collectionProperties.put(e.getKey(), e.getValue());
+            tmp.put(e.getKey(), new ListSetting(e.getValue()));
         }
 
         for (Entry<String, List<Collection<String>>> e : listlistMap.entrySet()) {
-            if (Preferences.equalArray(e.getValue(), tmpPref.arrayDefaults.get(e.getKey()))) continue;
             @SuppressWarnings("unchecked") List<List<String>> value = (List)e.getValue();
-            tmpPref.arrayProperties.put(e.getKey(), value);
-        }
-
+            tmp.put(e.getKey(), new ListListSetting(value));
+        }
         for (Entry<String, List<Map<String, String>>> e : listmapMap.entrySet()) {
-            if (Preferences.equalListOfStructs(e.getValue(), tmpPref.listOfStructsDefaults.get(e.getKey()))) continue;
-            tmpPref.listOfStructsProperties.put(e.getKey(), e.getValue());
+            tmp.put(e.getKey(), new MapListSetting(e.getValue()));
+        }
+        for (Entry<String, Setting> e : tmp.entrySet()) {
+            if (e.getValue().equals(tmpPref.defaultsMap.get(e.getKey()))) continue;
+            tmpPref.settingsMap.put(e.getKey(), e.getValue());
         }
     }
@@ -1079,19 +1035,37 @@
 
         if (includeDefaults) {
-            stringMap.putAll(tmpPref.defaults);
-            listMap.putAll(tmpPref.collectionDefaults);
-            listlistMap.putAll(tmpPref.arrayDefaults);
-            listmapMap.putAll(tmpPref.listOfStructsDefaults);
-        }
-
-        while (stringMap.values().remove(null));
-        while (listMap.values().remove(null));
-        while (listlistMap.values().remove(null));
-        while (listmapMap.values().remove(null));
-
-        stringMap.putAll(tmpPref.properties);
-        listMap.putAll(tmpPref.collectionProperties);
-        listlistMap.putAll(tmpPref.arrayProperties);
-        listmapMap.putAll(tmpPref.listOfStructsProperties);
+            for (Map.Entry<String, Setting> e: tmpPref.defaultsMap.entrySet()) {
+                Setting setting = e.getValue();
+                if (setting instanceof StringSetting) {
+                    stringMap.put(e.getKey(), ((StringSetting) setting).getValue());
+                } else if (setting instanceof ListSetting) {
+                    listMap.put(e.getKey(), ((ListSetting) setting).getValue());
+                } else if (setting instanceof ListListSetting) {
+                    listlistMap.put(e.getKey(), ((ListListSetting) setting).getValue());
+                } else if (setting instanceof MapListSetting) {
+                    listmapMap.put(e.getKey(), ((MapListSetting) setting).getValue());
+                }
+            }
+        }
+        Iterator<Map.Entry<String, Setting>> it = tmpPref.settingsMap.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, Setting> e = it.next();
+            if (e.getValue().getValue() == null) {
+                it.remove();
+            }
+        }
+
+        for (Map.Entry<String, Setting> e: tmpPref.settingsMap.entrySet()) {
+            Setting setting = e.getValue();
+            if (setting instanceof StringSetting) {
+                stringMap.put(e.getKey(), ((StringSetting) setting).getValue());
+            } else if (setting instanceof ListSetting) {
+                listMap.put(e.getKey(), ((ListSetting) setting).getValue());
+            } else if (setting instanceof ListListSetting) {
+                listlistMap.put(e.getKey(), ((ListListSetting) setting).getValue());
+            } else if (setting instanceof MapListSetting) {
+                listmapMap.put(e.getKey(), ((MapListSetting) setting).getValue());
+            }
+        }
 
         engine.put("stringMap", stringMap);
Index: trunk/src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 6578)
@@ -51,4 +51,5 @@
 import org.openstreetmap.josm.io.MirroredInputStream;
 import org.openstreetmap.josm.io.XmlWriter;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.Utils;
@@ -88,24 +89,22 @@
 
     /**
-     * Map the property name to strings. Does not contain null or "" values.
-     */
-    protected final SortedMap<String, String> properties = new TreeMap<String, String>();
-    /** Map of defaults, can contain null values */
-    protected final SortedMap<String, String> defaults = new TreeMap<String, String>();
+     * Map the setting name to the current value of the setting.
+     * The map must not contain null as key or value. The mapped setting objects
+     * must not have a null value.
+     */
+    protected final SortedMap<String, Setting> settingsMap = new TreeMap<String, Setting>();
+    /**
+     * Map the setting name to the default value of the setting.
+     * The map must not contain null as key or value. The value of the mapped
+     * setting objects can be null.
+     */
+    protected final SortedMap<String, Setting> defaultsMap = new TreeMap<String, Setting>();
+    // maps color keys to human readable color name
     protected final SortedMap<String, String> colornames = new TreeMap<String, String>();
 
-    /** Mapping for list settings. Must not contain null values */
-    protected final SortedMap<String, List<String>> collectionProperties = new TreeMap<String, List<String>>();
-    /** Defaults, can contain null values */
-    protected final SortedMap<String, List<String>> collectionDefaults = new TreeMap<String, List<String>>();
-
-    protected final SortedMap<String, List<List<String>>> arrayProperties = new TreeMap<String, List<List<String>>>();
-    protected final SortedMap<String, List<List<String>>> arrayDefaults = new TreeMap<String, List<List<String>>>();
-
-    protected final SortedMap<String, List<Map<String,String>>> listOfStructsProperties = new TreeMap<String, List<Map<String,String>>>();
-    protected final SortedMap<String, List<Map<String,String>>> listOfStructsDefaults = new TreeMap<String, List<Map<String,String>>>();
-
-    /**
-     * Interface for a preference value
+    /**
+     * Interface for a preference value.
+     *
+     * Implementations must provide a proper <code>equals</code> method.
      *
      * @param <T> the data type for the value
@@ -118,4 +117,17 @@
          */
         T getValue();
+
+        /**
+         * Check if the value of this Setting object is equal to the given value.
+         * @param otherVal the other value
+         * @return true if the values are equal
+         */
+        boolean equalVal(T otherVal);
+
+        /**
+         * Clone the current object.
+         * @return an identical copy of the current object
+         */
+        Setting<T> copy();
 
         /**
@@ -142,5 +154,5 @@
      */
     abstract public static class AbstractSetting<T> implements Setting<T> {
-        private final T value;
+        protected final T value;
         /**
          * Constructs a new {@code AbstractSetting} with the given value
@@ -169,4 +181,11 @@
             super(value);
         }
+        @Override public boolean equalVal(String otherVal) {
+            if (value == null) return otherVal == null;
+            return value.equals(otherVal);
+        }
+        @Override public StringSetting copy() {
+            return new StringSetting(value);
+        }
         @Override public void visit(SettingVisitor visitor) {
             visitor.visit(this);
@@ -174,4 +193,9 @@
         @Override public StringSetting getNullInstance() {
             return new StringSetting(null);
+        }
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof StringSetting)) return false;
+            return equalVal(((StringSetting) other).getValue());
         }
     }
@@ -187,4 +211,36 @@
         public ListSetting(List<String> value) {
             super(value);
+            consistencyTest();
+        }
+        /**
+         * Convenience factory method.
+         * @param value the value
+         * @return a corresponding ListSetting object
+         */
+        public static ListSetting create(Collection<String> value) {
+            return new ListSetting(value == null ? null : Collections.unmodifiableList(new ArrayList<String>(value)));
+        }
+        @Override public boolean equalVal(List<String> otherVal) {
+            return equalCollection(value, otherVal);
+        }
+        public static boolean equalCollection(Collection<String> a, Collection<String> b) {
+            if (a == null) return b == null;
+            if (b == null) return false;
+            if (a.size() != b.size()) return false;
+            Iterator<String> itA = a.iterator();
+            Iterator<String> itB = b.iterator();
+            while (itA.hasNext()) {
+                String aStr = itA.next();
+                String bStr = itB.next();
+                if (!Utils.equal(aStr,bStr)) return false;
+            }
+            return true;
+        }
+        @Override public ListSetting copy() {
+            return ListSetting.create(value);
+        }
+        private void consistencyTest() {
+            if (value != null && value.contains(null))
+                throw new RuntimeException("Error: Null as list element in preference setting");
         }
         @Override public void visit(SettingVisitor visitor) {
@@ -193,4 +249,9 @@
         @Override public ListSetting getNullInstance() {
             return new ListSetting(null);
+        }
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof ListSetting)) return false;
+            return equalVal(((ListSetting) other).getValue());
         }
     }
@@ -206,4 +267,48 @@
         public ListListSetting(List<List<String>> value) {
             super(value);
+            consistencyTest();
+        }
+        /**
+         * Convenience factory method.
+         * @param value the value
+         * @return a corresponding ListListSetting object
+         */
+        public static ListListSetting create(Collection<Collection<String>> value) {
+            if (value != null) {
+                List<List<String>> valueList = new ArrayList<List<String>>(value.size());
+                for (Collection<String> lst : value) {
+                    valueList.add(new ArrayList<String>(lst));
+                }
+                return new ListListSetting(valueList);
+            }
+            return new ListListSetting(null);
+        }
+        @Override public boolean equalVal(List<List<String>> otherVal) {
+            if (value == null) return otherVal == null;
+            if (otherVal == null) return false;
+            if (value.size() != otherVal.size()) return false;
+            Iterator<List<String>> itA = value.iterator();
+            Iterator<List<String>> itB = otherVal.iterator();
+            while (itA.hasNext()) {
+                if (!ListSetting.equalCollection(itA.next(), itB.next())) return false;
+            }
+            return true;
+        }
+        @Override public ListListSetting copy() {
+            if (value == null) return new ListListSetting(null);
+
+            List<List<String>> copy = new ArrayList<List<String>>(value.size());
+            for (Collection<String> lst : value) {
+                List<String> lstCopy = new ArrayList<String>(lst);
+                copy.add(Collections.unmodifiableList(lstCopy));
+            }
+            return new ListListSetting(Collections.unmodifiableList(copy));
+        }
+        private void consistencyTest() {
+            if (value == null) return;
+            if (value.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting");
+            for (Collection<String> lst : value) {
+                if (lst.contains(null)) throw new RuntimeException("Error: Null as inner list element in preference setting");
+            }
         }
         @Override public void visit(SettingVisitor visitor) {
@@ -212,4 +317,9 @@
         @Override public ListListSetting getNullInstance() {
             return new ListListSetting(null);
+        }
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof ListListSetting)) return false;
+            return equalVal(((ListListSetting) other).getValue());
         }
     }
@@ -225,4 +335,42 @@
         public MapListSetting(List<Map<String, String>> value) {
             super(value);
+            consistencyTest();
+        }
+        @Override public boolean equalVal(List<Map<String, String>> otherVal) {
+            if (value == null) return otherVal == null;
+            if (otherVal == null) return false;
+            if (value.size() != otherVal.size()) return false;
+            Iterator<Map<String, String>> itA = value.iterator();
+            Iterator<Map<String, String>> itB = otherVal.iterator();
+            while (itA.hasNext()) {
+                if (!equalMap(itA.next(), itB.next())) return false;
+            }
+            return true;
+        }
+        private static boolean equalMap(Map<String, String> a, Map<String, String> b) {
+            if (a == null) return b == null;
+            if (b == null) return false;
+            if (a.size() != b.size()) return false;
+            for (Entry<String, String> e : a.entrySet()) {
+                if (!Utils.equal(e.getValue(), b.get(e.getKey()))) return false;
+            }
+            return true;
+        }
+        @Override public MapListSetting copy() {
+            if (value == null) return new MapListSetting(null);
+            List<Map<String, String>> copy = new ArrayList<Map<String, String>>(value.size());
+            for (Map<String, String> map : value) {
+                Map<String, String> mapCopy = new LinkedHashMap<String,String>(map);
+                copy.add(Collections.unmodifiableMap(mapCopy));
+            }
+            return new MapListSetting(Collections.unmodifiableList(copy));
+        }
+        private void consistencyTest() {
+            if (value == null) return;
+            if (value.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting");
+            for (Map<String, String> map : value) {
+                if (map.keySet().contains(null)) throw new RuntimeException("Error: Null as map key in preference setting");
+                if (map.values().contains(null)) throw new RuntimeException("Error: Null as map value in preference setting");
+            }
         }
         @Override public void visit(SettingVisitor visitor) {
@@ -231,4 +379,9 @@
         @Override public MapListSetting getNullInstance() {
             return new MapListSetting(null);
+        }
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof MapListSetting)) return false;
+            return equalVal(((MapListSetting) other).getValue());
         }
     }
@@ -241,8 +394,8 @@
     }
 
-    public interface PreferenceChangeEvent<T> {
+    public interface PreferenceChangeEvent {
         String getKey();
-        Setting<T> getOldValue();
-        Setting<T> getNewValue();
+        Setting getOldValue();
+        Setting getNewValue();
     }
 
@@ -251,10 +404,10 @@
     }
 
-    private static class DefaultPreferenceChangeEvent<T> implements PreferenceChangeEvent<T> {
+    private static class DefaultPreferenceChangeEvent implements PreferenceChangeEvent {
         private final String key;
-        private final Setting<T> oldValue;
-        private final Setting<T> newValue;
-
-        public DefaultPreferenceChangeEvent(String key, Setting<T> oldValue, Setting<T> newValue) {
+        private final Setting oldValue;
+        private final Setting newValue;
+
+        public DefaultPreferenceChangeEvent(String key, Setting oldValue, Setting newValue) {
             this.key = key;
             this.oldValue = oldValue;
@@ -267,9 +420,9 @@
         }
         @Override
-        public Setting<T> getOldValue() {
+        public Setting getOldValue() {
             return oldValue;
         }
         @Override
-        public Setting<T> getNewValue() {
+        public Setting getNewValue() {
             return newValue;
         }
@@ -294,6 +447,6 @@
     }
 
-    protected <T> void firePreferenceChanged(String key, Setting<T> oldValue, Setting<T> newValue) {
-        PreferenceChangeEvent<T> evt = new DefaultPreferenceChangeEvent<T>(key, oldValue, newValue);
+    protected void firePreferenceChanged(String key, Setting oldValue, Setting newValue) {
+        PreferenceChangeEvent evt = new DefaultPreferenceChangeEvent(key, oldValue, newValue);
         for (PreferenceChangedListener l : listeners) {
             l.preferenceChanged(evt);
@@ -424,8 +577,6 @@
      */
     synchronized public String get(final String key) {
-        putDefault(key, null);
-        if (!properties.containsKey(key))
-            return "";
-        return properties.get(key);
+        String value = get(key, null);
+        return value == null ? "" : value;
     }
 
@@ -439,16 +590,12 @@
      */
     synchronized public String get(final String key, final String def) {
-        putDefault(key, def);
-        final String prop = properties.get(key);
-        if (prop == null || prop.isEmpty())
-            return def;
-        return prop;
+        return getSetting(key, new StringSetting(def), StringSetting.class).getValue();
     }
 
     synchronized public Map<String, String> getAllPrefix(final String prefix) {
         final Map<String,String> all = new TreeMap<String,String>();
-        for (final Entry<String,String> e : properties.entrySet()) {
-            if (e.getKey().startsWith(prefix)) {
-                all.put(e.getKey(), e.getValue());
+        for (final Entry<String,Setting> e : settingsMap.entrySet()) {
+            if (e.getKey().startsWith(prefix) && (e.getValue() instanceof StringSetting)) {
+                all.put(e.getKey(), ((StringSetting) e.getValue()).getValue());
             }
         }
@@ -458,7 +605,7 @@
     synchronized public List<String> getAllPrefixCollectionKeys(final String prefix) {
         final List<String> all = new LinkedList<String>();
-        for (final String e : collectionProperties.keySet()) {
-            if (e.startsWith(prefix)) {
-                all.add(e);
+        for (Map.Entry<String, Setting> entry : settingsMap.entrySet()) {
+            if (entry.getKey().startsWith(prefix) && entry.getValue() instanceof ListSetting) {
+                all.add(entry.getKey());
             }
         }
@@ -468,12 +615,15 @@
     synchronized public Map<String, String> getAllColors() {
         final Map<String,String> all = new TreeMap<String,String>();
-        for (final Entry<String,String> e : defaults.entrySet()) {
-            if (e.getKey().startsWith("color.") && e.getValue() != null) {
-                all.put(e.getKey().substring(6), e.getValue());
-            }
-        }
-        for (final Entry<String,String> e : properties.entrySet()) {
-            if (e.getKey().startsWith("color.")) {
-                all.put(e.getKey().substring(6), e.getValue());
+        for (final Entry<String,Setting> e : defaultsMap.entrySet()) {
+            if (e.getKey().startsWith("color.") && e.getValue() instanceof StringSetting) {
+                StringSetting d = (StringSetting) e.getValue();
+                if (d.getValue() != null) {
+                    all.put(e.getKey().substring(6), d.getValue());
+                }
+            }
+        }
+        for (final Entry<String,Setting> e : settingsMap.entrySet()) {
+            if (e.getKey().startsWith("color.") && (e.getValue() instanceof StringSetting)) {
+                all.put(e.getKey().substring(6), ((StringSetting) e.getValue()).getValue());
             }
         }
@@ -481,80 +631,35 @@
     }
 
-    synchronized public Map<String, String> getDefaults() {
-        return defaults;
-    }
-
-    synchronized public void putDefault(final String key, final String def) {
-        if(!defaults.containsKey(key) || defaults.get(key) == null) {
-            defaults.put(key, def);
-        } else if(def != null && !defaults.get(key).equals(def)) {
-            Main.info("Defaults for " + key + " differ: " + def + " != " + defaults.get(key));
-        }
-    }
-
     synchronized public boolean getBoolean(final String key) {
-        putDefault(key, null);
-        return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : false;
+        String s = get(key, null);
+        return s == null ? false : Boolean.parseBoolean(s);
     }
 
     synchronized public boolean getBoolean(final String key, final boolean def) {
-        putDefault(key, Boolean.toString(def));
-        return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : def;
+        return Boolean.parseBoolean(get(key, Boolean.toString(def)));
     }
 
     synchronized public boolean getBoolean(final String key, final String specName, final boolean def) {
-        putDefault(key, Boolean.toString(def));
+        boolean generic = getBoolean(key, def);
         String skey = key+"."+specName;
-        if(properties.containsKey(skey))
-            return Boolean.parseBoolean(properties.get(skey));
-        return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : def;
-    }
-
-    /**
-     * Set a value for a certain setting. The changed setting is saved
-     * to the preference file immediately. Due to caching mechanisms on modern
-     * operating systems and hardware, this shouldn't be a performance problem.
+        Setting prop = settingsMap.get(skey);
+        if (prop instanceof StringSetting)
+            return Boolean.parseBoolean(((StringSetting)prop).getValue());
+        else
+            return generic;
+    }
+
+    /**
+     * Set a value for a certain setting.
      * @param key the unique identifier for the setting
      * @param value the value of the setting. Can be null or "" which both removes
      *  the key-value entry.
-     * @return if true, something has changed (i.e. value is different than before)
+     * @return true, if something has changed (i.e. value is different than before)
      */
     public boolean put(final String key, String value) {
-        boolean changed = false;
-        String oldValue = null;
-
-        synchronized (this) {
-            oldValue = properties.get(key);
-            if(value != null && value.length() == 0) {
-                value = null;
-            }
-            // value is the same as before - no need to save anything
-            boolean equalValue = oldValue != null && oldValue.equals(value);
-            // The setting was previously unset and we are supposed to put a
-            // value that equals the default value. This is not necessary because
-            // the default value is the same throughout josm. In addition we like
-            // to have the possibility to change the default value from version
-            // to version, which would not work if we wrote it to the preference file.
-            boolean unsetIsDefault = oldValue == null && (value == null || value.equals(defaults.get(key)));
-
-            if (!(equalValue || unsetIsDefault)) {
-                if (value == null) {
-                    properties.remove(key);
-                } else {
-                    properties.put(key, value);
-                }
-                try {
-                    save();
-                } catch (IOException e) {
-                    Main.warn(tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()));
-                }
-                changed = true;
-            }
-        }
-        if (changed) {
-            // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
-            firePreferenceChanged(key, new StringSetting(oldValue), new StringSetting(value));
-        }
-        return changed;
+        if(value != null && value.length() == 0) {
+            value = null;
+        }
+        return putSetting(key, value == null ? null : new StringSetting(value));
     }
 
@@ -618,5 +723,5 @@
 
     public void load() throws Exception {
-        properties.clear();
+        settingsMap.clear();
         if (!Main.applet) {
             File pref = getPreferenceFile();
@@ -708,5 +813,5 @@
 
     public final void resetToDefault(){
-        properties.clear();
+        settingsMap.clear();
     }
 
@@ -764,8 +869,7 @@
             colornames.put(colKey, colName);
         }
-        putDefault("color."+colKey, ColorHelper.color2html(def));
         String colStr = specName != null ? get("color."+specName) : "";
         if(colStr.isEmpty()) {
-            colStr = get("color."+colKey);
+            colStr = get("color."+colKey, ColorHelper.color2html(def));
         }
         return colStr.isEmpty() ? def : ColorHelper.html2color(colStr);
@@ -773,5 +877,6 @@
 
     synchronized public Color getDefaultColor(String colKey) {
-        String colStr = defaults.get("color."+colKey);
+        StringSetting col = Utils.cast(defaultsMap.get("color."+colKey), StringSetting.class);
+        String colStr = col == null ? null : col.getValue();
         return colStr == null || colStr.isEmpty() ? null : ColorHelper.html2color(colStr);
     }
@@ -782,6 +887,5 @@
 
     synchronized public int getInteger(String key, int def) {
-        putDefault(key, Integer.toString(def));
-        String v = get(key);
+        String v = get(key, Integer.toString(def));
         if(v.isEmpty())
             return def;
@@ -796,8 +900,7 @@
 
     synchronized public int getInteger(String key, String specName, int def) {
-        putDefault(key, Integer.toString(def));
         String v = get(key+"."+specName);
         if(v.isEmpty())
-            v = get(key);
+            v = get(key,Integer.toString(def));
         if(v.isEmpty())
             return def;
@@ -812,6 +915,5 @@
 
     synchronized public long getLong(String key, long def) {
-        putDefault(key, Long.toString(def));
-        String v = get(key);
+        String v = get(key, Long.toString(def));
         if(null == v)
             return def;
@@ -826,6 +928,5 @@
 
     synchronized public double getDouble(String key, double def) {
-        putDefault(key, Double.toString(def));
-        String v = get(key);
+        String v = get(key, Double.toString(def));
         if(null == v)
             return def;
@@ -847,10 +948,5 @@
      */
     public Collection<String> getCollection(String key, Collection<String> def) {
-        putCollectionDefault(key, def == null ? null : new ArrayList<String>(def));
-        Collection<String> prop = collectionProperties.get(key);
-        if (prop != null)
-            return prop;
-        else
-            return def;
+        return getSetting(key, ListSetting.create(def), ListSetting.class).getValue();
     }
 
@@ -862,10 +958,6 @@
      */
     public Collection<String> getCollection(String key) {
-        putCollectionDefault(key, null);
-        Collection<String> prop = collectionProperties.get(key);
-        if (prop != null)
-            return prop;
-        else
-            return Collections.emptyList();
+        Collection<String> val = getCollection(key, null);
+        return val == null ? Collections.<String>emptyList() : val;
     }
 
@@ -876,23 +968,32 @@
     }
 
-    public boolean putCollection(String key, Collection<String> value) {
-        List<String> oldValue = null;
-        List<String> valueCopy = null;
-
+    /**
+     * Set a value for a certain setting. The changed setting is saved
+     * to the preference file immediately. Due to caching mechanisms on modern
+     * operating systems and hardware, this shouldn't be a performance problem.
+     * @param key the unique identifier for the setting
+     * @param setting the value of the setting. In case it is null, the key-value
+     * entry will be removed.
+     * @return true, if something has changed (i.e. value is different than before)
+     */
+    public boolean putSetting(final String key, Setting setting) {
+        CheckParameterUtil.ensureParameterNotNull(key);
+        if (setting != null && setting.getValue() == null)
+            throw new IllegalArgumentException("setting argument must not have null value");
+        Setting settingOld;
+        Setting settingCopy = null;
         synchronized (this) {
-            if (value == null) {
-                oldValue = collectionProperties.remove(key);
-                boolean changed = oldValue != null;
-                changed |= properties.remove(key) != null;
-                if (!changed) return false;
+            if (setting == null) {
+                settingOld = settingsMap.remove(key);
+                if (settingOld == null)
+                    return false;
             } else {
-                oldValue = collectionProperties.get(key);
-                if (equalCollection(value, oldValue)) return false;
-                Collection<String> defValue = collectionDefaults.get(key);
-                if (oldValue == null && equalCollection(value, defValue)) return false;
-
-                valueCopy = new ArrayList<String>(value);
-                if (valueCopy.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting (key '"+key+"')");
-                collectionProperties.put(key, Collections.unmodifiableList(valueCopy));
+                settingOld = settingsMap.get(key);
+                if (setting.equals(settingOld))
+                    return false;
+                if (settingOld == null && setting.equals(defaultsMap.get(key)))
+                    return false;
+                settingCopy = setting.copy();
+                settingsMap.put(key, settingCopy);
             }
             try {
@@ -903,20 +1004,42 @@
         }
         // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
-        firePreferenceChanged(key, new ListSetting(oldValue), new ListSetting(valueCopy));
+        firePreferenceChanged(key, settingOld, settingCopy);
         return true;
     }
 
-    public static boolean equalCollection(Collection<String> a, Collection<String> b) {
-        if (a == null) return b == null;
-        if (b == null) return false;
-        if (a.size() != b.size()) return false;
-        Iterator<String> itA = a.iterator();
-        Iterator<String> itB = b.iterator();
-        while (itA.hasNext()) {
-            String aStr = itA.next();
-            String bStr = itB.next();
-            if (!Utils.equal(aStr,bStr)) return false;
-        }
-        return true;
+    synchronized public Setting getSetting(String key, Setting def) {
+        return getSetting(key, def, Setting.class);
+    }
+
+    /**
+     * Get settings value for a certain key and provide default a value.
+     * @param <T> the setting type
+     * @param key the identifier for the setting
+     * @param def the default value. For each call of getSetting() with a given
+     * key, the default value must be the same. <code>def</code> must not be
+     * null, but the value of <code>def</code> can be null.
+     * @param klass the setting type (same as T)
+     * @return the corresponding value if the property has been set before,
+     *  def otherwise
+     */
+    synchronized public <T extends Setting> T getSetting(String key, T def, Class<T> klass) {
+        CheckParameterUtil.ensureParameterNotNull(key);
+        CheckParameterUtil.ensureParameterNotNull(def);
+        if (defaultsMap.containsKey(key) && !def.equals(defaultsMap.get(key))) {
+            Main.info("Defaults for " + key + " differ: " + def + " != " + defaultsMap.get(key));
+        }
+        defaultsMap.put(key, def.copy());
+        Setting prop = settingsMap.get(key);
+        if (klass.isInstance(prop)) {
+            @SuppressWarnings("unchecked")
+            T prop_cast = (T) prop;
+            return prop_cast;
+        } else {
+            return def;
+        }
+    }
+
+    public boolean putCollection(String key, Collection<String> value) {
+        return putSetting(key, value == null ? null : ListSetting.create(value));
     }
 
@@ -935,169 +1058,30 @@
     }
 
-    synchronized private void putCollectionDefault(String key, List<String> val) {
-        collectionDefaults.put(key, val);
-    }
-
     /**
      * Used to read a 2-dimensional array of strings from the preference file.
-     * If not a single entry could be found, def is returned.
+     * If not a single entry could be found, <code>def</code> is returned.
      */
     synchronized public Collection<Collection<String>> getArray(String key, Collection<Collection<String>> def) {
-        if (def != null) {
-            List<List<String>> defCopy = new ArrayList<List<String>>(def.size());
-            for (Collection<String> lst : def) {
-                defCopy.add(Collections.unmodifiableList(new ArrayList<String>(lst)));
-            }
-            putArrayDefault(key, Collections.unmodifiableList(defCopy));
-        } else {
-            putArrayDefault(key, null);
-        }
-        List<List<String>> prop = arrayProperties.get(key);
-        if (prop != null) {
-            @SuppressWarnings({ "unchecked", "rawtypes" })
-            Collection<Collection<String>> prop_cast = (Collection) prop;
-            return prop_cast;
-        } else
-            return def;
+        ListListSetting val = getSetting(key, ListListSetting.create(def), ListListSetting.class);
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        Collection<Collection<String>> val_cast = (Collection) val.getValue();
+        return val_cast;
     }
 
     public Collection<Collection<String>> getArray(String key) {
-        putArrayDefault(key, null);
-        List<List<String>> prop = arrayProperties.get(key);
-        if (prop != null) {
-            @SuppressWarnings({ "unchecked", "rawtypes" })
-            Collection<Collection<String>> prop_cast = (Collection) prop;
-            return prop_cast;
-        } else
-            return Collections.emptyList();
+        Collection<Collection<String>> res = getArray(key, null);
+        return res == null ? Collections.<Collection<String>>emptyList() : res;
     }
 
     public boolean putArray(String key, Collection<Collection<String>> value) {
-        List<List<String>> oldValue = null;
-        List<List<String>> valueCopy = null;
-
-        synchronized (this) {
-            oldValue = arrayProperties.get(key);
-            if (value == null) {
-                if (arrayProperties.remove(key) != null) return false;
-            } else {
-                if (equalArray(value, oldValue)) return false;
-
-                List<List<String>> defValue = arrayDefaults.get(key);
-                if (oldValue == null && equalArray(value, defValue)) return false;
-
-                valueCopy = new ArrayList<List<String>>(value.size());
-                if (valueCopy.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting (key '"+key+"')");
-                for (Collection<String> lst : value) {
-                    List<String> lstCopy = new ArrayList<String>(lst);
-                    if (lstCopy.contains(null)) throw new RuntimeException("Error: Null as inner list element in preference setting (key '"+key+"')");
-                    valueCopy.add(Collections.unmodifiableList(lstCopy));
-                }
-                arrayProperties.put(key, Collections.unmodifiableList(valueCopy));
-            }
-            try {
-                save();
-            } catch (IOException e){
-                Main.warn(tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()));
-            }
-        }
-        // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
-        firePreferenceChanged(key, new ListListSetting(oldValue), new ListListSetting(valueCopy));
-        return true;
-    }
-
-    public static boolean equalArray(Collection<Collection<String>> a, Collection<List<String>> b) {
-        if (a == null) return b == null;
-        if (b == null) return false;
-        if (a.size() != b.size()) return false;
-        Iterator<Collection<String>> itA = a.iterator();
-        Iterator<List<String>> itB = b.iterator();
-        while (itA.hasNext()) {
-            if (!equalCollection(itA.next(), itB.next())) return false;
-        }
-        return true;
-    }
-
-    synchronized private void putArrayDefault(String key, List<List<String>> val) {
-        arrayDefaults.put(key, val);
+        return putSetting(key, value == null ? null : ListListSetting.create(value));
     }
 
     public Collection<Map<String, String>> getListOfStructs(String key, Collection<Map<String, String>> def) {
-        if (def != null) {
-            List<Map<String, String>> defCopy = new ArrayList<Map<String, String>>(def.size());
-            for (Map<String, String> map : def) {
-                defCopy.add(Collections.unmodifiableMap(new LinkedHashMap<String,String>(map)));
-            }
-            putListOfStructsDefault(key, Collections.unmodifiableList(defCopy));
-        } else {
-            putListOfStructsDefault(key, null);
-        }
-        Collection<Map<String, String>> prop = listOfStructsProperties.get(key);
-        if (prop != null)
-            return prop;
-        else
-            return def;
+        return getSetting(key, new MapListSetting(def == null ? null : new ArrayList<Map<String, String>>(def)), MapListSetting.class).getValue();
     }
 
     public boolean putListOfStructs(String key, Collection<Map<String, String>> value) {
-
-        List<Map<String, String>> oldValue;
-        List<Map<String, String>> valueCopy = null;
-
-        synchronized (this) {
-            oldValue = listOfStructsProperties.get(key);
-            if (value == null) {
-                if (listOfStructsProperties.remove(key) != null) return false;
-            } else {
-                if (equalListOfStructs(oldValue, value)) return false;
-
-                List<Map<String, String>> defValue = listOfStructsDefaults.get(key);
-                if (oldValue == null && equalListOfStructs(value, defValue)) return false;
-
-                valueCopy = new ArrayList<Map<String, String>>(value.size());
-                if (valueCopy.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting (key '"+key+"')");
-                for (Map<String, String> map : value) {
-                    Map<String, String> mapCopy = new LinkedHashMap<String,String>(map);
-                    if (mapCopy.keySet().contains(null)) throw new RuntimeException("Error: Null as map key in preference setting (key '"+key+"')");
-                    if (mapCopy.values().contains(null)) throw new RuntimeException("Error: Null as map value in preference setting (key '"+key+"')");
-                    valueCopy.add(Collections.unmodifiableMap(mapCopy));
-                }
-                listOfStructsProperties.put(key, Collections.unmodifiableList(valueCopy));
-            }
-            try {
-                save();
-            } catch (IOException e) {
-                Main.warn(tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()));
-            }
-        }
-        // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
-        firePreferenceChanged(key, new MapListSetting(oldValue), new MapListSetting(valueCopy));
-        return true;
-    }
-
-    public static boolean equalListOfStructs(Collection<Map<String, String>> a, Collection<Map<String, String>> b) {
-        if (a == null) return b == null;
-        if (b == null) return false;
-        if (a.size() != b.size()) return false;
-        Iterator<Map<String, String>> itA = a.iterator();
-        Iterator<Map<String, String>> itB = b.iterator();
-        while (itA.hasNext()) {
-            if (!equalMap(itA.next(), itB.next())) return false;
-        }
-        return true;
-    }
-
-    private static boolean equalMap(Map<String, String> a, Map<String, String> b) {
-        if (a == null) return b == null;
-        if (b == null) return false;
-        if (a.size() != b.size()) return false;
-        for (Entry<String, String> e : a.entrySet()) {
-            if (!Utils.equal(e.getValue(), b.get(e.getKey()))) return false;
-        }
-        return true;
-    }
-
-    synchronized private void putListOfStructsDefault(String key, List<Map<String, String>> val) {
-        listOfStructsDefaults.put(key, val);
+        return putSetting(key, value == null ? null : new MapListSetting(new ArrayList<Map<String, String>>(value)));
     }
 
@@ -1255,66 +1239,10 @@
     }
 
-    public boolean putSetting(final String key, Setting value) {
-        if (value == null) return false;
-        class PutVisitor implements SettingVisitor {
-            public boolean changed;
-            @Override
-            public void visit(StringSetting setting) {
-                changed = put(key, setting.getValue());
-            }
-            @Override
-            public void visit(ListSetting setting) {
-                changed = putCollection(key, setting.getValue());
-            }
-            @Override
-            public void visit(ListListSetting setting) {
-                @SuppressWarnings("unchecked")
-                boolean changed = putArray(key, (Collection) setting.getValue());
-                this.changed = changed;
-            }
-            @Override
-            public void visit(MapListSetting setting) {
-                changed = putListOfStructs(key, setting.getValue());
-            }
-        }
-        PutVisitor putVisitor = new PutVisitor();
-        value.visit(putVisitor);
-        return putVisitor.changed;
-    }
-
     public Map<String, Setting> getAllSettings() {
-        Map<String, Setting> settings = new TreeMap<String, Setting>();
-
-        for (Entry<String, String> e : properties.entrySet()) {
-            settings.put(e.getKey(), new StringSetting(e.getValue()));
-        }
-        for (Entry<String, List<String>> e : collectionProperties.entrySet()) {
-            settings.put(e.getKey(), new ListSetting(e.getValue()));
-        }
-        for (Entry<String, List<List<String>>> e : arrayProperties.entrySet()) {
-            settings.put(e.getKey(), new ListListSetting(e.getValue()));
-        }
-        for (Entry<String, List<Map<String, String>>> e : listOfStructsProperties.entrySet()) {
-            settings.put(e.getKey(), new MapListSetting(e.getValue()));
-        }
-        return settings;
+        return new TreeMap<String, Setting>(settingsMap);
     }
 
     public Map<String, Setting> getAllDefaults() {
-        Map<String, Setting> allDefaults = new TreeMap<String, Setting>();
-
-        for (Entry<String, String> e : defaults.entrySet()) {
-            allDefaults.put(e.getKey(), new StringSetting(e.getValue()));
-        }
-        for (Entry<String, List<String>> e : collectionDefaults.entrySet()) {
-            allDefaults.put(e.getKey(), new ListSetting(e.getValue()));
-        }
-        for (Entry<String, List<List<String>>> e : arrayDefaults.entrySet()) {
-            allDefaults.put(e.getKey(), new ListListSetting(e.getValue()));
-        }
-        for (Entry<String, List<Map<String, String>>> e : listOfStructsDefaults.entrySet()) {
-            allDefaults.put(e.getKey(), new MapListSetting(e.getValue()));
-        }
-        return allDefaults;
+        return new TreeMap<String, Setting>(defaultsMap);
     }
 
@@ -1414,5 +1342,5 @@
             if (event == XMLStreamConstants.START_ELEMENT) {
                 if (parser.getLocalName().equals("tag")) {
-                    properties.put(parser.getAttributeValue(null, "key"), parser.getAttributeValue(null, "value"));
+                    settingsMap.put(parser.getAttributeValue(null, "key"), new StringSetting(parser.getAttributeValue(null, "value")));
                     jumpToEnd();
                 } else if (parser.getLocalName().equals("list") ||
@@ -1476,16 +1404,16 @@
         }
         if (entries != null) {
-            collectionProperties.put(key, Collections.unmodifiableList(entries));
+            settingsMap.put(key, new ListSetting(Collections.unmodifiableList(entries)));
         } else if (lists != null) {
-            arrayProperties.put(key, Collections.unmodifiableList(lists));
+            settingsMap.put(key, new ListListSetting(Collections.unmodifiableList(lists)));
         } else if (maps != null) {
-            listOfStructsProperties.put(key, Collections.unmodifiableList(maps));
+            settingsMap.put(key, new MapListSetting(Collections.unmodifiableList(maps)));
         } else {
             if (name.equals("lists")) {
-                arrayProperties.put(key, Collections.<List<String>>emptyList());
+                settingsMap.put(key, new ListListSetting(Collections.<List<String>>emptyList()));
             } else if (name.equals("maps")) {
-                listOfStructsProperties.put(key, Collections.<Map<String, String>>emptyList());
+                settingsMap.put(key, new MapListSetting(Collections.<Map<String, String>>emptyList()));
             } else {
-                collectionProperties.put(key, Collections.<String>emptyList());
+                settingsMap.put(key, new ListSetting(Collections.<String>emptyList()));
             }
         }
@@ -1550,14 +1478,12 @@
             if (noPassword && key.equals("osm-server.password"))
                 return; // do not store plain password.
-            String r = setting.getValue();
-            String s = defaults.get(key);
             /* don't save default values */
-            if(s == null || !s.equals(r)) {
-                b.append("  <tag key='");
-                b.append(XmlWriter.encode(key));
-                b.append("' value='");
-                b.append(XmlWriter.encode(setting.getValue()));
-                b.append("'/>\n");
-            }
+            if (setting.equals(defaultsMap.get(key)))
+                return;
+            b.append("  <tag key='");
+            b.append(XmlWriter.encode(key));
+            b.append("' value='");
+            b.append(XmlWriter.encode(setting.getValue()));
+            b.append("'/>\n");
         }
 
@@ -1604,19 +1530,5 @@
                 Version.getInstance().getVersion() + "\">\n");
         SettingToXml toXml = new SettingToXml(b, nopass);
-        Map<String, Setting<?>> settings = new TreeMap<String, Setting<?>>();
-
-        for (Entry<String, String> e : properties.entrySet()) {
-            settings.put(e.getKey(), new StringSetting(e.getValue()));
-        }
-        for (Entry<String, List<String>> e : collectionProperties.entrySet()) {
-            settings.put(e.getKey(), new ListSetting(e.getValue()));
-        }
-        for (Entry<String, List<List<String>>> e : arrayProperties.entrySet()) {
-            settings.put(e.getKey(), new ListListSetting(e.getValue()));
-        }
-        for (Entry<String, List<Map<String, String>>> e : listOfStructsProperties.entrySet()) {
-            settings.put(e.getKey(), new MapListSetting(e.getValue()));
-        }
-        for (Entry<String, Setting<?>> e : settings.entrySet()) {
+        for (Entry<String, Setting> e : settingsMap.entrySet()) {
             toXml.setKey(e.getKey());
             e.getValue().visit(toXml);
@@ -1648,10 +1560,6 @@
         };
         for (String key : obsolete) {
-            boolean removed = false;
-            if (properties.containsKey(key)) { properties.remove(key); removed = true; }
-            if (collectionProperties.containsKey(key)) { collectionProperties.remove(key); removed = true; }
-            if (arrayProperties.containsKey(key)) { arrayProperties.remove(key); removed = true; }
-            if (listOfStructsProperties.containsKey(key)) { listOfStructsProperties.remove(key); removed = true; }
-            if (removed) {
+            if (settingsMap.containsKey(key)) {
+                settingsMap.remove(key);
                 Main.info(tr("Preference setting {0} has been removed since it is no longer used.", key));
             }
@@ -1659,27 +1567,6 @@
     }
 
-    public static boolean isEqual(Setting<?> a, Setting<?> b) {
-        if (a==null && b==null) return true;
-        if (a==null) return false;
-        if (b==null) return false;
-        if (a==b) return true;
-
-        if (a instanceof StringSetting)
-            return (a.getValue().equals(b.getValue()));
-        if (a instanceof ListSetting) {
-            @SuppressWarnings("unchecked") Collection<String> aValue = (Collection<String>) a.getValue();
-            @SuppressWarnings("unchecked") Collection<String> bValue = (Collection<String>) b.getValue();
-            return equalCollection(aValue, bValue);
-        }
-        if (a instanceof ListListSetting) {
-            @SuppressWarnings("unchecked") Collection<Collection<String>> aValue = (Collection<Collection<String>>) a.getValue();
-            @SuppressWarnings("unchecked") Collection<List<String>> bValue = (Collection<List<String>>) b.getValue();
-            return equalArray(aValue, bValue);
-        }
-        if (a instanceof MapListSetting) {
-            @SuppressWarnings("unchecked") Collection<Map<String, String>> aValue = (Collection<Map<String, String>>) a.getValue();
-            @SuppressWarnings("unchecked") Collection<Map<String, String>> bValue = (Collection<Map<String, String>>) b.getValue();
-            return equalListOfStructs(aValue, bValue);
-        }
+    public static boolean isEqual(Setting a, Setting b) {
+        if (a == null) return b == null;
         return a.equals(b);
     }
Index: trunk/src/org/openstreetmap/josm/data/ServerSidePreferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/ServerSidePreferences.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/data/ServerSidePreferences.java	(revision 6578)
@@ -151,9 +151,9 @@
 
     public void download(String userName, String password) {
-        if (!properties.containsKey("applet.username") && userName != null) {
-            properties.put("applet.username", userName);
+        if (!settingsMap.containsKey("applet.username") && userName != null) {
+            settingsMap.put("applet.username", new StringSetting(userName));
         }
-        if (!properties.containsKey("applet.password") && password != null) {
-            properties.put("applet.password", password);
+        if (!settingsMap.containsKey("applet.password") && password != null) {
+            settingsMap.put("applet.password", new StringSetting(password));
         }
         try {
Index: trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 6578)
@@ -398,5 +398,5 @@
         Main.MasterWindowListener.setup();
 
-        boolean maximized = Boolean.parseBoolean(Main.pref.get("gui.maximized"));
+        boolean maximized = Main.pref.getBoolean("gui.maximized", false);
         if ((!args.containsKey(Option.NO_MAXIMIZE) && maximized) || args.containsKey(Option.MAXIMIZE)) {
             if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java	(revision 6578)
@@ -436,5 +436,5 @@
         for (PrefEntry e : allData) {
             if (e.isChanged()) {
-                Main.pref.putSetting(e.getKey(), e.getValue());
+                Main.pref.putSetting(e.getKey(), e.getValue().getValue() == null ? null : e.getValue());
             }
         }
Index: trunk/src/org/openstreetmap/josm/gui/preferences/advanced/PreferencesTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/advanced/PreferencesTable.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/advanced/PreferencesTable.java	(revision 6578)
@@ -1,4 +1,7 @@
 // License: GPL. See LICENSE file for details.
 package org.openstreetmap.josm.gui.preferences.advanced;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.Color;
@@ -12,4 +15,5 @@
 import java.util.List;
 import java.util.Map;
+
 import javax.swing.ButtonGroup;
 import javax.swing.DefaultCellEditor;
@@ -22,4 +26,5 @@
 import javax.swing.table.DefaultTableCellRenderer;
 import javax.swing.table.DefaultTableModel;
+
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Preferences;
@@ -27,6 +32,4 @@
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.tools.GBC;
-import static org.openstreetmap.josm.tools.I18n.marktr;
-import static org.openstreetmap.josm.tools.I18n.tr;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -106,5 +109,5 @@
             if (lEditor.getValue() == 1) {
                 List<String> data = lEditor.getData();
-                if (!Preferences.equalCollection(lSetting.getValue(), data)) {
+                if (!lSetting.equalVal(data)) {
                     e.setValue(new Preferences.ListSetting(data));
                     return true;
@@ -112,5 +115,6 @@
             }
         } else if (stg instanceof Preferences.ListListSetting) {
-            ListListEditor llEditor = new ListListEditor(gui, e, (Preferences.ListListSetting) stg);
+            Preferences.ListListSetting llSetting = (Preferences.ListListSetting) stg;
+            ListListEditor llEditor = new ListListEditor(gui, e, llSetting);
             llEditor.showDialog();
             if (llEditor.getValue() == 1) {
@@ -118,5 +122,5 @@
                 @SuppressWarnings("unchecked")
                 Collection<Collection<String>> stgValue = (Collection<Collection<String>>) stg.getValue();
-                if (!Preferences.equalArray(stgValue, data)) {
+                if (!llSetting.equalVal(data)) {
                     e.setValue(new Preferences.ListListSetting(data));
                     return true;
@@ -129,5 +133,5 @@
             if (mlEditor.getValue() == 1) {
                 List<Map<String, String>> data = mlEditor.getData();
-                if (!Preferences.equalListOfStructs(mlSetting.getValue(), data)) {
+                if (!mlSetting.equalVal(data)) {
                     e.setValue(new Preferences.MapListSetting(data));
                     return true;
@@ -196,5 +200,5 @@
                 if (lEditor.getValue() == 1) {
                     List<String> data = lEditor.getData();
-                    if (!Preferences.equalCollection(lSetting.getValue(), data)) {
+                    if (!lSetting.equalVal(data)) {
                         pe.setValue(new Preferences.ListSetting(data));
                         ok = true;
@@ -208,7 +212,5 @@
                 if (llEditor.getValue() == 1) {
                     List<List<String>> data = llEditor.getData();
-                    @SuppressWarnings("unchecked")
-                    Collection<Collection<String>> llSettingValue = (Collection) llSetting.getValue();
-                    if (!Preferences.equalArray(llSettingValue, data)) {
+                    if (!llSetting.equalVal(data)) {
                         pe.setValue(new Preferences.ListListSetting(data));
                         ok = true;
@@ -222,5 +224,5 @@
                 if (mlEditor.getValue() == 1) {
                     List<Map<String, String>> data = mlEditor.getData();
-                    if (!Preferences.equalListOfStructs(mlSetting.getValue(), data)) {
+                    if (!mlSetting.equalVal(data)) {
                         pe.setValue(new Preferences.MapListSetting(data));
                         ok = true;
Index: trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 6577)
+++ trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 6578)
@@ -857,3 +857,21 @@
         }
     }
+
+    /**
+     * Cast an object savely.
+     * @param <T> the target type
+     * @param o the object to cast
+     * @param klass the target class (same as T)
+     * @return null if <code>o</code> is null or the type <code>o</code> is not
+     *  a subclass of <code>klass</code>. The casted value otherwise.
+     */
+    public static <T> T cast(Object o, Class<T> klass) {
+        if (klass.isInstance(o)) {
+            @SuppressWarnings("unchecked")
+            T ret = (T) o;
+            return ret;
+        }
+        return null;
+    }
+
 }
