source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/Cascade.java@ 12378

Last change on this file since 12378 was 12378, checked in by michael2402, 7 years ago

Document the gui.mappaint package

  • Property svn:eol-style set to native
File size: 9.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import java.awt.Color;
5import java.util.Arrays;
6import java.util.HashMap;
7import java.util.List;
8import java.util.Map;
9import java.util.Map.Entry;
10import java.util.TreeSet;
11import java.util.regex.Pattern;
12
13import org.openstreetmap.josm.Main;
14import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors;
15import org.openstreetmap.josm.tools.ColorHelper;
16import org.openstreetmap.josm.tools.Utils;
17
18/**
19 * Simple map of properties with dynamic typing.
20 */
21public final class Cascade {
22
23 private final Map<String, Object> prop;
24
25 private boolean defaultSelectedHandling = true;
26
27 private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})");
28
29 /**
30 * Constructs a new {@code Cascade}.
31 */
32 public Cascade() {
33 this.prop = new HashMap<>();
34 }
35
36 /**
37 * Constructs a new {@code Cascade} from existing one.
38 * @param other other Cascade
39 */
40 public Cascade(Cascade other) {
41 this.prop = new HashMap<>(other.prop);
42 }
43
44 /**
45 * Gets the value for a given key with the given type
46 * @param <T> the expected type
47 * @param key the key
48 * @param def default value, can be null
49 * @param klass the same as T
50 * @return if a value that can be converted to class klass has been mapped to key, returns this
51 * value, def otherwise
52 */
53 public <T> T get(String key, T def, Class<T> klass) {
54 return get(key, def, klass, false);
55 }
56
57 /**
58 * Get value for the given key
59 * @param <T> the expected type
60 * @param key the key
61 * @param def default value, can be null
62 * @param klass the same as T
63 * @param suppressWarnings show or don't show a warning when some value is
64 * found, but cannot be converted to the requested type
65 * @return if a value that can be converted to class klass has been mapped to key, returns this
66 * value, def otherwise
67 */
68 public <T> T get(String key, T def, Class<T> klass, boolean suppressWarnings) {
69 if (def != null && !klass.isInstance(def))
70 throw new IllegalArgumentException(def+" is not an instance of "+klass);
71 Object o = prop.get(key);
72 if (o == null)
73 return def;
74 T res = convertTo(o, klass);
75 if (res == null) {
76 if (!suppressWarnings) {
77 Main.warn(String.format("Unable to convert property %s to type %s: found %s of type %s!", key, klass, o, o.getClass()));
78 }
79 return def;
80 } else
81 return res;
82 }
83
84 /**
85 * Gets a property for the given key (like stroke, ...)
86 * @param key The key of the property
87 * @return The value or <code>null</code> if it is not set. May be of any type
88 */
89 public Object get(String key) {
90 return prop.get(key);
91 }
92
93 /**
94 * Sets the property for the given key
95 * @param key The key
96 * @param val The value
97 */
98 public void put(String key, Object val) {
99 prop.put(key, val);
100 }
101
102 /**
103 * Sets the property for the given key, removes it if the value is <code>null</code>
104 * @param key The key
105 * @param val The value, may be <code>null</code>
106 */
107 public void putOrClear(String key, Object val) {
108 if (val != null) {
109 prop.put(key, val);
110 } else {
111 prop.remove(key);
112 }
113 }
114
115 /**
116 * Removes the property with the given key
117 * @param key The key
118 */
119 public void remove(String key) {
120 prop.remove(key);
121 }
122
123 /**
124 * Converts an object to a given other class.
125 *
126 * Only conversions that are useful for MapCSS are supported
127 * @param <T> The class type
128 * @param o The object to convert
129 * @param klass The class
130 * @return The converted object or <code>null</code> if the conversion failed
131 */
132 @SuppressWarnings("unchecked")
133 public static <T> T convertTo(Object o, Class<T> klass) {
134 if (o == null)
135 return null;
136 if (klass.isInstance(o))
137 return (T) o;
138
139 if (klass == float.class || klass == Float.class)
140 return (T) toFloat(o);
141
142 if (klass == double.class || klass == Double.class) {
143 o = toFloat(o);
144 if (o != null) {
145 o = Double.valueOf((Float) o);
146 }
147 return (T) o;
148 }
149
150 if (klass == boolean.class || klass == Boolean.class)
151 return (T) toBool(o);
152
153 if (klass == float[].class)
154 return (T) toFloatArray(o);
155
156 if (klass == Color.class)
157 return (T) toColor(o);
158
159 if (klass == String.class) {
160 if (o instanceof Keyword)
161 return (T) ((Keyword) o).val;
162 if (o instanceof Color) {
163 Color c = (Color) o;
164 int alpha = c.getAlpha();
165 if (alpha != 255)
166 return (T) String.format("#%06x%02x", ((Color) o).getRGB() & 0x00ffffff, alpha);
167 return (T) String.format("#%06x", ((Color) o).getRGB() & 0x00ffffff);
168 }
169
170 return (T) o.toString();
171 }
172
173 return null;
174 }
175
176 private static Float toFloat(Object o) {
177 if (o instanceof Number)
178 return ((Number) o).floatValue();
179 if (o instanceof String && !((String) o).isEmpty()) {
180 try {
181 return Float.valueOf((String) o);
182 } catch (NumberFormatException e) {
183 if (Main.isDebugEnabled()) {
184 Main.debug('\'' + (String) o + "' cannot be converted to float");
185 }
186 }
187 }
188 return null;
189 }
190
191 private static Boolean toBool(Object o) {
192 if (o instanceof Boolean)
193 return (Boolean) o;
194 String s = null;
195 if (o instanceof Keyword) {
196 s = ((Keyword) o).val;
197 } else if (o instanceof String) {
198 s = (String) o;
199 }
200 if (s != null)
201 return !(s.isEmpty() || "false".equals(s) || "no".equals(s) || "0".equals(s) || "0.0".equals(s));
202 if (o instanceof Number)
203 return ((Number) o).floatValue() != 0;
204 if (o instanceof List)
205 return !((List) o).isEmpty();
206 if (o instanceof float[])
207 return ((float[]) o).length != 0;
208
209 return null;
210 }
211
212 private static float[] toFloatArray(Object o) {
213 if (o instanceof float[])
214 return (float[]) o;
215 if (o instanceof List) {
216 List<?> l = (List<?>) o;
217 float[] a = new float[l.size()];
218 for (int i = 0; i < l.size(); ++i) {
219 Float f = toFloat(l.get(i));
220 if (f == null)
221 return null;
222 else
223 a[i] = f;
224 }
225 return a;
226 }
227 Float f = toFloat(o);
228 if (f != null)
229 return new float[] {f};
230 return null;
231 }
232
233 private static Color toColor(Object o) {
234 if (o instanceof Color)
235 return (Color) o;
236 if (o instanceof Keyword)
237 return CSSColors.get(((Keyword) o).val);
238 if (o instanceof String) {
239 Color c = CSSColors.get((String) o);
240 if (c != null)
241 return c;
242 if (HEX_COLOR_PATTERN.matcher((String) o).matches()) {
243 return ColorHelper.html2color((String) o);
244 }
245 }
246 return null;
247 }
248
249 @Override
250 public String toString() {
251 StringBuilder res = new StringBuilder("Cascade{ ");
252 // List properties in alphabetical order to be deterministic, without changing "prop" to a TreeMap
253 // (no reason too, not sure about the potential memory/performance impact of such a change)
254 TreeSet<String> props = new TreeSet<>();
255 for (Entry<String, Object> entry : prop.entrySet()) {
256 StringBuilder sb = new StringBuilder(entry.getKey()).append(':');
257 Object val = entry.getValue();
258 if (val instanceof float[]) {
259 sb.append(Arrays.toString((float[]) val));
260 } else if (val instanceof Color) {
261 sb.append(Utils.toString((Color) val));
262 } else if (val != null) {
263 sb.append(val);
264 }
265 sb.append("; ");
266 props.add(sb.toString());
267 }
268 for (String s : props) {
269 res.append(s);
270 }
271 return res.append('}').toString();
272 }
273
274 /**
275 * Checks if this cascade has a value for given key
276 * @param key The key to check
277 * @return <code>true</code> if there is a value
278 */
279 public boolean containsKey(String key) {
280 return prop.containsKey(key);
281 }
282
283 /**
284 * Get if the default selection drawing should be used for the object this cascade applies to
285 * @return <code>true</code> to use the default selection drawing
286 */
287 public boolean isDefaultSelectedHandling() {
288 return defaultSelectedHandling;
289 }
290
291 /**
292 * Set that the default selection drawing should be used for the object this cascade applies to
293 * @param defaultSelectedHandling <code>true</code> to use the default selection drawing
294 */
295 public void setDefaultSelectedHandling(boolean defaultSelectedHandling) {
296 this.defaultSelectedHandling = defaultSelectedHandling;
297 }
298}
Note: See TracBrowser for help on using the repository browser.