Index: trunk/src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 3907)
+++ trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 3908)
@@ -13,4 +13,7 @@
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
@@ -736,4 +739,161 @@
     }
 
+    @Retention(RetentionPolicy.RUNTIME) public @interface pref { }
+    @Retention(RetentionPolicy.RUNTIME) public @interface writeExplicitly { }
+
+    /**
+     * Get a list of hashes which are represented by a struct-like class.
+     * It reads lines of the form
+     *  > key.0=prop:val \u001e prop:val \u001e ... \u001e prop:val
+     *  > ...
+     *  > key.N=prop:val \u001e prop:val \u001e ... \u001e prop:val
+     * Possible properties are given by fields of the class klass that have
+     * the @pref annotation.
+     * Default constructor is used to initialize the struct objects, properties
+     * then override some of these default values.
+     * @param key main preference key
+     * @param klass The struct class
+     * @return a list of objects of type T or an empty list if nothing was found
+     */
+    public <T> List<T> getListOfStructs(String key, Class<T> klass) {
+        List<T> r = getListOfStructs(key, null, klass);
+        if (r == null)
+            return Collections.emptyList();
+        else
+            return r;
+    }
+
+    /**
+     * same as above, but returns def if nothing was found
+     */
+    public <T> List<T> getListOfStructs(String key, Collection<T> def, Class<T> klass) {
+        Collection<Collection<String>> array =
+                getArray(key, def == null ? null : serializeListOfStructs(def, klass));
+        if (array == null)
+            return def == null ? null : new ArrayList<T>(def);
+        List<T> lst = new ArrayList<T>();
+        for (Collection<String> entries : array) {
+            T struct = deserializeStruct(entries, klass);
+            lst.add(struct);
+        }
+        return lst;
+    }
+
+    /**
+     * Save a list of hashes represented by a struct-like class.
+     * Considers only fields that have the @pref annotation.
+     * In addition it does not write fields with null values. (Thus they are cleared)
+     * Default values are given by the field values after default constructor has
+     * been called.
+     * Fields equal to the default value are not written unless the field has
+     * the @writeExplicitly annotation.
+     * @param key main preference key
+     * @param val the list that is supposed to be saved
+     * @param klass The struct class
+     * @return true if something has changed
+     */
+    public <T> boolean putListOfStructs(String key, Collection<T> val, Class<T> klass) {
+        return putArray(key, serializeListOfStructs(val, klass));
+    }
+
+    private <T> Collection<Collection<String>> serializeListOfStructs(Collection<T> l, Class<T> klass) {
+        if (l == null)
+            return null;
+        Collection<Collection<String>> vals = new ArrayList<Collection<String>>();
+        for (T struct : l) {
+            if (struct == null)
+                continue;
+            vals.add(serializeStruct(struct, klass));
+        }
+        return vals;
+    }
+
+    private <T> Collection<String> serializeStruct(T struct, Class<T> klass) {
+        T structPrototype;
+        try {
+            structPrototype = klass.newInstance();
+        } catch (InstantiationException ex) {
+            throw new RuntimeException();
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException();
+        }
+
+        Collection<String> hash = new ArrayList<String>();
+        for (Field f : klass.getDeclaredFields()) {
+            if (f.getAnnotation(pref.class) == null) {
+                continue;
+            }
+            f.setAccessible(true);
+            try {
+                Object fieldValue = f.get(struct);
+                Object defaultFieldValue = f.get(structPrototype);
+                if (fieldValue != null) {
+                    if (f.getAnnotation(writeExplicitly.class) != null || !Utils.equal(fieldValue, defaultFieldValue)) {
+                        hash.add(String.format("%s:%s", f.getName().replace("_", "-"), fieldValue.toString()));
+                    }
+                }
+            } catch (IllegalArgumentException ex) {
+                throw new RuntimeException();
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException();
+            }
+        }
+        return hash;
+    }
+
+    private <T> T deserializeStruct(Collection<String> hash, Class<T> klass) {
+        T struct = null;
+        try {
+            struct = klass.newInstance();
+        } catch (InstantiationException ex) {
+            throw new RuntimeException();
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException();
+        }
+        for (String key_value : hash) {
+            final int i = key_value.indexOf(':');
+            if (i == -1 || i == 0) {
+                continue;
+            }
+            String key = key_value.substring(0,i);
+            String valueString = key_value.substring(i+1);
+
+            Object value = null;
+            Field f;
+            try {
+                f = klass.getDeclaredField(key.replace("-", "_"));
+            } catch (NoSuchFieldException ex) {
+                continue;
+            } catch (SecurityException ex) {
+                throw new RuntimeException();
+            }
+            if (f.getAnnotation(pref.class) == null) {
+                continue;
+            }
+            f.setAccessible(true);
+            if (f.getType() == Boolean.class || f.getType() == boolean.class) {
+                value = Boolean.parseBoolean(valueString);
+            } else if (f.getType() == Integer.class || f.getType() == int.class) {
+                try {
+                    value = Integer.parseInt(valueString);
+                } catch (NumberFormatException nfe) {
+                    continue;
+                }
+            } else  if (f.getType() == String.class) {
+                value = valueString;
+            } else
+                throw new RuntimeException("unsupported preference primitive type");
+            
+            try {
+                f.set(struct, value);
+            } catch (IllegalArgumentException ex) {
+                throw new AssertionError();
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException();
+            }
+        }
+        return struct;
+    }
+
     /**
      * Updates system properties with the current values in the preferences.
Index: trunk/src/org/openstreetmap/josm/data/osm/Filter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Filter.java	(revision 3907)
+++ trunk/src/org/openstreetmap/josm/data/osm/Filter.java	(revision 3908)
@@ -2,6 +2,11 @@
 package org.openstreetmap.josm.data.osm;
 
+import static org.openstreetmap.josm.tools.Utils.equal;
+
 import org.openstreetmap.josm.actions.search.SearchAction.SearchMode;
 import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
+import org.openstreetmap.josm.data.Preferences.pref;
+import org.openstreetmap.josm.data.Preferences.writeExplicitly;
+import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -24,4 +29,5 @@
     }
 
+    @Deprecated
     public Filter(String prefText) {
         super("", SearchMode.add, false, false, false);
@@ -49,10 +55,44 @@
     }
 
-    public String getPrefString(){
-        return version + ";" +
-        text + ";" + mode + ";" + caseSensitive + ";" + regexSearch + ";" +
-        "legacy" + ";" + enable + ";" + hiding + ";" +
-        inverted + ";" +
-        "false"; // last parameter is not used any more (was: applyForChildren)
+    public Filter(FilterPreferenceEntry e) {
+        super(e.text, SearchMode.add, false, false, false);
+        if (equal(e.mode, "replace")) {
+            mode = SearchMode.replace;
+        } else if (equal(e.mode, "add")) {
+            mode = SearchMode.add;
+        } else if (equal(e.mode, "remove")) {
+            mode = SearchMode.remove;
+        } else  if (equal(e.mode, "in_selection")) {
+            mode = SearchMode.in_selection;
+        }
+        caseSensitive = e.case_sensitive;
+        regexSearch = e.regex_search;
+        enable = e.enable;
+        hiding = e.hiding;
+        inverted = e.inverted;
+    }
+
+    public static class FilterPreferenceEntry {
+        @pref @writeExplicitly public String version = "1";
+        @pref public String text = null;
+        @pref @writeExplicitly public String mode = "add";
+        @pref public boolean case_sensitive = false;
+        @pref public boolean regex_search = false;
+        @pref @writeExplicitly public boolean enable = true;
+        @pref @writeExplicitly public boolean hiding = false;
+        @pref @writeExplicitly public boolean inverted = false;
+    }
+
+    public FilterPreferenceEntry getPreferenceEntry() {
+        FilterPreferenceEntry e = new FilterPreferenceEntry();
+        e.version = version;
+        e.text = text;
+        e.mode = mode.toString();
+        e.case_sensitive = caseSensitive;
+        e.regex_search = regexSearch;
+        e.enable = enable;
+        e.hiding = hiding;
+        e.inverted = inverted;
+        return e;
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java	(revision 3907)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java	(revision 3908)
@@ -25,4 +25,5 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Filter;
+import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry;
 import org.openstreetmap.josm.data.osm.FilterMatcher;
 import org.openstreetmap.josm.data.osm.FilterWorker;
@@ -60,6 +61,4 @@
         }
     }
-
-
 
     public void executeFilters() {
@@ -110,5 +109,4 @@
     }
 
-
     public void executeFilters(Collection<? extends OsmPrimitive> primitives) {
         DataSet ds = Main.main.getCurrentDataSet();
@@ -174,4 +172,22 @@
 
     private void loadPrefs() {
+        if (!loadPrefsImpl()) {
+            loadPrefsOld();
+            savePrefs();
+        }
+    }
+
+    private boolean loadPrefsImpl() {
+        List<FilterPreferenceEntry> entries = Main.pref.getListOfStructs("filters.entries", null, FilterPreferenceEntry.class);
+        if (entries == null)
+            return false;
+        for (FilterPreferenceEntry e : entries) {
+            filters.add(new Filter(e));
+        }
+        return true;
+    }
+
+    @Deprecated
+    private void loadPrefsOld() {
         Map<String, String> prefs = Main.pref.getAllPrefix("filters.filter");
         for (String value : prefs.values()) {
@@ -182,29 +198,14 @@
 
     private void savePrefs() {
-        Map<String, String> prefs = Main.pref.getAllPrefix("filters.filter");
-        for (String key : prefs.keySet()) {
-            String[] sts = key.split("\\.");
-            if (sts.length != 3)
-                throw new Error("Incompatible filter preferences");
-            Main.pref.put("filters.filter." + sts[2], null);
-        }
-
-        int i = 0;
+        Collection<FilterPreferenceEntry> entries = new ArrayList<FilterPreferenceEntry>();
         for (Filter flt : filters) {
-            Main.pref.put("filters.filter." + i++, flt.getPrefString());
-        }
-    }
-
-    private void savePref(int i) {
-        if (i >= filters.size()) {
-            Main.pref.put("filters.filter." + i, null);
-        } else {
-            Main.pref.put("filters.filter." + i, filters.get(i).getPrefString());
-        }
+            entries.add(flt.getPreferenceEntry());
+        }
+        Main.pref.putListOfStructs("filters.entries", entries, FilterPreferenceEntry.class);
     }
 
     public void addFilter(Filter f) {
         filters.add(f);
-        savePref(filters.size() - 1);
+        savePrefs();
         updateFilters();
         fireTableRowsInserted(filters.size() - 1, filters.size() - 1);
@@ -215,6 +216,5 @@
             return;
         filters.add(i + 1, filters.remove(i));
-        savePref(i);
-        savePref(i + 1);
+        savePrefs();
         updateFilters();
         fireTableRowsUpdated(i, i + 1);
@@ -225,6 +225,5 @@
             return;
         filters.add(i - 1, filters.remove(i));
-        savePref(i);
-        savePref(i - 1);
+        savePrefs();
         updateFilters();
         fireTableRowsUpdated(i - 1, i);
@@ -240,5 +239,5 @@
     public void setFilter(int i, Filter f) {
         filters.set(i, f);
-        savePref(i);
+        savePrefs();
         updateFilters();
         fireTableRowsUpdated(i, i);
@@ -295,5 +294,5 @@
         case 0:
             f.enable = (Boolean) aValue;
-            savePref(row);
+            savePrefs();
             updateFilters();
             fireTableRowsUpdated(row, row);
@@ -301,14 +300,14 @@
         case 1:
             f.hiding = (Boolean) aValue;
-            savePref(row);
+            savePrefs();
             updateFilters();
             break;
         case 2:
             f.text = (String) aValue;
-            savePref(row);
+            savePrefs();
             break;
         case 3:
             f.inverted = (Boolean) aValue;
-            savePref(row);
+            savePrefs();
             updateFilters();
             break;
