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

Last change on this file since 16252 was 16252, checked in by simon04, 4 years ago

fix #18961 - ColorHelper: harmonize color functions

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