- Timestamp:
- 2021-04-12T21:20:31+02:00 (4 years ago)
- Location:
- trunk
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java
r17619 r17758 132 132 .filter(c -> c instanceof ConditionFactory.ExpressionCondition) 133 133 .map(c -> ((ConditionFactory.ExpressionCondition) c).getExpression()) 134 .filter(c -> c instanceof ExpressionFactory.ParameterFunction) 135 .map(c -> (ExpressionFactory.ParameterFunction) c) 136 .filter(c -> c.getMethod().equals(insideMethod)) 137 .flatMap(c -> c.getArgs().stream()) 134 .filter(c -> c instanceof ExpressionFactory.IsInsideFunction) 135 .map(c -> (ExpressionFactory.IsInsideFunction) c) 136 .map(ExpressionFactory.IsInsideFunction::getArg) 138 137 .filter(e -> e instanceof LiteralExpression) 139 138 .map(e -> ((LiteralExpression) e).getLiteral()) -
trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
r16438 r17758 2 2 package org.openstreetmap.josm.gui.mappaint.mapcss; 3 3 4 import java.awt.Color; 4 5 import java.lang.annotation.ElementType; 5 6 import java.lang.annotation.Retention; 6 7 import java.lang.annotation.RetentionPolicy; 7 8 import java.lang.annotation.Target; 8 import java.lang.reflect.Array;9 import java.lang.reflect.InvocationTargetException;10 import java.lang.reflect.Method;11 import java.util.ArrayList;12 9 import java.util.Collection; 13 10 import java.util.Collections; 11 import java.util.HashMap; 14 12 import java.util.List; 13 import java.util.Map; 15 14 import java.util.Objects; 15 import java.util.function.BiFunction; 16 import java.util.function.DoubleBinaryOperator; 17 import java.util.function.DoubleUnaryOperator; 16 18 import java.util.function.Function; 17 import java.util.stream.Collectors;18 19 19 20 import org.openstreetmap.josm.gui.mappaint.Cascade; 20 21 import org.openstreetmap.josm.gui.mappaint.Environment; 21 import org.openstreetmap.josm.tools.JosmRuntimeException;22 import org.openstreetmap.josm.tools.Logging;23 22 import org.openstreetmap.josm.tools.SubclassFilteredCollection; 24 23 import org.openstreetmap.josm.tools.Utils; … … 38 37 @interface NullableArguments {} 39 38 40 private static final List<Method> arrayFunctions = new ArrayList<>(); 41 private static final List<Method> parameterFunctions = new ArrayList<>(); 42 private static final List<Method> parameterFunctionsEnv = new ArrayList<>(); 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<>(); 43 135 44 136 static { 45 for (Method m : Functions.class.getDeclaredMethods()) { 46 Class<?>[] paramTypes = m.getParameterTypes(); 47 if (paramTypes.length == 1 && paramTypes[0].isArray()) { 48 arrayFunctions.add(m); 49 } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) { 50 parameterFunctionsEnv.add(m); 51 } else { 52 parameterFunctions.add(m); 53 } 54 } 55 try { 56 parameterFunctions.add(Math.class.getMethod("abs", float.class)); 57 parameterFunctions.add(Math.class.getMethod("acos", double.class)); 58 parameterFunctions.add(Math.class.getMethod("asin", double.class)); 59 parameterFunctions.add(Math.class.getMethod("atan", double.class)); 60 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class)); 61 parameterFunctions.add(Math.class.getMethod("ceil", double.class)); 62 parameterFunctions.add(Math.class.getMethod("cos", double.class)); 63 parameterFunctions.add(Math.class.getMethod("cosh", double.class)); 64 parameterFunctions.add(Math.class.getMethod("exp", double.class)); 65 parameterFunctions.add(Math.class.getMethod("floor", double.class)); 66 parameterFunctions.add(Math.class.getMethod("log", double.class)); 67 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class)); 68 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class)); 69 parameterFunctions.add(Math.class.getMethod("random")); 70 parameterFunctions.add(Math.class.getMethod("round", float.class)); 71 parameterFunctions.add(Math.class.getMethod("signum", double.class)); 72 parameterFunctions.add(Math.class.getMethod("sin", double.class)); 73 parameterFunctions.add(Math.class.getMethod("sinh", double.class)); 74 parameterFunctions.add(Math.class.getMethod("sqrt", double.class)); 75 parameterFunctions.add(Math.class.getMethod("tan", double.class)); 76 parameterFunctions.add(Math.class.getMethod("tanh", double.class)); 77 } catch (NoSuchMethodException | SecurityException ex) { 78 throw new JosmRuntimeException(ex); 79 } 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("not", Factory.of(boolean.class, Functions::not)); 193 FACTORY_MAP.put("not_equal", Factory.of(Object.class, Object.class, Functions::not_equal)); 194 FACTORY_MAP.put("number_of_tags", Factory.ofEnv(Functions::number_of_tags)); 195 FACTORY_MAP.put("osm_changeset_id", Factory.ofEnv(Functions::osm_changeset_id)); 196 FACTORY_MAP.put("osm_id", Factory.ofEnv(Functions::osm_id)); 197 FACTORY_MAP.put("osm_timestamp", Factory.ofEnv(Functions::osm_timestamp)); 198 FACTORY_MAP.put("osm_user_id", Factory.ofEnv(Functions::osm_user_id)); 199 FACTORY_MAP.put("osm_user_name", Factory.ofEnv(Functions::osm_user_name)); 200 FACTORY_MAP.put("osm_version", Factory.ofEnv(Functions::osm_version)); 201 FACTORY_MAP.put("outside", Factory.ofEnv(String.class, Functions::outside)); 202 FACTORY_MAP.put("parent_osm_id", Factory.ofEnv(Functions::parent_osm_id)); 203 FACTORY_MAP.put("parent_tag", Factory.ofEnv(String.class, Functions::parent_tag)); 204 FACTORY_MAP.put("parent_tags", Factory.ofEnv(String.class, Functions::parent_tags)); 205 FACTORY_MAP.put("plus", Factory.ofNumberVarArgs(Functions::plus)); 206 FACTORY_MAP.put("print", Factory.of(Object.class, Functions::print)); 207 FACTORY_MAP.put("println", Factory.of(Object.class, Functions::println)); 208 FACTORY_MAP.put("prop", Factory.ofEnv(String.class, Functions::prop)); 209 FACTORY_MAP.put("red", Factory.of(Color.class, Functions::red)); 210 FACTORY_MAP.put("regexp_match", Factory.of(String.class, String.class, String.class, Functions::regexp_match, Functions::regexp_match)); 211 FACTORY_MAP.put("regexp_test", Factory.of(String.class, String.class, String.class, Functions::regexp_test, Functions::regexp_test)); 212 FACTORY_MAP.put("replace", Factory.of(String.class, String.class, String.class, null, Functions::replace)); 213 FACTORY_MAP.put("rgb", Factory.of(float.class, float.class, float.class, null, Functions::rgb)); 214 FACTORY_MAP.put("rgba", Factory.of(float.class, float.class, float.class, float.class, Functions::rgba)); 215 FACTORY_MAP.put("role", Factory.ofEnv(Functions::role)); 216 FACTORY_MAP.put("round", Factory.of(Math::acos)); 217 FACTORY_MAP.put("setting", Factory.ofEnv(String.class, Functions::setting)); 218 FACTORY_MAP.put("signum", Factory.of(Math::signum)); 219 FACTORY_MAP.put("sin", Factory.of(Math::sin)); 220 FACTORY_MAP.put("sinh", Factory.of(Math::sinh)); 221 FACTORY_MAP.put("sort", Factory.ofStringVarargs(Functions::sort)); 222 FACTORY_MAP.put("sort_list", Factory.of(List.class, Functions::sort_list)); 223 FACTORY_MAP.put("split", Factory.of(String.class, String.class, Functions::split)); 224 FACTORY_MAP.put("sqrt", Factory.of(Math::sqrt)); 225 FACTORY_MAP.put("substring", Factory.of(String.class, float.class, float.class, Functions::substring, Functions::substring)); 226 FACTORY_MAP.put("tag", Factory.ofEnv(String.class, Functions::tag)); 227 FACTORY_MAP.put("tag_regex", Factory.ofEnv(String.class, String.class, Functions::tag_regex, Functions::tag_regex)); 228 FACTORY_MAP.put("tan", Factory.of(Math::tan)); 229 FACTORY_MAP.put("tanh", Factory.of(Math::tanh)); 230 FACTORY_MAP.put("times", Factory.ofNumberVarArgs(Functions::times)); 231 FACTORY_MAP.put("title", Factory.of(String.class, Functions::title)); 232 FACTORY_MAP.put("to_boolean", Factory.of(String.class, Functions::to_boolean)); 233 FACTORY_MAP.put("to_byte", Factory.of(String.class, Functions::to_byte)); 234 FACTORY_MAP.put("to_double", Factory.of(String.class, Functions::to_double)); 235 FACTORY_MAP.put("to_float", Factory.of(String.class, Functions::to_float)); 236 FACTORY_MAP.put("to_int", Factory.of(String.class, Functions::to_int)); 237 FACTORY_MAP.put("to_long", Factory.of(String.class, Functions::to_long)); 238 FACTORY_MAP.put("to_short", Factory.of(String.class, Functions::to_short)); 239 FACTORY_MAP.put("tr", Factory.ofStringVarargs(Functions::tr)); 240 FACTORY_MAP.put("trim", Factory.of(String.class, Functions::trim)); 241 FACTORY_MAP.put("trim_list", Factory.of(List.class, Functions::trim_list)); 242 FACTORY_MAP.put("uniq", Factory.ofStringVarargs(Functions::uniq)); 243 FACTORY_MAP.put("uniq_list", Factory.of(List.class, Functions::uniq_list)); 244 FACTORY_MAP.put("upper", Factory.of(String.class, Functions::upper)); 245 FACTORY_MAP.put("waylength", Factory.ofEnv(Functions::waylength)); 80 246 } 81 247 … … 105 271 else if ("min".equals(name) && !args.isEmpty()) 106 272 return new MinMaxFunction(args, false); 107 108 for (Method m : arrayFunctions) { 109 if (m.getName().equals(name)) 110 return new ArrayFunction(m, args); 111 } 112 for (Method m : parameterFunctions) { 113 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length) 114 return new ParameterFunction(m, args, false); 115 } 116 for (Method m : parameterFunctionsEnv) { 117 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length-1) 118 return new ParameterFunction(m, args, true); 273 else if ("inside".equals(name) && args.size() == 1) 274 return new IsInsideFunction(args.get(0)); 275 else if ("random".equals(name)) 276 return env -> Math.random(); 277 278 Factory factory = FACTORY_MAP.get(name); 279 if (factory != null) { 280 return factory.createExpression(args); 119 281 } 120 282 return NullExpression.INSTANCE; … … 283 445 284 446 /** 285 * Function that takes a certain number of argument with specific type.447 * {@code Functions#inside} implementation for use in {@link org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker} 286 448 * 287 * Implementation is based on a Method object. 288 * If any of the arguments evaluate to null, the result will also be null. 289 */ 290 public static class ParameterFunction implements Expression { 291 292 private final Method m; 293 private final boolean nullable; 294 private final List<Expression> args; 295 private final Class<?>[] expectedParameterTypes; 296 private final boolean needsEnvironment; 297 298 /** 299 * Constructs a new {@code ParameterFunction}. 300 * @param m method 301 * @param args arguments 302 * @param needsEnvironment whether function needs environment 303 */ 304 public ParameterFunction(Method m, List<Expression> args, boolean needsEnvironment) { 305 this.m = m; 306 this.nullable = m.getAnnotation(NullableArguments.class) != null; 307 this.args = args; 308 this.expectedParameterTypes = m.getParameterTypes(); 309 this.needsEnvironment = needsEnvironment; 310 } 311 312 /** 313 * Returns the method. 314 * @return the method 315 * @since 14484 316 */ 317 public final Method getMethod() { 318 return m; 319 } 320 321 /** 322 * Returns the arguments. 323 * @return the arguments 324 * @since 14484 325 */ 326 public final List<Expression> getArgs() { 327 return args; 328 } 329 330 @Override 331 public Object evaluate(Environment env) { 332 Object[] convertedArgs; 333 334 int start = 0; 335 int offset = 0; 336 if (needsEnvironment) { 337 start = 1; 338 offset = 1; 339 convertedArgs = new Object[args.size() + 1]; 340 convertedArgs[0] = env; 341 } else { 342 convertedArgs = new Object[args.size()]; 343 } 344 345 for (int i = start; i < convertedArgs.length; ++i) { 346 if (!expectedParameterTypes[i].isArray()) { 347 convertedArgs[i] = Cascade.convertTo(args.get(i - offset).evaluate(env), expectedParameterTypes[i]); 348 } else { 349 Class<?> clazz = expectedParameterTypes[i].getComponentType(); 350 Object[] varargs = (Object[]) Array.newInstance(clazz, args.size() - i + 1); 351 for (int j = 0; j < args.size() - i + 1; ++j) { 352 varargs[j] = Cascade.convertTo(args.get(j + i - 1).evaluate(env), clazz); 353 } 354 convertedArgs[i] = expectedParameterTypes[i].cast(varargs); 355 break; 356 } 357 if (convertedArgs[i] == null && !nullable) { 358 return null; 359 } 360 } 361 362 Object result = null; 363 try { 364 result = m.invoke(null, convertedArgs); 365 } catch (IllegalAccessException | IllegalArgumentException ex) { 366 throw new JosmRuntimeException(ex); 367 } catch (InvocationTargetException ex) { 368 Logging.error(ex); 369 return null; 370 } 371 return result; 372 } 373 374 @Override 375 public String toString() { 376 StringBuilder b = new StringBuilder("ParameterFunction~"); 377 b.append(m.getName()).append('('); 378 for (int i = 0; i < expectedParameterTypes.length; ++i) { 379 if (i > 0) b.append(','); 380 b.append(expectedParameterTypes[i]); 381 if (!needsEnvironment) { 382 b.append(' ').append(args.get(i)); 383 } else if (i > 0) { 384 b.append(' ').append(args.get(i-1)); 385 } 386 } 387 b.append(')'); 388 return b.toString(); 389 } 390 } 391 392 /** 393 * Function that takes an arbitrary number of arguments. 394 * 395 * Currently, all array functions are static, so there is no need to 396 * provide the environment, like it is done in {@link ParameterFunction}. 397 * If any of the arguments evaluate to null, the result will also be null. 398 */ 399 public static class ArrayFunction implements Expression { 400 401 private final Method m; 402 private final boolean nullable; 403 private final List<Expression> args; 404 private final Class<?>[] expectedParameterTypes; 405 private final Class<?> arrayComponentType; 406 407 /** 408 * Constructs a new {@code ArrayFunction}. 409 * @param m method 410 * @param args arguments 411 */ 412 public ArrayFunction(Method m, List<Expression> args) { 413 this.m = m; 414 this.nullable = m.getAnnotation(NullableArguments.class) != null; 415 this.args = args; 416 this.expectedParameterTypes = m.getParameterTypes(); 417 this.arrayComponentType = expectedParameterTypes[0].getComponentType(); 418 } 419 420 @Override 421 public Object evaluate(Environment env) { 422 Object[] convertedArgs = new Object[expectedParameterTypes.length]; 423 Object arrayArg = Array.newInstance(arrayComponentType, args.size()); 424 for (int i = 0; i < args.size(); ++i) { 425 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType); 426 if (o == null && !nullable) { 427 return null; 428 } 429 Array.set(arrayArg, i, o); 430 } 431 convertedArgs[0] = arrayArg; 432 433 Object result = null; 434 try { 435 result = m.invoke(null, convertedArgs); 436 } catch (IllegalAccessException | IllegalArgumentException ex) { 437 throw new JosmRuntimeException(ex); 438 } catch (InvocationTargetException ex) { 439 Logging.error(ex); 440 return null; 441 } 442 return result; 443 } 444 445 @Override 446 public String toString() { 447 return args.stream() 448 .map(arg -> arrayComponentType + " " + arg) 449 .collect(Collectors.joining(",", "ArrayFunction~" + m.getName() + '(', ")")); 449 * @see Functions#inside 450 */ 451 public static class IsInsideFunction implements Expression { 452 private final Expression arg; 453 454 /** 455 * Constructs a new {@code IsInsideFunction}. 456 * @param arg argument 457 */ 458 public IsInsideFunction(Expression arg) { 459 this.arg = arg; 460 } 461 462 /** 463 * Returns the argument 464 * @return the argument 465 */ 466 public Expression getArg() { 467 return arg; 468 } 469 470 @Override 471 public Object evaluate(Environment env) { 472 String codes = Cascade.convertTo(arg.evaluate(env), String.class); 473 return Functions.inside(env, codes); 450 474 } 451 475 } -
trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java
r17614 r17758 72 72 /** 73 73 * Function associated to the numeric "+" operator. 74 * @param args arguments 74 * @param a the first operand 75 * @param b the second operand 75 76 * @return Sum of arguments 76 */ 77 public static float plus(float... args) { // NO_UCD (unused code) 78 float res = 0; 79 for (float f : args) { 80 res += f; 81 } 82 return res; 77 * @see Float#sum 78 */ 79 public static double plus(double a, double b) { // NO_UCD (unused code) 80 return a + b; 83 81 } 84 82 85 83 /** 86 84 * Function associated to the numeric "-" operator. 87 * @param args arguments 88 * @return Substraction of arguments 89 */ 90 public static Float minus(float... args) { // NO_UCD (unused code) 91 if (args.length == 0) { 92 return 0.0F; 93 } 94 if (args.length == 1) { 95 return -args[0]; 96 } 97 float res = args[0]; 98 for (int i = 1; i < args.length; ++i) { 99 res -= args[i]; 100 } 101 return res; 85 * @param a the first operand 86 * @param b the second operand 87 * @return Subtraction of arguments 88 */ 89 public static double minus(double a, double b) { // NO_UCD (unused code) 90 return a - b; 102 91 } 103 92 104 93 /** 105 94 * Function associated to the numeric "*" operator. 106 * @param args arguments 95 * @param a the first operand 96 * @param b the second operand 107 97 * @return Multiplication of arguments 108 98 */ 109 public static float times(float... args) { // NO_UCD (unused code) 110 float res = 1; 111 for (float f : args) { 112 res *= f; 113 } 114 return res; 99 public static double times(double a, double b) { // NO_UCD (unused code) 100 return a * b; 115 101 } 116 102 117 103 /** 118 104 * Function associated to the numeric "/" operator. 119 * @param args arguments 105 * @param a the first operand 106 * @param b the second operand 120 107 * @return Division of arguments 121 108 */ 122 public static Float divided_by(float... args) { // NO_UCD (unused code) 123 if (args.length == 0) { 124 return 1.0F; 125 } 126 float res = args[0]; 127 for (int i = 1; i < args.length; ++i) { 128 if (args[i] == 0) { 129 return null; 130 } 131 res /= args[i]; 132 } 133 return res; 109 public static double divided_by(double a, double b) { // NO_UCD (unused code) 110 return a / b; 134 111 } 135 112 136 113 /** 137 114 * Creates a list of values, e.g., for the {@code dashes} property. 115 * @param ignored The environment (ignored) 138 116 * @param args The values to put in a list 139 117 * @return list of values 140 118 * @see Arrays#asList(Object[]) 141 119 */ 142 public static List<Object> list( Object... args) { // NO_UCD (unused code)120 public static List<Object> list(Environment ignored, Object... args) { // NO_UCD (unused code) 143 121 return Arrays.asList(args); 144 122 } … … 156 134 * Returns the first non-null object. 157 135 * The name originates from <a href="http://wiki.openstreetmap.org/wiki/MapCSS/0.2/eval">MapCSS standard</a>. 136 * @param ignored The environment (ignored) 158 137 * @param args arguments 159 138 * @return the first non-null object … … 161 140 */ 162 141 @NullableArguments 163 public static Object any( Object... args) { // NO_UCD (unused code)142 public static Object any(Environment ignored, Object... args) { // NO_UCD (unused code) 164 143 return Utils.firstNonNull(args); 165 144 } … … 304 283 /** 305 284 * Assembles the strings to one. 285 * @param ignored The environment (ignored) 306 286 * @param args arguments 307 287 * @return assembled string … … 309 289 */ 310 290 @NullableArguments 311 public static String concat( Object... args) { // NO_UCD (unused code)291 public static String concat(Environment ignored, Object... args) { // NO_UCD (unused code) 312 292 return Arrays.stream(args) 313 293 .filter(Objects::nonNull) … … 318 298 /** 319 299 * Assembles the strings to one, where the first entry is used as separator. 300 * @param ignored The environment (ignored) 320 301 * @param args arguments. First one is used as separator 321 302 * @return assembled string … … 323 304 */ 324 305 @NullableArguments 325 public static String join( String... args) { // NO_UCD (unused code)306 public static String join(Environment ignored, String... args) { // NO_UCD (unused code) 326 307 return String.join(args[0], Arrays.asList(args).subList(1, args.length)); 327 308 } … … 548 529 /** 549 530 * Sort an array of strings 531 * @param ignored The environment (ignored) 550 532 * @param sortables The array to sort 551 533 * @return The sorted list 552 534 * @since 15279 553 535 */ 554 public static List<String> sort( String... sortables) { // NO_UCD (unused code)536 public static List<String> sort(Environment ignored, String... sortables) { // NO_UCD (unused code) 555 537 Arrays.parallelSort(sortables); 556 538 return Arrays.asList(sortables); … … 570 552 /** 571 553 * Get unique values 554 * @param ignored The environment (ignored) 572 555 * @param values A list of values that may have duplicates 573 556 * @return A list with no duplicates 574 557 * @since 15323 575 558 */ 576 public static List<String> uniq( String... values) { // NO_UCD (unused code)559 public static List<String> uniq(Environment ignored, String... values) { // NO_UCD (unused code) 577 560 return uniq_list(Arrays.asList(values)); 578 561 } … … 904 887 * Translates some text for the current locale. The first argument is the text to translate, 905 888 * and the subsequent arguments are parameters for the string indicated by <code>{0}</code>, <code>{1}</code>, … 889 * @param ignored The environment (ignored) 906 890 * @param args arguments 907 891 * @return the translated string 908 892 */ 909 893 @NullableArguments 910 public static String tr( String... args) { // NO_UCD (unused code)894 public static String tr(Environment ignored, String... args) { // NO_UCD (unused code) 911 895 final String text = args[0]; 912 896 System.arraycopy(args, 1, args, 0, args.length - 1); -
trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactoryTest.java
r17275 r17758 8 8 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 9 9 import net.trajano.commons.testing.UtilityClassTestUtil; 10 11 import java.lang.reflect.Method; 12 import java.lang.reflect.Modifier; 10 13 11 14 /** … … 29 32 UtilityClassTestUtil.assertUtilityClassWellDefined(Functions.class); 30 33 } 34 35 /** 36 * Tests that all functions have been registered to {@link ExpressionFactory#FACTORY_MAP} 37 * 38 * For instance to register {@link Functions#osm_id}, {@code FACTORY_MAP.put("osm_id", Factory.ofEnv(Functions::osm_id))} 39 */ 40 @Test 41 void testNoUnregisteredFunctions() { 42 for (Method m : Functions.class.getDeclaredMethods()) { 43 if (!Modifier.isPrivate(m.getModifiers()) && !ExpressionFactory.FACTORY_MAP.containsKey(m.getName())) { 44 throw new AssertionError(m + " has not registered in ExpressionFactory.FACTORY_MAP"); 45 } 46 } 47 } 31 48 } -
trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParserTest.java
r17745 r17758 368 368 MultiCascade mc = new MultiCascade(); 369 369 sheet.apply(mc, OsmUtils.createPrimitive("way foo=bar"), 20, false); 370 assertEquals( Float.valueOf(5f), mc.getCascade(null).get("width"));370 assertEquals(5.0f, mc.getCascade(null).get("width")); 371 371 sheet.apply(mc, OsmUtils.createPrimitive("way keyA=true"), 20, false); 372 assertEquals( Float.valueOf(15f), mc.getCascade(null).get("width"));372 assertEquals(15.0, mc.getCascade(null).get("width")); 373 373 sheet.apply(mc, OsmUtils.createPrimitive("way keyB=true"), 20, false); 374 assertEquals( Float.valueOf(15f), mc.getCascade(null).get("width"));374 assertEquals(15.0, mc.getCascade(null).get("width")); 375 375 sheet.apply(mc, OsmUtils.createPrimitive("way keyA=true keyB=true"), 20, false); 376 assertEquals( Float.valueOf(15f), mc.getCascade(null).get("width"));376 assertEquals(15.0, mc.getCascade(null).get("width")); 377 377 } 378 378 … … 480 480 @Test 481 481 void testSort() throws Exception { 482 assertEquals(Arrays.asList(new String[] {"alpha", "beta"}), Functions.sort( "beta", "alpha"));482 assertEquals(Arrays.asList(new String[] {"alpha", "beta"}), Functions.sort(null, "beta", "alpha")); 483 483 Way way1 = TestUtils.newWay("highway=residential name=Alpha alt_name=Beta ref=\"A9;A8\"", new Node(new LatLon(0.001, 0.001)), 484 484 new Node(new LatLon(0.002, 0.002))); … … 490 490 assertTrue(source.rules.get(0).matches(e)); 491 491 source.rules.get(0).declaration.execute(e); 492 assertEquals(Functions.join( ",", "Alpha", "Beta"), e.getCascade(null).get("sorted", null, String.class));492 assertEquals(Functions.join(null, ",", "Alpha", "Beta"), e.getCascade(null).get("sorted", null, String.class)); 493 493 494 494 source = new MapCSSStyleSource("way[ref] {sorted: join_list(\",\", sort_list(split(\";\", tag(\"ref\"))));}"); … … 497 497 assertTrue(source.rules.get(0).matches(e)); 498 498 source.rules.get(0).declaration.execute(e); 499 assertEquals(Functions.join( ",", "A8", "A9"), e.getCascade(null).get("sorted", null, String.class));499 assertEquals(Functions.join(null, ",", "A8", "A9"), e.getCascade(null).get("sorted", null, String.class)); 500 500 } 501 501 … … 503 503 void testUniqueValues() throws Exception { 504 504 assertEquals(Arrays.asList(new String[] {"alpha", "beta"}), 505 Functions.uniq( "alpha", "alpha", "alpha", "beta"));505 Functions.uniq(null, "alpha", "alpha", "alpha", "beta")); 506 506 assertEquals(Arrays.asList(new String[] {"one", "two", "three"}), 507 507 Functions.uniq_list(Arrays.asList(new String[] {"one", "one", "two", "two", "two", "three"}))); … … 597 597 598 598 @Test 599 void testMath() { 600 MapCSSStyleSource source = new MapCSSStyleSource("node { add: 1 + 2 + 3 + 4; mul: 2 * 3 * 5 * 7; sub: 0 - 1 - 2 - 3; div: 360 / 15; }"); 601 source.loadStyleSource(); 602 MultiCascade mc = new MultiCascade(); 603 source.apply(mc, OsmUtils.createPrimitive("node"), 20, false); 604 assertEquals(10.0, mc.getCascade(null).get("add")); 605 assertEquals(210.0, mc.getCascade(null).get("mul")); 606 assertEquals(-6.0, mc.getCascade(null).get("sub")); 607 assertEquals(24.0, mc.getCascade(null).get("div")); 608 } 609 610 @Test 599 611 void testMinMaxFunctions() throws Exception { 600 612 MapCSSStyleSource sheet = new MapCSSStyleSource("* {" +
Note:
See TracChangeset
for help on using the changeset viewer.