source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java@ 17765

Last change on this file since 17765 was 17764, checked in by simon04, 3 years ago

see #20744 - Fix MapCSS function prop

Regression of r17758.

  • Property svn:eol-style set to native
File size: 21.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.awt.Color;
5import java.lang.annotation.ElementType;
6import java.lang.annotation.Retention;
7import java.lang.annotation.RetentionPolicy;
8import java.lang.annotation.Target;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Objects;
15import java.util.function.BiFunction;
16import java.util.function.DoubleBinaryOperator;
17import java.util.function.DoubleUnaryOperator;
18import java.util.function.Function;
19
20import org.openstreetmap.josm.gui.mappaint.Cascade;
21import org.openstreetmap.josm.gui.mappaint.Environment;
22import org.openstreetmap.josm.tools.SubclassFilteredCollection;
23import org.openstreetmap.josm.tools.Utils;
24
25/**
26 * Factory to generate {@link Expression}s.
27 * <p>
28 * See {@link #createFunctionExpression}.
29 */
30public final class ExpressionFactory {
31
32 /**
33 * Marks functions which should be executed also when one or more arguments are null.
34 */
35 @Target(ElementType.METHOD)
36 @Retention(RetentionPolicy.RUNTIME)
37 @interface NullableArguments {}
38
39 @FunctionalInterface
40 public interface TriFunction<T, U, V, R> {
41 R apply(T t, U u, V v);
42 }
43
44 @FunctionalInterface
45 public interface QuadFunction<T, U, V, W, R> {
46 R apply(T t, U u, V v, W w);
47 }
48
49 @FunctionalInterface
50 interface Factory {
51 Expression createExpression(List<Expression> args);
52
53 static Factory of(DoubleUnaryOperator operator) {
54 return of(Double.class, operator::applyAsDouble);
55 }
56
57 static Factory ofNumberVarArgs(DoubleBinaryOperator operator) {
58 return args -> env -> args.stream()
59 .map(arg -> Cascade.convertTo(arg.evaluate(env), Double.class))
60 .filter(Objects::nonNull)
61 .reduce(operator::applyAsDouble)
62 .orElse(null);
63 }
64
65 static Factory ofStringVarargs(BiFunction<Environment, String[], ?> function) {
66 return args -> env -> function.apply(env, args.stream()
67 .map(arg -> Cascade.convertTo(arg.evaluate(env), String.class))
68 .toArray(String[]::new));
69 }
70
71 static Factory ofObjectVarargs(BiFunction<Environment, Object[], ?> function) {
72 return args -> env -> function.apply(env, args.stream()
73 .map(arg -> arg.evaluate(env))
74 .toArray(Object[]::new));
75 }
76
77 static <T> Factory of(Class<T> type, Function<T, ?> function) {
78 return args -> env -> {
79 T v = Cascade.convertTo(args.get(0).evaluate(env), type);
80 return v == null ? null : function.apply(v);
81 };
82 }
83
84 static <T, U> Factory of(Class<T> type1, Class<U> type2, BiFunction<T, U, ?> function) {
85 return args -> env -> {
86 T v1 = Cascade.convertTo(args.get(0).evaluate(env), type1);
87 U v2 = Cascade.convertTo(args.get(1).evaluate(env), type2);
88 return v1 == null || v2 == null ? null : function.apply(v1, v2);
89 };
90 }
91
92 static <T, U, V> Factory of(Class<T> type1, Class<U> type2, Class<V> type3,
93 BiFunction<T, U, ?> biFunction, TriFunction<T, U, V, ?> triFunction) {
94 return args -> env -> {
95 T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
96 U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
97 V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
98 return v1 == null || v2 == null ? null : v3 == null ? biFunction.apply(v1, v2) : triFunction.apply(v1, v2, v3);
99 };
100 }
101
102 static <T, U, V, W> Factory of(Class<T> type1, Class<U> type2, Class<V> type3, Class<W> type4,
103 QuadFunction<T, U, V, W, ?> function) {
104 return args -> env -> {
105 T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
106 U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
107 V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
108 W v4 = args.size() >= 4 ? Cascade.convertTo(args.get(3).evaluate(env), type4) : null;
109 return v1 == null || v2 == null || v3 == null || v4 == null ? null : function.apply(v1, v2, v3, v4);
110 };
111 }
112
113 static <T> Factory ofEnv(Function<Environment, ?> function) {
114 return args -> function::apply;
115 }
116
117 static <T> Factory ofEnv(Class<T> type, BiFunction<Environment, T, ?> function) {
118 return args -> env -> {
119 T v = Cascade.convertTo(args.get(0).evaluate(env), type);
120 return v == null ? null : function.apply(env, v);
121 };
122 }
123
124 static <T, U> Factory ofEnv(Class<T> type1, Class<U> type2,
125 BiFunction<Environment, T, ?> biFunction, TriFunction<Environment, T, U, ?> triFunction) {
126 return args -> env -> {
127 T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
128 U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
129 return v1 == null ? null : v2 == null ? biFunction.apply(env, v1) : triFunction.apply(env, v1, v2);
130 };
131 }
132 }
133
134 static final Map<String, Factory> FACTORY_MAP = new HashMap<>();
135
136 static {
137 FACTORY_MAP.put("CRC32_checksum", Factory.of(String.class, Functions::CRC32_checksum));
138 FACTORY_MAP.put("JOSM_pref", Factory.ofEnv(String.class, String.class, null, Functions::JOSM_pref));
139 FACTORY_MAP.put("JOSM_search", Factory.ofEnv(String.class, Functions::JOSM_search));
140 FACTORY_MAP.put("URL_decode", Factory.of(String.class, Functions::URL_decode));
141 FACTORY_MAP.put("URL_encode", Factory.of(String.class, Functions::URL_encode));
142 FACTORY_MAP.put("XML_encode", Factory.of(String.class, Functions::XML_encode));
143 FACTORY_MAP.put("abs", Factory.of(Math::acos));
144 FACTORY_MAP.put("acos", Factory.of(Math::acos));
145 FACTORY_MAP.put("alpha", Factory.of(Color.class, Functions::alpha));
146 FACTORY_MAP.put("any", Factory.ofObjectVarargs(Functions::any));
147 FACTORY_MAP.put("areasize", Factory.ofEnv(Functions::areasize));
148 FACTORY_MAP.put("asin", Factory.of(Math::asin));
149 FACTORY_MAP.put("at", Factory.ofEnv(double.class, double.class, null, Functions::at));
150 FACTORY_MAP.put("atan", Factory.of(Math::atan));
151 FACTORY_MAP.put("atan2", Factory.of(Double.class, Double.class, Math::atan2));
152 FACTORY_MAP.put("blue", Factory.of(Color.class, Functions::blue));
153 FACTORY_MAP.put("cardinal_to_radians", Factory.of(String.class, Functions::cardinal_to_radians));
154 FACTORY_MAP.put("ceil", Factory.of(Math::ceil));
155 FACTORY_MAP.put("center", Factory.ofEnv(Functions::center));
156 FACTORY_MAP.put("child_tag", Factory.ofEnv(String.class, Functions::child_tag));
157 FACTORY_MAP.put("color2html", Factory.of(Color.class, Functions::color2html));
158 FACTORY_MAP.put("concat", Factory.ofObjectVarargs(Functions::concat));
159 FACTORY_MAP.put("cos", Factory.of(Math::cos));
160 FACTORY_MAP.put("cosh", Factory.of(Math::cosh));
161 FACTORY_MAP.put("count", Factory.of(List.class, Functions::count));
162 FACTORY_MAP.put("count_roles", Factory.ofStringVarargs(Functions::count_roles));
163 FACTORY_MAP.put("degree_to_radians", Factory.of(Functions::degree_to_radians));
164 FACTORY_MAP.put("divided_by", Factory.ofNumberVarArgs(Functions::divided_by));
165 FACTORY_MAP.put("equal", Factory.of(Object.class, Object.class, Functions::equal));
166 FACTORY_MAP.put("eval", Factory.of(Object.class, Functions::eval));
167 FACTORY_MAP.put("exp", Factory.of(Math::exp));
168 FACTORY_MAP.put("floor", Factory.of(Math::floor));
169 FACTORY_MAP.put("get", Factory.of(List.class, float.class, Functions::get));
170 FACTORY_MAP.put("gpx_distance", Factory.ofEnv(Functions::gpx_distance));
171 FACTORY_MAP.put("greater", Factory.of(float.class, float.class, Functions::greater));
172 FACTORY_MAP.put("greater_equal", Factory.of(float.class, float.class, Functions::greater_equal));
173 FACTORY_MAP.put("green", Factory.of(Color.class, Functions::green));
174 FACTORY_MAP.put("has_tag_key", Factory.ofEnv(String.class, Functions::has_tag_key));
175 FACTORY_MAP.put("hsb_color", Factory.of(float.class, float.class, float.class, null, Functions::hsb_color));
176 FACTORY_MAP.put("html2color", Factory.of(String.class, Functions::html2color));
177 FACTORY_MAP.put("index", Factory.ofEnv(Functions::index));
178 FACTORY_MAP.put("inside", Factory.ofEnv(String.class, Functions::inside));
179 FACTORY_MAP.put("is_anticlockwise", Factory.ofEnv(Functions::is_anticlockwise));
180 FACTORY_MAP.put("is_clockwise", Factory.ofEnv(Functions::is_clockwise));
181 FACTORY_MAP.put("is_prop_set", Factory.ofEnv(String.class, Functions::is_prop_set));
182 FACTORY_MAP.put("is_right_hand_traffic", Factory.ofEnv(Functions::is_right_hand_traffic));
183 FACTORY_MAP.put("is_similar", Factory.of(String.class, String.class, Functions::is_similar));
184 FACTORY_MAP.put("join", Factory.ofStringVarargs(Functions::join));
185 FACTORY_MAP.put("join_list", Factory.of(String.class, List.class, Functions::join_list));
186 FACTORY_MAP.put("less", Factory.of(float.class, float.class, Functions::less));
187 FACTORY_MAP.put("less_equal", Factory.of(float.class, float.class, Functions::less_equal));
188 FACTORY_MAP.put("list", Factory.ofObjectVarargs(Functions::list));
189 FACTORY_MAP.put("log", Factory.of(Math::log));
190 FACTORY_MAP.put("lower", Factory.of(String.class, Functions::lower));
191 FACTORY_MAP.put("minus", Factory.ofNumberVarArgs(Functions::minus));
192 FACTORY_MAP.put("mod", Factory.of(float.class, float.class, Functions::mod));
193 FACTORY_MAP.put("not", Factory.of(boolean.class, Functions::not));
194 FACTORY_MAP.put("not_equal", Factory.of(Object.class, Object.class, Functions::not_equal));
195 FACTORY_MAP.put("number_of_tags", Factory.ofEnv(Functions::number_of_tags));
196 FACTORY_MAP.put("osm_changeset_id", Factory.ofEnv(Functions::osm_changeset_id));
197 FACTORY_MAP.put("osm_id", Factory.ofEnv(Functions::osm_id));
198 FACTORY_MAP.put("osm_timestamp", Factory.ofEnv(Functions::osm_timestamp));
199 FACTORY_MAP.put("osm_user_id", Factory.ofEnv(Functions::osm_user_id));
200 FACTORY_MAP.put("osm_user_name", Factory.ofEnv(Functions::osm_user_name));
201 FACTORY_MAP.put("osm_version", Factory.ofEnv(Functions::osm_version));
202 FACTORY_MAP.put("outside", Factory.ofEnv(String.class, Functions::outside));
203 FACTORY_MAP.put("parent_osm_id", Factory.ofEnv(Functions::parent_osm_id));
204 FACTORY_MAP.put("parent_tag", Factory.ofEnv(String.class, Functions::parent_tag));
205 FACTORY_MAP.put("parent_tags", Factory.ofEnv(String.class, Functions::parent_tags));
206 FACTORY_MAP.put("plus", Factory.ofNumberVarArgs(Functions::plus));
207 FACTORY_MAP.put("print", Factory.of(Object.class, Functions::print));
208 FACTORY_MAP.put("println", Factory.of(Object.class, Functions::println));
209 FACTORY_MAP.put("prop", Factory.ofEnv(String.class, String.class, Functions::prop, Functions::prop));
210 FACTORY_MAP.put("red", Factory.of(Color.class, Functions::red));
211 FACTORY_MAP.put("regexp_match", Factory.of(String.class, String.class, String.class, Functions::regexp_match, Functions::regexp_match));
212 FACTORY_MAP.put("regexp_test", Factory.of(String.class, String.class, String.class, Functions::regexp_test, Functions::regexp_test));
213 FACTORY_MAP.put("replace", Factory.of(String.class, String.class, String.class, null, Functions::replace));
214 FACTORY_MAP.put("rgb", Factory.of(float.class, float.class, float.class, null, Functions::rgb));
215 FACTORY_MAP.put("rgba", Factory.of(float.class, float.class, float.class, float.class, Functions::rgba));
216 FACTORY_MAP.put("role", Factory.ofEnv(Functions::role));
217 FACTORY_MAP.put("round", Factory.of(Math::acos));
218 FACTORY_MAP.put("setting", Factory.ofEnv(String.class, Functions::setting));
219 FACTORY_MAP.put("signum", Factory.of(Math::signum));
220 FACTORY_MAP.put("sin", Factory.of(Math::sin));
221 FACTORY_MAP.put("sinh", Factory.of(Math::sinh));
222 FACTORY_MAP.put("sort", Factory.ofStringVarargs(Functions::sort));
223 FACTORY_MAP.put("sort_list", Factory.of(List.class, Functions::sort_list));
224 FACTORY_MAP.put("split", Factory.of(String.class, String.class, Functions::split));
225 FACTORY_MAP.put("sqrt", Factory.of(Math::sqrt));
226 FACTORY_MAP.put("substring", Factory.of(String.class, float.class, float.class, Functions::substring, Functions::substring));
227 FACTORY_MAP.put("tag", Factory.ofEnv(String.class, Functions::tag));
228 FACTORY_MAP.put("tag_regex", Factory.ofEnv(String.class, String.class, Functions::tag_regex, Functions::tag_regex));
229 FACTORY_MAP.put("tan", Factory.of(Math::tan));
230 FACTORY_MAP.put("tanh", Factory.of(Math::tanh));
231 FACTORY_MAP.put("times", Factory.ofNumberVarArgs(Functions::times));
232 FACTORY_MAP.put("title", Factory.of(String.class, Functions::title));
233 FACTORY_MAP.put("to_boolean", Factory.of(String.class, Functions::to_boolean));
234 FACTORY_MAP.put("to_byte", Factory.of(String.class, Functions::to_byte));
235 FACTORY_MAP.put("to_double", Factory.of(String.class, Functions::to_double));
236 FACTORY_MAP.put("to_float", Factory.of(String.class, Functions::to_float));
237 FACTORY_MAP.put("to_int", Factory.of(String.class, Functions::to_int));
238 FACTORY_MAP.put("to_long", Factory.of(String.class, Functions::to_long));
239 FACTORY_MAP.put("to_short", Factory.of(String.class, Functions::to_short));
240 FACTORY_MAP.put("tr", Factory.ofStringVarargs(Functions::tr));
241 FACTORY_MAP.put("trim", Factory.of(String.class, Functions::trim));
242 FACTORY_MAP.put("trim_list", Factory.of(List.class, Functions::trim_list));
243 FACTORY_MAP.put("uniq", Factory.ofStringVarargs(Functions::uniq));
244 FACTORY_MAP.put("uniq_list", Factory.of(List.class, Functions::uniq_list));
245 FACTORY_MAP.put("upper", Factory.of(String.class, Functions::upper));
246 FACTORY_MAP.put("waylength", Factory.ofEnv(Functions::waylength));
247 }
248
249 private ExpressionFactory() {
250 // Hide default constructor for utils classes
251 }
252
253 /**
254 * Main method to create an function-like expression.
255 *
256 * @param name the name of the function or operator
257 * @param args the list of arguments (as expressions)
258 * @return the generated Expression. If no suitable function can be found,
259 * returns {@link NullExpression#INSTANCE}.
260 */
261 public static Expression createFunctionExpression(String name, List<Expression> args) {
262 if ("cond".equals(name) && args.size() == 3)
263 return new CondOperator(args.get(0), args.get(1), args.get(2));
264 else if ("and".equals(name))
265 return new AndOperator(args);
266 else if ("or".equals(name))
267 return new OrOperator(args);
268 else if ("length".equals(name) && args.size() == 1)
269 return new LengthFunction(args.get(0));
270 else if ("max".equals(name) && !args.isEmpty())
271 return new MinMaxFunction(args, true);
272 else if ("min".equals(name) && !args.isEmpty())
273 return new MinMaxFunction(args, false);
274 else if ("inside".equals(name) && args.size() == 1)
275 return new IsInsideFunction(args.get(0));
276 else if ("random".equals(name))
277 return env -> Math.random();
278
279 Factory factory = FACTORY_MAP.get(name);
280 if (factory != null) {
281 return factory.createExpression(args);
282 }
283 return NullExpression.INSTANCE;
284 }
285
286 /**
287 * Expression that always evaluates to null.
288 */
289 public static class NullExpression implements Expression {
290
291 /**
292 * The unique instance.
293 */
294 public static final NullExpression INSTANCE = new NullExpression();
295
296 @Override
297 public Object evaluate(Environment env) {
298 return null;
299 }
300 }
301
302 /**
303 * Conditional operator.
304 */
305 public static class CondOperator implements Expression {
306
307 private final Expression condition, firstOption, secondOption;
308
309 /**
310 * Constructs a new {@code CondOperator}.
311 * @param condition condition
312 * @param firstOption first option
313 * @param secondOption second option
314 */
315 public CondOperator(Expression condition, Expression firstOption, Expression secondOption) {
316 this.condition = condition;
317 this.firstOption = firstOption;
318 this.secondOption = secondOption;
319 }
320
321 @Override
322 public Object evaluate(Environment env) {
323 Boolean b = Cascade.convertTo(condition.evaluate(env), boolean.class);
324 if (b != null && b)
325 return firstOption.evaluate(env);
326 else
327 return secondOption.evaluate(env);
328 }
329 }
330
331 /**
332 * "And" logical operator.
333 */
334 public static class AndOperator implements Expression {
335
336 private final List<Expression> args;
337
338 /**
339 * Constructs a new {@code AndOperator}.
340 * @param args arguments
341 */
342 public AndOperator(List<Expression> args) {
343 this.args = args;
344 }
345
346 @Override
347 public Object evaluate(Environment env) {
348 return args.stream()
349 .map(arg -> Cascade.convertTo(arg.evaluate(env), boolean.class))
350 .allMatch(Boolean.TRUE::equals);
351 }
352 }
353
354 /**
355 * "Or" logical operator.
356 */
357 public static class OrOperator implements Expression {
358
359 private final List<Expression> args;
360
361 /**
362 * Constructs a new {@code OrOperator}.
363 * @param args arguments
364 */
365 public OrOperator(List<Expression> args) {
366 this.args = args;
367 }
368
369 @Override
370 public Object evaluate(Environment env) {
371 return args.stream()
372 .map(arg -> Cascade.convertTo(arg.evaluate(env), boolean.class))
373 .anyMatch(Boolean.TRUE::equals);
374 }
375 }
376
377 /**
378 * Function to calculate the length of a string or list in a MapCSS eval expression.
379 *
380 * Separate implementation to support overloading for different argument types.
381 *
382 * The use for calculating the length of a list is deprecated, use
383 * {@link Functions#count(java.util.List)} instead (see #10061).
384 */
385 public static class LengthFunction implements Expression {
386
387 private final Expression arg;
388
389 /**
390 * Constructs a new {@code LengthFunction}.
391 * @param args arguments
392 */
393 public LengthFunction(Expression args) {
394 this.arg = args;
395 }
396
397 @Override
398 public Object evaluate(Environment env) {
399 List<?> l = Cascade.convertTo(arg.evaluate(env), List.class);
400 if (l != null)
401 return l.size();
402 String s = Cascade.convertTo(arg.evaluate(env), String.class);
403 if (s != null)
404 return s.length();
405 return null;
406 }
407 }
408
409 /**
410 * Computes the maximum/minimum value an arbitrary number of floats, or a list of floats.
411 */
412 public static class MinMaxFunction implements Expression {
413
414 private final List<Expression> args;
415 private final boolean computeMax;
416
417 /**
418 * Constructs a new {@code MinMaxFunction}.
419 * @param args arguments
420 * @param computeMax if {@code true}, compute max. If {@code false}, compute min
421 */
422 public MinMaxFunction(final List<Expression> args, final boolean computeMax) {
423 this.args = args;
424 this.computeMax = computeMax;
425 }
426
427 /**
428 * Compute the minimum / maximum over the list
429 * @param lst The list
430 * @return The minimum or maximum depending on {@link #computeMax}
431 */
432 public Float aggregateList(List<?> lst) {
433 final List<Float> floats = Utils.transform(lst, (Function<Object, Float>) x -> Cascade.convertTo(x, float.class));
434 final Collection<Float> nonNullList = SubclassFilteredCollection.filter(floats, Objects::nonNull);
435 return nonNullList.isEmpty() ? (Float) Float.NaN : computeMax ? Collections.max(nonNullList) : Collections.min(nonNullList);
436 }
437
438 @Override
439 public Object evaluate(final Environment env) {
440 List<?> l = Cascade.convertTo(args.get(0).evaluate(env), List.class);
441 if (args.size() != 1 || l == null)
442 l = Utils.transform(args, (Function<Expression, Object>) x -> x.evaluate(env));
443 return aggregateList(l);
444 }
445 }
446
447 /**
448 * {@code Functions#inside} implementation for use in {@link org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker}
449 *
450 * @see Functions#inside
451 */
452 public static class IsInsideFunction implements Expression {
453 private final Expression arg;
454
455 /**
456 * Constructs a new {@code IsInsideFunction}.
457 * @param arg argument
458 */
459 public IsInsideFunction(Expression arg) {
460 this.arg = arg;
461 }
462
463 /**
464 * Returns the argument
465 * @return the argument
466 */
467 public Expression getArg() {
468 return arg;
469 }
470
471 @Override
472 public Object evaluate(Environment env) {
473 String codes = Cascade.convertTo(arg.evaluate(env), String.class);
474 return Functions.inside(env, codes);
475 }
476 }
477}
Note: See TracBrowser for help on using the repository browser.