Index: trunk/src/org/openstreetmap/josm/gui/mappaint/Cascade.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/Cascade.java	(revision 16183)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/Cascade.java	(revision 16184)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors;
 import org.openstreetmap.josm.tools.ColorHelper;
+import org.openstreetmap.josm.tools.GenericParser;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
@@ -26,4 +27,15 @@
 
     private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})");
+
+    private static final GenericParser<Object> GENERIC_PARSER = new GenericParser<>()
+            .registerParser(float.class, Cascade::toFloat)
+            .registerParser(Float.class, Cascade::toFloat)
+            .registerParser(double.class, Cascade::toDouble)
+            .registerParser(Double.class, Cascade::toDouble)
+            .registerParser(boolean.class, Cascade::toBool)
+            .registerParser(Boolean.class, Cascade::toBool)
+            .registerParser(float[].class, Cascade::toFloatArray)
+            .registerParser(Color.class, Cascade::toColor)
+            .registerParser(String.class, Cascade::toString);
 
     /**
@@ -137,39 +149,16 @@
             return (T) o;
 
-        if (klass == float.class || klass == Float.class)
-            return (T) toFloat(o);
-
-        if (klass == double.class || klass == Double.class) {
-            o = toFloat(o);
-            if (o != null) {
-                o = Double.valueOf((Float) o);
-            }
-            return (T) o;
-        }
-
-        if (klass == boolean.class || klass == Boolean.class)
-            return (T) toBool(o);
-
-        if (klass == float[].class)
-            return (T) toFloatArray(o);
-
-        if (klass == Color.class)
-            return (T) toColor(o);
-
-        if (klass == String.class) {
-            if (o instanceof Keyword)
-                return (T) ((Keyword) o).val;
-            if (o instanceof Color) {
-                Color c = (Color) o;
-                int alpha = c.getAlpha();
-                if (alpha != 255)
-                    return (T) String.format("#%06x%02x", ((Color) o).getRGB() & 0x00ffffff, alpha);
-                return (T) String.format("#%06x", ((Color) o).getRGB() & 0x00ffffff);
-            }
-
-            return (T) o.toString();
-        }
-
-        return null;
+        return GENERIC_PARSER.supports(klass)
+                ? GENERIC_PARSER.parse(klass, o)
+                : null;
+    }
+
+    private static String toString(Object o) {
+        if (o instanceof Keyword)
+            return ((Keyword) o).val;
+        if (o instanceof Color) {
+            return ColorHelper.color2html((Color) o);
+        }
+        return o.toString();
     }
 
@@ -185,4 +174,9 @@
         }
         return null;
+    }
+
+    private static Double toDouble(Object o) {
+        final Float number = toFloat(o);
+        return number != null ? Double.valueOf(number) : null;
     }
 
Index: trunk/src/org/openstreetmap/josm/tools/GenericParser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/GenericParser.java	(revision 16184)
+++ trunk/src/org/openstreetmap/josm/tools/GenericParser.java	(revision 16184)
@@ -0,0 +1,90 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Utility class to parse various types from other values.
+ *
+ * @since 16184
+ */
+public class GenericParser<U> {
+
+    protected final Map<Class<?>, Function<U, ?>> parsers;
+
+    public GenericParser() {
+        this(new LinkedHashMap<>());
+    }
+
+    /**
+     * Creates a new {@code GenericParser} by deeply copying {@code parser}
+     *
+     * @param parser the parser to copy
+     */
+    public GenericParser(GenericParser<U> parser) {
+        this(new LinkedHashMap<>(parser.parsers));
+    }
+
+    protected GenericParser(Map<Class<?>, Function<U, ?>> parsers) {
+        this.parsers = parsers;
+    }
+
+    public <T> GenericParser<U> registerParser(Class<T> type, Function<U, T> value) {
+        parsers.put(type, value);
+        return this;
+    }
+
+    /**
+     * Determines whether {@code type} can be {@linkplain #parse parsed}
+     *
+     * @param type the type
+     * @return true if {@code type} can be parsed
+     */
+    public boolean supports(Class<?> type) {
+        return parsers.containsKey(type);
+    }
+
+    /**
+     * Parses the given {@code value} as {@code type} and returns the result
+     *
+     * @param type  the type class
+     * @param value the value to parse
+     * @param <T>   the type
+     * @return the parsed value for {@code string} as type {@code type}
+     * @throws UnsupportedOperationException if {@code type} is not {@linkplain #supports supported}
+     * @throws UncheckedParseException       when the parsing fails
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T parse(Class<T> type, U value) {
+        final Function<U, ?> parser = parsers.get(type);
+        if (parser == null) {
+            throw new UnsupportedOperationException(type + " is not supported");
+        }
+        try {
+            return (T) parser.apply(value);
+        } catch (RuntimeException ex) {
+            throw new UncheckedParseException("Failed to parse [" + value + "] as " + type, ex);
+        }
+    }
+
+    /**
+     * Tries to parse the given {@code value} as {@code type} and returns the result.
+     *
+     * @param type  the type class
+     * @param value the value to parse
+     * @param <T>   the type
+     * @return the parsed value for {@code value} as type {@code type},
+     * or {@code Optional.empty()} (if parsing fails, or the type is not {@linkplain #supports supported})
+     */
+    public <T> Optional<?> tryParse(Class<?> type, U value) {
+        try {
+            return Optional.ofNullable(parse(type, value));
+        } catch (RuntimeException ex) {
+            Logging.trace(ex);
+            return Optional.empty();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/tools/StringParser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/StringParser.java	(revision 16183)
+++ trunk/src/org/openstreetmap/josm/tools/StringParser.java	(revision 16184)
@@ -2,7 +2,5 @@
 package org.openstreetmap.josm.tools;
 
-import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Optional;
 import java.util.function.Function;
 
@@ -12,5 +10,5 @@
  * @since 16181
  */
-public class StringParser {
+public class StringParser extends GenericParser<String> {
 
     /**
@@ -38,8 +36,6 @@
             .parsers));
 
-    private final Map<Class<?>, Function<String, ?>> parsers;
-
     public StringParser() {
-        this(new LinkedHashMap<>());
+        super();
     }
 
@@ -50,65 +46,15 @@
      */
     public StringParser(StringParser parser) {
-        this(new LinkedHashMap<>(parser.parsers));
+        super(parser);
     }
 
-    private StringParser(Map<Class<?>, Function<String, ?>> parsers) {
-        this.parsers = parsers;
+    protected StringParser(Map<Class<?>, Function<String, ?>> parsers) {
+        super(parsers);
     }
 
+    @Override
     public <T> StringParser registerParser(Class<T> type, Function<String, T> value) {
-        parsers.put(type, value);
+        super.registerParser(type, value);
         return this;
     }
-
-    /**
-     * Determines whether {@code type} can be {@linkplain #parse parsed}
-     *
-     * @param type the type
-     * @return true if {@code type} can be parsed
-     */
-    public boolean supports(Class<?> type) {
-        return parsers.containsKey(type);
-    }
-
-    /**
-     * Parses the given {@code string} as {@code type} and returns the result
-     *
-     * @param type   the type class
-     * @param string the string to parse
-     * @param <T>    the type
-     * @return the parsed value for {@code string} as type {@code type}
-     * @throws UnsupportedOperationException if {@code type} is not {@linkplain #supports supported}
-     * @throws UncheckedParseException       when the parsing fails
-     */
-    @SuppressWarnings("unchecked")
-    public <T> T parse(Class<T> type, String string) {
-        final Function<String, ?> parser = parsers.get(type);
-        if (parser == null) {
-            throw new UnsupportedOperationException(type + " is not supported");
-        }
-        try {
-            return (T) parser.apply(string);
-        } catch (RuntimeException ex) {
-            throw new UncheckedParseException("Failed to parse [" + string + "] as " + type, ex);
-        }
-    }
-
-    /**
-     * Tries to parse the given {@code string} as {@code type} and returns the result.
-     *
-     * @param type   the type class
-     * @param string the string to parse
-     * @param <T>    the type
-     * @return the parsed value for {@code string} as type {@code type},
-     * or {@code Optional.empty()} (if parsing fails, or the type is not {@linkplain #supports supported})
-     */
-    public <T> Optional<?> tryParse(Class<?> type, String string) {
-        try {
-            return Optional.ofNullable(parse(type, string));
-        } catch (RuntimeException ex) {
-            Logging.trace(ex);
-            return Optional.empty();
-        }
-    }
 }
