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

Last change on this file since 13724 was 12620, checked in by Don-vip, 7 years ago

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

  • Property svn:eol-style set to native
File size: 9.2 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.gui.mappaint.mapcss.CSSColors;
14import org.openstreetmap.josm.tools.ColorHelper;
15import org.openstreetmap.josm.tools.Logging;
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 Logging.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 Logging.debug("'{0}' cannot be converted to float", o);
184 }
185 }
186 return null;
187 }
188
189 private static Boolean toBool(Object o) {
190 if (o instanceof Boolean)
191 return (Boolean) o;
192 String s = null;
193 if (o instanceof Keyword) {
194 s = ((Keyword) o).val;
195 } else if (o instanceof String) {
196 s = (String) o;
197 }
198 if (s != null)
199 return !(s.isEmpty() || "false".equals(s) || "no".equals(s) || "0".equals(s) || "0.0".equals(s));
200 if (o instanceof Number)
201 return ((Number) o).floatValue() != 0;
202 if (o instanceof List)
203 return !((List) o).isEmpty();
204 if (o instanceof float[])
205 return ((float[]) o).length != 0;
206
207 return null;
208 }
209
210 private static float[] toFloatArray(Object o) {
211 if (o instanceof float[])
212 return (float[]) o;
213 if (o instanceof List) {
214 List<?> l = (List<?>) o;
215 float[] a = new float[l.size()];
216 for (int i = 0; i < l.size(); ++i) {
217 Float f = toFloat(l.get(i));
218 if (f == null)
219 return null;
220 else
221 a[i] = f;
222 }
223 return a;
224 }
225 Float f = toFloat(o);
226 if (f != null)
227 return new float[] {f};
228 return null;
229 }
230
231 private static Color toColor(Object o) {
232 if (o instanceof Color)
233 return (Color) o;
234 if (o instanceof Keyword)
235 return CSSColors.get(((Keyword) o).val);
236 if (o instanceof String) {
237 Color c = CSSColors.get((String) o);
238 if (c != null)
239 return c;
240 if (HEX_COLOR_PATTERN.matcher((String) o).matches()) {
241 return ColorHelper.html2color((String) o);
242 }
243 }
244 return null;
245 }
246
247 @Override
248 public String toString() {
249 StringBuilder res = new StringBuilder("Cascade{ ");
250 // List properties in alphabetical order to be deterministic, without changing "prop" to a TreeMap
251 // (no reason too, not sure about the potential memory/performance impact of such a change)
252 TreeSet<String> props = new TreeSet<>();
253 for (Entry<String, Object> entry : prop.entrySet()) {
254 StringBuilder sb = new StringBuilder(entry.getKey()).append(':');
255 Object val = entry.getValue();
256 if (val instanceof float[]) {
257 sb.append(Arrays.toString((float[]) val));
258 } else if (val instanceof Color) {
259 sb.append(Utils.toString((Color) val));
260 } else if (val != null) {
261 sb.append(val);
262 }
263 sb.append("; ");
264 props.add(sb.toString());
265 }
266 for (String s : props) {
267 res.append(s);
268 }
269 return res.append('}').toString();
270 }
271
272 /**
273 * Checks if this cascade has a value for given key
274 * @param key The key to check
275 * @return <code>true</code> if there is a value
276 */
277 public boolean containsKey(String key) {
278 return prop.containsKey(key);
279 }
280
281 /**
282 * Get if the default selection drawing should be used for the object this cascade applies to
283 * @return <code>true</code> to use the default selection drawing
284 */
285 public boolean isDefaultSelectedHandling() {
286 return defaultSelectedHandling;
287 }
288
289 /**
290 * Set that the default selection drawing should be used for the object this cascade applies to
291 * @param defaultSelectedHandling <code>true</code> to use the default selection drawing
292 */
293 public void setDefaultSelectedHandling(boolean defaultSelectedHandling) {
294 this.defaultSelectedHandling = defaultSelectedHandling;
295 }
296}
Note: See TracBrowser for help on using the repository browser.