001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint; 003 004import static org.openstreetmap.josm.tools.Utils.equal; 005 006import java.awt.Color; 007import java.util.Arrays; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011import java.util.regex.Pattern; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors; 015import org.openstreetmap.josm.tools.Utils; 016 017/** 018 * Simple map of properties with dynamic typing. 019 */ 020public final class Cascade implements Cloneable { 021 022 public static final Cascade EMPTY_CASCADE = new Cascade(); 023 024 protected Map<String, Object> prop = new HashMap<String, Object>(); 025 026 private final static Pattern HEX_COLOR_PATTERN = Pattern.compile("#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})"); 027 028 public <T> T get(String key, T def, Class<T> klass) { 029 return get(key, def, klass, false); 030 } 031 032 /** 033 * Get value for the given key 034 * @param <T> the expected type 035 * @param key the key 036 * @param def default value, can be null 037 * @param klass the same as T 038 * @param suppressWarnings show or don't show a warning when some value is 039 * found, but cannot be converted to the requested type 040 * @return if a value with class klass has been mapped to key, returns this 041 * value, def otherwise 042 */ 043 public <T> T get(String key, T def, Class<T> klass, boolean suppressWarnings) { 044 if (def != null && !klass.isInstance(def)) 045 throw new IllegalArgumentException(); 046 Object o = prop.get(key); 047 if (o == null) 048 return def; 049 T res = convertTo(o, klass); 050 if (res == null) { 051 if (!suppressWarnings) { 052 Main.warn(String.format("Unable to convert property %s to type %s: found %s of type %s!", key, klass, o, o.getClass())); 053 } 054 return def; 055 } else 056 return res; 057 } 058 059 public Object get(String key) { 060 return prop.get(key); 061 } 062 063 public void put(String key, Object val) { 064 prop.put(key, val); 065 } 066 067 public void putOrClear(String key, Object val) { 068 if (val != null) { 069 prop.put(key, val); 070 } else { 071 prop.remove(key); 072 } 073 } 074 075 public void remove(String key) { 076 prop.remove(key); 077 } 078 079 @SuppressWarnings("unchecked") 080 public static <T> T convertTo(Object o, Class<T> klass) { 081 if (o == null) 082 return null; 083 if (klass.isInstance(o)) 084 return (T) o; 085 086 if (klass == float.class || klass == Float.class) 087 return (T) toFloat(o); 088 089 if (klass == double.class || klass == Double.class) { 090 o = toFloat(o); 091 if (o != null) { 092 o = new Double((Float) o); 093 } 094 return (T) o; 095 } 096 097 if (klass == boolean.class || klass == Boolean.class) 098 return (T) toBool(o); 099 100 if (klass == float[].class) 101 return (T) toFloatArray(o); 102 103 if (klass == Color.class) 104 return (T) toColor(o); 105 106 if (klass == String.class) { 107 if (o instanceof Keyword) 108 return (T) ((Keyword) o).val; 109 110 return (T) o.toString(); 111 } 112 113 return null; 114 } 115 116 private static Float toFloat(Object o) { 117 if (o instanceof Float) 118 return (Float) o; 119 if (o instanceof Double) 120 return new Float((Double) o); 121 if (o instanceof Integer) 122 return new Float((Integer) o); 123 if (o instanceof String && !((String) o).isEmpty()) { 124 try { 125 return Float.parseFloat((String) o); 126 } catch (NumberFormatException e) { 127 Main.debug("'"+o+"' cannot be converted to float"); 128 } 129 } 130 return null; 131 } 132 133 private static Boolean toBool(Object o) { 134 if (o instanceof Boolean) 135 return (Boolean) o; 136 if (o instanceof Keyword) { 137 String s = ((Keyword) o).val; 138 if (equal(s, "true") || equal(s, "yes")) 139 return true; 140 if (equal(s, "false") || equal(s, "no")) 141 return false; 142 } 143 return null; 144 } 145 146 private static float[] toFloatArray(Object o) { 147 if (o instanceof float[]) 148 return (float[]) o; 149 if (o instanceof List) { 150 List<?> l = (List<?>) o; 151 float[] a = new float[l.size()]; 152 for (int i=0; i<l.size(); ++i) { 153 Float f = toFloat(l.get(i)); 154 if (f == null) 155 return null; 156 else 157 a[i] = f; 158 } 159 return a; 160 } 161 Float f = toFloat(o); 162 if (f != null) 163 return new float[] { f }; 164 return null; 165 } 166 167 private static Color toColor(Object o) { 168 if (o instanceof Color) 169 return (Color) o; 170 if (o instanceof Keyword) 171 return CSSColors.get(((Keyword) o).val); 172 if (o instanceof String) { 173 Color c = CSSColors.get((String) o); 174 if (c != null) 175 return c; 176 if (HEX_COLOR_PATTERN.matcher((String) o).matches()) { 177 return Utils.hexToColor((String) o); 178 } 179 } 180 return null; 181 } 182 183 @Override 184 public Cascade clone() { 185 @SuppressWarnings("unchecked") 186 HashMap<String, Object> clonedProp = (HashMap) ((HashMap) this.prop).clone(); 187 Cascade c = new Cascade(); 188 c.prop = clonedProp; 189 return c; 190 } 191 192 @Override 193 public String toString() { 194 StringBuilder res = new StringBuilder("Cascade{ "); 195 for (String key : prop.keySet()) { 196 res.append(key+":"); 197 Object val = prop.get(key); 198 if (val instanceof float[]) { 199 res.append(Arrays.toString((float[]) val)); 200 } else if (val instanceof Color) { 201 res.append(Utils.toString((Color)val)); 202 } else { 203 res.append(val+""); 204 } 205 res.append("; "); 206 } 207 return res.append("}").toString(); 208 } 209 210 public boolean containsKey(String key) { 211 return prop.containsKey(key); 212 } 213}