001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004import java.awt.Color;
005import java.nio.charset.StandardCharsets;
006import java.util.Arrays;
007import java.util.Collections;
008import java.util.List;
009import java.util.Locale;
010import java.util.Map.Entry;
011import java.util.Objects;
012import java.util.regex.Matcher;
013import java.util.regex.Pattern;
014import java.util.stream.Collectors;
015import java.util.zip.CRC32;
016
017import org.openstreetmap.josm.data.coor.LatLon;
018import org.openstreetmap.josm.data.gpx.GpxDistance;
019import org.openstreetmap.josm.data.osm.IPrimitive;
020import org.openstreetmap.josm.data.osm.Node;
021import org.openstreetmap.josm.data.osm.OsmPrimitive;
022import org.openstreetmap.josm.data.osm.Relation;
023import org.openstreetmap.josm.data.osm.Way;
024import org.openstreetmap.josm.data.osm.search.SearchCompiler;
025import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
026import org.openstreetmap.josm.data.osm.search.SearchParseError;
027import org.openstreetmap.josm.data.preferences.NamedColorProperty;
028import org.openstreetmap.josm.gui.MainApplication;
029import org.openstreetmap.josm.gui.mappaint.Cascade;
030import org.openstreetmap.josm.gui.mappaint.Environment;
031import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
032import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory.NullableArguments;
033import org.openstreetmap.josm.io.XmlWriter;
034import org.openstreetmap.josm.tools.AlphanumComparator;
035import org.openstreetmap.josm.tools.ColorHelper;
036import org.openstreetmap.josm.tools.Geometry;
037import org.openstreetmap.josm.tools.Logging;
038import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
039import org.openstreetmap.josm.tools.RotationAngle;
040import org.openstreetmap.josm.tools.StreamUtils;
041import org.openstreetmap.josm.tools.Territories;
042import org.openstreetmap.josm.tools.Utils;
043
044/**
045 * List of functions that can be used in MapCSS expressions.
046 *
047 * First parameter can be of type {@link Environment} (if needed). This is
048 * automatically filled in by JOSM and the user only sees the remaining arguments.
049 * When one of the user supplied arguments cannot be converted the
050 * expected type or is null, the function is not called and it returns null
051 * immediately. Add the annotation {@link NullableArguments} to allow null arguments.
052 * Every method must be static.
053 *
054 * @since 15245 (extracted from {@link ExpressionFactory})
055 */
056@SuppressWarnings("UnusedDeclaration")
057public final class Functions {
058
059    private Functions() {
060        // Hide implicit public constructor for utility classes
061    }
062
063    /**
064     * Identity function for compatibility with MapCSS specification.
065     * @param o any object
066     * @return {@code o} unchanged
067     */
068    public static Object eval(Object o) {
069        return o;
070    }
071
072    /**
073     * Function associated to the numeric "+" operator.
074     * @param a the first operand
075     * @param b the second operand
076     * @return Sum of arguments
077     * @see Float#sum
078     */
079    public static double plus(double a, double b) {
080        return a + b;
081    }
082
083    /**
084     * Function associated to the numeric "-" operator.
085     * @param a the first operand
086     * @param b the second operand
087     * @return Subtraction of arguments
088     */
089    public static double minus(double a, double b) {
090        return a - b;
091    }
092
093    /**
094     * Function associated to the numeric "*" operator.
095     * @param a the first operand
096     * @param b the second operand
097     * @return Multiplication of arguments
098     */
099    public static double times(double a, double b) {
100        return a * b;
101    }
102
103    /**
104     * Function associated to the numeric "/" operator.
105     * @param a the first operand
106     * @param b the second operand
107     * @return Division of arguments
108     */
109    public static double divided_by(double a, double b) {
110        return a / b;
111    }
112
113    /**
114     * Function associated to the math modulo "%" operator.
115     * @param a first value
116     * @param b second value
117     * @return {@code a mod b}, e.g., {@code mod(7, 5) = 2}
118     */
119    public static float mod(float a, float b) {
120        return a % b;
121    }
122
123    /**
124     * Creates a list of values, e.g., for the {@code dashes} property.
125     * @param ignored The environment (ignored)
126     * @param args The values to put in a list
127     * @return list of values
128     * @see Arrays#asList(Object[])
129     */
130    public static List<Object> list(Environment ignored, Object... args) {
131        return Arrays.asList(args);
132    }
133
134    /**
135     * Returns the number of elements in a list.
136     * @param lst the list
137     * @return length of the list
138     */
139    public static Integer count(List<?> lst) {
140        return lst.size();
141    }
142
143    /**
144     * Returns the first non-null object.
145     * The name originates from <a href="http://wiki.openstreetmap.org/wiki/MapCSS/0.2/eval">MapCSS standard</a>.
146     * @param ignored The environment (ignored)
147     * @param args arguments
148     * @return the first non-null object
149     * @see Utils#firstNonNull(Object[])
150     */
151    @NullableArguments
152    public static Object any(Environment ignored, Object... args) {
153        return Utils.firstNonNull(args);
154    }
155
156    /**
157     * Get the {@code n}th element of the list {@code lst} (counting starts at 0).
158     * @param lst list
159     * @param n index
160     * @return {@code n}th element of the list, or {@code null} if index out of range
161     * @since 5699
162     */
163    public static Object get(List<?> lst, float n) {
164        int idx = Math.round(n);
165        if (idx >= 0 && idx < lst.size()) {
166            return lst.get(idx);
167        }
168        return null;
169    }
170
171    /**
172     * Splits string {@code toSplit} at occurrences of the separator string {@code sep} and returns a list of matches.
173     * @param sep separator string
174     * @param toSplit string to split
175     * @return list of matches
176     * @see String#split(String)
177     * @since 5699
178     */
179    public static List<String> split(String sep, String toSplit) {
180        return Arrays.asList(toSplit.split(Pattern.quote(sep), -1));
181    }
182
183    /**
184     * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue (arguments from 0.0 to 1.0)
185     * @param r the red component
186     * @param g the green component
187     * @param b the blue component
188     * @return color matching the given components
189     * @see Color#Color(float, float, float)
190     */
191    public static Color rgb(float r, float g, float b) {
192        try {
193            return new Color(r, g, b);
194        } catch (IllegalArgumentException e) {
195            Logging.trace(e);
196            return null;
197        }
198    }
199
200    /**
201     * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue, {@code alpha}
202     * (arguments from 0.0 to 1.0)
203     * @param r the red component
204     * @param g the green component
205     * @param b the blue component
206     * @param alpha the alpha component
207     * @return color matching the given components
208     * @see Color#Color(float, float, float, float)
209     */
210    public static Color rgba(float r, float g, float b, float alpha) {
211        try {
212            return new Color(r, g, b, alpha);
213        } catch (IllegalArgumentException e) {
214            Logging.trace(e);
215            return null;
216        }
217    }
218
219    /**
220     * Create color from hsb color model. (arguments form 0.0 to 1.0)
221     * @param h hue
222     * @param s saturation
223     * @param b brightness
224     * @return the corresponding color
225     */
226    public static Color hsb_color(float h, float s, float b) {
227        try {
228            return Color.getHSBColor(h, s, b);
229        } catch (IllegalArgumentException e) {
230            Logging.trace(e);
231            return null;
232        }
233    }
234
235    /**
236     * Creates a color value from an HTML notation, i.e., {@code #rrggbb}.
237     * @param html HTML notation
238     * @return color matching the given notation
239     */
240    public static Color html2color(String html) {
241        return ColorHelper.html2color(html);
242    }
243
244    /**
245     * Computes the HTML notation ({@code #rrggbb}) for a color value).
246     * @param c color
247     * @return HTML notation matching the given color
248     */
249    public static String color2html(Color c) {
250        return ColorHelper.color2html(c);
251    }
252
253    /**
254     * Get the value of the red color channel in the rgb color model
255     * @param c color
256     * @return the red color channel in the range [0;1]
257     * @see java.awt.Color#getRed()
258     */
259    public static float red(Color c) {
260        return ColorHelper.int2float(c.getRed());
261    }
262
263    /**
264     * Get the value of the green color channel in the rgb color model
265     * @param c color
266     * @return the green color channel in the range [0;1]
267     * @see java.awt.Color#getGreen()
268     */
269    public static float green(Color c) {
270        return ColorHelper.int2float(c.getGreen());
271    }
272
273    /**
274     * Get the value of the blue color channel in the rgb color model
275     * @param c color
276     * @return the blue color channel in the range [0;1]
277     * @see java.awt.Color#getBlue()
278     */
279    public static float blue(Color c) {
280        return ColorHelper.int2float(c.getBlue());
281    }
282
283    /**
284     * Get the value of the alpha channel in the rgba color model
285     * @param c color
286     * @return the alpha channel in the range [0;1]
287     * @see java.awt.Color#getAlpha()
288     */
289    public static float alpha(Color c) {
290        return ColorHelper.int2float(c.getAlpha());
291    }
292
293    /**
294     * Assembles the strings to one.
295     * @param ignored The environment (ignored)
296     * @param args arguments
297     * @return assembled string
298     * @see Collectors#joining
299     */
300    @NullableArguments
301    public static String concat(Environment ignored, Object... args) {
302        return Arrays.stream(args)
303                .filter(Objects::nonNull)
304                .map(String::valueOf)
305                .collect(Collectors.joining());
306    }
307
308    /**
309     * Assembles the strings to one, where the first entry is used as separator.
310     * @param ignored The environment (ignored)
311     * @param args arguments. First one is used as separator
312     * @return assembled string
313     * @see String#join
314     */
315    @NullableArguments
316    public static String join(Environment ignored, String... args) {
317        return String.join(args[0], Arrays.asList(args).subList(1, args.length));
318    }
319
320    /**
321     * Joins a list of {@code values} into a single string with fields separated by {@code separator}.
322     * @param separator the separator
323     * @param values collection of objects
324     * @return assembled string
325     * @see String#join
326     */
327    public static String join_list(final String separator, final List<String> values) {
328        return String.join(separator, values);
329    }
330
331    /**
332     * Returns the value of the property {@code key}, e.g., {@code prop("width")}.
333     * @param env the environment
334     * @param key the property key
335     * @return the property value
336     */
337    public static Object prop(final Environment env, String key) {
338        return prop(env, key, null);
339    }
340
341    /**
342     * Returns the value of the property {@code key} from layer {@code layer}.
343     * @param env the environment
344     * @param key the property key
345     * @param layer layer
346     * @return the property value
347     */
348    public static Object prop(final Environment env, String key, String layer) {
349        return env.getCascade(layer).get(key);
350    }
351
352    /**
353     * Determines whether property {@code key} is set.
354     * @param env the environment
355     * @param key the property key
356     * @return {@code true} if the property is set, {@code false} otherwise
357     */
358    public static Boolean is_prop_set(final Environment env, String key) {
359        return is_prop_set(env, key, null);
360    }
361
362    /**
363     * Determines whether property {@code key} is set on layer {@code layer}.
364     * @param env the environment
365     * @param key the property key
366     * @param layer layer
367     * @return {@code true} if the property is set, {@code false} otherwise
368     */
369    public static Boolean is_prop_set(final Environment env, String key, String layer) {
370        return env.getCascade(layer).containsKey(key);
371    }
372
373    /**
374     * Gets the value of the key {@code key} from the object in question.
375     * @param env the environment
376     * @param key the OSM key
377     * @return the value for given key
378     */
379    public static String tag(final Environment env, String key) {
380        return env.osm == null ? null : env.osm.get(key);
381    }
382
383    /**
384     * Get keys that follow a regex
385     * @param env the environment
386     * @param keyRegex the pattern that the key must match
387     * @return the values for the keys that match the pattern
388     * @see Functions#tag_regex(Environment, String, String)
389     * @since 15315
390     */
391    public static List<String> tag_regex(final Environment env, String keyRegex) {
392        return tag_regex(env, keyRegex, "");
393    }
394
395    /**
396     * Get keys that follow a regex
397     * @param env the environment
398     * @param keyRegex the pattern that the key must match
399     * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
400     * @return the values for the keys that match the pattern
401     * @see Pattern#CASE_INSENSITIVE
402     * @see Pattern#DOTALL
403     * @see Pattern#MULTILINE
404     * @since 15315
405     */
406    public static List<String> tag_regex(final Environment env, String keyRegex, String flags) {
407        int f = parse_regex_flags(flags);
408        Pattern compiled = Pattern.compile(keyRegex, f);
409        return env.osm.getKeys().entrySet().stream()
410                .filter(object -> compiled.matcher(object.getKey()).find())
411                .map(Entry::getValue).collect(Collectors.toList());
412    }
413
414    /**
415     * Parse flags for regex usage. Shouldn't be used in mapcss
416     * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
417     * @return An int that can be used by a {@link Pattern} object
418     * @see Pattern#CASE_INSENSITIVE
419     * @see Pattern#DOTALL
420     * @see Pattern#MULTILINE
421     */
422    private static int parse_regex_flags(String flags) {
423        int f = 0;
424        if (flags.contains("i")) {
425            f |= Pattern.CASE_INSENSITIVE;
426        }
427        if (flags.contains("s")) {
428            f |= Pattern.DOTALL;
429        }
430        if (flags.contains("m")) {
431            f |= Pattern.MULTILINE;
432        }
433        return f;
434    }
435
436    /**
437     * Gets the first non-null value of the key {@code key} from the object's parent(s).
438     * @param env the environment
439     * @param key the OSM key
440     * @return first non-null value of the key {@code key} from the object's parent(s)
441     */
442    public static String parent_tag(final Environment env, String key) {
443        if (env.parent == null) {
444            if (env.osm != null) {
445                // we don't have a matched parent, so just search all referrers
446                return env.osm.getReferrers().stream()
447                        .map(parent -> parent.get(key))
448                        .filter(Objects::nonNull)
449                        .findFirst().orElse(null);
450            }
451            return null;
452        }
453        return env.parent.get(key);
454    }
455
456    /**
457     * Gets a list of all non-null values of the key {@code key} from the object's parent(s).
458     *
459     * The values are sorted according to {@link AlphanumComparator}.
460     * @param env the environment
461     * @param key the OSM key
462     * @return a list of non-null values of the key {@code key} from the object's parent(s)
463     */
464    public static List<String> parent_tags(final Environment env, String key) {
465        if (env.parent == null) {
466            if (env.osm != null) {
467                // we don't have a matched parent, so just search all referrers
468                return env.osm.getReferrers().stream().map(parent -> parent.get(key))
469                        .filter(Objects::nonNull)
470                        .distinct()
471                        .sorted(AlphanumComparator.getInstance())
472                        .collect(StreamUtils.toUnmodifiableList());
473            }
474            return Collections.emptyList();
475        }
476        return Collections.singletonList(env.parent.get(key));
477    }
478
479    /**
480     * Gets the value of the key {@code key} from the object's child.
481     * @param env the environment
482     * @param key the OSM key
483     * @return the value of the key {@code key} from the object's child, or {@code null} if there is no child
484     */
485    public static String child_tag(final Environment env, String key) {
486        return env.child == null ? null : env.child.get(key);
487    }
488
489    /**
490     * Returns the OSM id of the object's parent.
491     * <p>
492     * Parent must be matched by child selector.
493     * @param env the environment
494     * @return the OSM id of the object's parent, if available, or {@code null}
495     * @see IPrimitive#getUniqueId()
496     */
497    public static Long parent_osm_id(final Environment env) {
498        return env.parent == null ? null : env.parent.getUniqueId();
499    }
500
501    /**
502     * Returns the lowest distance between the OSM object and a GPX point
503     * <p>
504     * @param env the environment
505     * @return the distance between the object and the closest gpx point or {@code Double.MAX_VALUE}
506     * @since 14802
507     */
508    public static double gpx_distance(final Environment env) {
509        if (env.osm instanceof OsmPrimitive) {
510            return MainApplication.getLayerManager().getAllGpxData().stream()
511                    .mapToDouble(gpx -> GpxDistance.getLowestDistance((OsmPrimitive) env.osm, gpx))
512                    .min().orElse(Double.MAX_VALUE);
513        }
514        return Double.MAX_VALUE;
515    }
516
517    /**
518     * Determines whether the object has a tag with the given key.
519     * @param env the environment
520     * @param key the OSM key
521     * @return {@code true} if the object has a tag with the given key, {@code false} otherwise
522     */
523    public static boolean has_tag_key(final Environment env, String key) {
524        return env.osm.hasKey(key);
525    }
526
527    /**
528     * Returns the index of node in parent way or member in parent relation.
529     * @param env the environment
530     * @return the index as float. Starts at 1
531     */
532    public static Float index(final Environment env) {
533        if (env.index == null) {
534            return null;
535        }
536        return Float.valueOf(env.index + 1f);
537    }
538
539    /**
540     * Sort an array of strings
541     * @param ignored The environment (ignored)
542     * @param sortables The array to sort
543     * @return The sorted list
544     * @since 15279
545     */
546    public static List<String> sort(Environment ignored, String... sortables) {
547        Arrays.parallelSort(sortables);
548        return Arrays.asList(sortables);
549    }
550
551    /**
552     * Sort a list of strings
553     * @param sortables The list to sort
554     * @return The sorted list
555     * @since 15279
556     */
557    public static List<String> sort_list(List<String> sortables) {
558        Collections.sort(sortables);
559        return sortables;
560    }
561
562    /**
563     * Get unique values
564     * @param ignored The environment (ignored)
565     * @param values A list of values that may have duplicates
566     * @return A list with no duplicates
567     * @since 15323
568     */
569    public static List<String> uniq(Environment ignored, String... values) {
570        return uniq_list(Arrays.asList(values));
571    }
572
573    /**
574     * Get unique values
575     * @param values A list of values that may have duplicates
576     * @return A list with no duplicates
577     * @since 15323
578     */
579    public static List<String> uniq_list(List<String> values) {
580        return values.stream().distinct().collect(Collectors.toList());
581    }
582
583    /**
584     * Returns the role of current object in parent relation, or role of child if current object is a relation.
585     * @param env the environment
586     * @return role of current object in parent relation, or role of child if current object is a relation
587     * @see Environment#getRole()
588     */
589    public static String role(final Environment env) {
590        return env.getRole();
591    }
592
593    /**
594     * Returns the number of primitives in a relation with the specified roles.
595     * @param env the environment
596     * @param roles The roles to count in the relation
597     * @return The number of relation members with the specified role
598     * @since 15196
599     */
600    public static int count_roles(final Environment env, String... roles) {
601        int rValue = 0;
602        if (env.osm instanceof Relation) {
603            List<String> roleList = Arrays.asList(roles);
604            Relation rel = (Relation) env.osm;
605            rValue = (int) rel.getMembers().stream()
606                    .filter(member -> roleList.contains(member.getRole()))
607                    .count();
608        }
609        return rValue;
610    }
611
612    /**
613     * Returns the area of a closed way or multipolygon in square meters or {@code null}.
614     * @param env the environment
615     * @return the area of a closed way or multipolygon in square meters or {@code null}
616     * @see Geometry#computeArea(IPrimitive)
617     */
618    public static Float areasize(final Environment env) {
619        final Double area = Geometry.computeArea(env.osm);
620        return area == null ? null : area.floatValue();
621    }
622
623    /**
624     * Returns the length of the way in metres or {@code null}.
625     * @param env the environment
626     * @return the length of the way in metres or {@code null}.
627     * @see Way#getLength()
628     */
629    public static Float waylength(final Environment env) {
630        if (env.osm instanceof Way) {
631            return (float) ((Way) env.osm).getLength();
632        } else {
633            return null;
634        }
635    }
636
637    /**
638     * Function associated to the logical "!" operator.
639     * @param b boolean value
640     * @return {@code true} if {@code !b}
641     */
642    public static boolean not(boolean b) {
643        return !b;
644    }
645
646    /**
647     * Function associated to the logical "&gt;=" operator.
648     * @param a first value
649     * @param b second value
650     * @return {@code true} if {@code a >= b}
651     */
652    public static boolean greater_equal(float a, float b) {
653        return a >= b;
654    }
655
656    /**
657     * Function associated to the logical "&lt;=" operator.
658     * @param a first value
659     * @param b second value
660     * @return {@code true} if {@code a <= b}
661     */
662    public static boolean less_equal(float a, float b) {
663        return a <= b;
664    }
665
666    /**
667     * Function associated to the logical "&gt;" operator.
668     * @param a first value
669     * @param b second value
670     * @return {@code true} if {@code a > b}
671     */
672    public static boolean greater(float a, float b) {
673        return a > b;
674    }
675
676    /**
677     * Function associated to the logical "&lt;" operator.
678     * @param a first value
679     * @param b second value
680     * @return {@code true} if {@code a < b}
681     */
682    public static boolean less(float a, float b) {
683        return a < b;
684    }
685
686    /**
687     * Converts an angle in degrees to radians.
688     * @param degree the angle in degrees
689     * @return the angle in radians
690     * @see Math#toRadians(double)
691     */
692    public static double degree_to_radians(double degree) {
693        return Utils.toRadians(degree);
694    }
695
696    /**
697     * Converts an angle diven in cardinal directions to radians.
698     * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast},
699     * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south},
700     * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}.
701     * @param cardinal the angle in cardinal directions.
702     * @return the angle in radians
703     * @see RotationAngle#parseCardinalRotation(String)
704     */
705    public static Double cardinal_to_radians(String cardinal) {
706        try {
707            return RotationAngle.parseCardinalRotation(cardinal);
708        } catch (IllegalArgumentException ignore) {
709            Logging.trace(ignore);
710            return null;
711        }
712    }
713
714    /**
715     * Determines if the objects {@code a} and {@code b} are equal.
716     * @param a First object
717     * @param b Second object
718     * @return {@code true} if objects are equal, {@code false} otherwise
719     * @see Object#equals(Object)
720     */
721    public static boolean equal(Object a, Object b) {
722        if (a.getClass() == b.getClass()) return a.equals(b);
723        if (a.equals(Cascade.convertTo(b, a.getClass()))) return true;
724        return b.equals(Cascade.convertTo(a, b.getClass()));
725    }
726
727    /**
728     * Determines if the objects {@code a} and {@code b} are not equal.
729     * @param a First object
730     * @param b Second object
731     * @return {@code false} if objects are equal, {@code true} otherwise
732     * @see Object#equals(Object)
733     */
734    public static boolean not_equal(Object a, Object b) {
735        return !equal(a, b);
736    }
737
738    /**
739     * Determines whether the JOSM search with {@code searchStr} applies to the object.
740     * @param env the environment
741     * @param searchStr the search string
742     * @return {@code true} if the JOSM search with {@code searchStr} applies to the object
743     * @see SearchCompiler
744     */
745    public static Boolean JOSM_search(final Environment env, String searchStr) {
746        Match m;
747        try {
748            m = SearchCompiler.compile(searchStr);
749        } catch (SearchParseError ex) {
750            Logging.trace(ex);
751            return null;
752        }
753        return m.match(env.osm);
754    }
755
756    /**
757     * Obtains the JOSM'key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
758     * and defaults to {@code def} if that is null.
759     *
760     * If the default value can be {@linkplain Cascade#convertTo converted} to a {@link Color},
761     * the {@link NamedColorProperty} is retrieved as string.
762     *
763     * @param env the environment
764     * @param key Key in JOSM preference
765     * @param def Default value
766     * @return value for key, or default value if not found
767     */
768    public static String JOSM_pref(Environment env, String key, String def) {
769        return MapPaintStyles.getStyles().getPreferenceCached(env != null ? env.source : null, key, def);
770    }
771
772    /**
773     * Tests if string {@code target} matches pattern {@code pattern}
774     * @param pattern The regex expression
775     * @param target The character sequence to be matched
776     * @return {@code true} if, and only if, the entire region sequence matches the pattern
777     * @see Pattern#matches(String, CharSequence)
778     * @since 5699
779     */
780    public static boolean regexp_test(String pattern, String target) {
781        return Pattern.matches(pattern, target);
782    }
783
784    /**
785     * Tests if string {@code target} matches pattern {@code pattern}
786     * @param pattern The regex expression
787     * @param target The character sequence to be matched
788     * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
789     * @return {@code true} if, and only if, the entire region sequence matches the pattern
790     * @see Pattern#CASE_INSENSITIVE
791     * @see Pattern#DOTALL
792     * @see Pattern#MULTILINE
793     * @since 5699
794     */
795    public static boolean regexp_test(String pattern, String target, String flags) {
796        int f = parse_regex_flags(flags);
797        return Pattern.compile(pattern, f).matcher(target).matches();
798    }
799
800    /**
801     * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
802     * The first element (index 0) is the complete match (i.e. string).
803     * Further elements correspond to the bracketed parts of the regular expression.
804     * @param pattern The regex expression
805     * @param target The character sequence to be matched
806     * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
807     * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
808     * @see Pattern#CASE_INSENSITIVE
809     * @see Pattern#DOTALL
810     * @see Pattern#MULTILINE
811     * @since 5701
812     */
813    public static List<String> regexp_match(String pattern, String target, String flags) {
814        int f = parse_regex_flags(flags);
815        return Utils.getMatches(Pattern.compile(pattern, f).matcher(target));
816    }
817
818    /**
819     * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
820     * The first element (index 0) is the complete match (i.e. string).
821     * Further elements correspond to the bracketed parts of the regular expression.
822     * @param pattern The regex expression
823     * @param target The character sequence to be matched
824     * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
825     * @since 5701
826     */
827    public static List<String> regexp_match(String pattern, String target) {
828        return Utils.getMatches(Pattern.compile(pattern).matcher(target));
829    }
830
831    /**
832     * Returns the OSM id of the current object.
833     * @param env the environment
834     * @return the OSM id of the current object
835     * @see IPrimitive#getUniqueId()
836     */
837    public static long osm_id(final Environment env) {
838        return env.osm.getUniqueId();
839    }
840
841    /**
842     * Returns the OSM user name who last touched the current object.
843     * @param env the environment
844     * @return the OSM user name who last touched the current object
845     * @see IPrimitive#getUser
846     * @since 15246
847     */
848    public static String osm_user_name(final Environment env) {
849        return env.osm.getUser().getName();
850    }
851
852    /**
853     * Returns the OSM user id who last touched the current object.
854     * @param env the environment
855     * @return the OSM user id who last touched the current object
856     * @see IPrimitive#getUser
857     * @since 15246
858     */
859    public static long osm_user_id(final Environment env) {
860        return env.osm.getUser().getId();
861    }
862
863    /**
864     * Returns the version number of the current object.
865     * @param env the environment
866     * @return the version number of the current object
867     * @see IPrimitive#getVersion
868     * @since 15246
869     */
870    public static int osm_version(final Environment env) {
871        return env.osm.getVersion();
872    }
873
874    /**
875     * Returns the id of the changeset the current object was last uploaded to.
876     * @param env the environment
877     * @return the id of the changeset the current object was last uploaded to
878     * @see IPrimitive#getChangesetId
879     * @since 15246
880     */
881    public static int osm_changeset_id(final Environment env) {
882        return env.osm.getChangesetId();
883    }
884
885    /**
886     * Returns the time of last modification to the current object, as timestamp.
887     * @param env the environment
888     * @return the time of last modification to the current object, as timestamp
889     * @see IPrimitive#getRawTimestamp
890     * @since 15246
891     */
892    public static int osm_timestamp(final Environment env) {
893        return env.osm.getRawTimestamp();
894    }
895
896    /**
897     * Translates some text for the current locale. The first argument is the text to translate,
898     * and the subsequent arguments are parameters for the string indicated by <code>{0}</code>, <code>{1}</code>, …
899     * @param ignored The environment (ignored)
900     * @param args arguments
901     * @return the translated string
902     */
903    @NullableArguments
904    public static String tr(Environment ignored, String... args) {
905        final String text = args[0];
906        System.arraycopy(args, 1, args, 0, args.length - 1);
907        return org.openstreetmap.josm.tools.I18n.tr(text, (Object[]) args);
908    }
909
910    /**
911     * Returns the substring of {@code s} starting at index {@code begin} (inclusive, 0-indexed).
912     * @param s The base string
913     * @param begin The start index
914     * @return the substring
915     * @see String#substring(int)
916     */
917    public static String substring(String s, /* due to missing Cascade.convertTo for int*/ float begin) {
918        return s == null ? null : s.substring((int) begin);
919    }
920
921    /**
922     * Returns the substring of {@code s} starting at index {@code begin} (inclusive)
923     * and ending at index {@code end}, (exclusive, 0-indexed).
924     * @param s The base string
925     * @param begin The start index
926     * @param end The end index
927     * @return the substring
928     * @see String#substring(int, int)
929     */
930    public static String substring(String s, float begin, float end) {
931        return s == null ? null : s.substring((int) begin, (int) end);
932    }
933
934    /**
935     * Replaces in {@code s} every {@code} target} substring by {@code replacement}.
936     * @param s The source string
937     * @param target The sequence of char values to be replaced
938     * @param replacement The replacement sequence of char values
939     * @return The resulting string
940     * @see String#replace(CharSequence, CharSequence)
941     */
942    public static String replace(String s, String target, String replacement) {
943        return s == null ? null : s.replace(target, replacement);
944    }
945
946    /**
947     * Converts string {@code s} to uppercase.
948     * @param s The source string
949     * @return The resulting string
950     * @see String#toUpperCase(Locale)
951     * @since 11756
952     */
953    public static String upper(String s) {
954        return s == null ? null : s.toUpperCase(Locale.ENGLISH);
955    }
956
957    /**
958     * Converts string {@code s} to lowercase.
959     * @param s The source string
960     * @return The resulting string
961     * @see String#toLowerCase(Locale)
962     * @since 11756
963     */
964    public static String lower(String s) {
965        return s == null ? null : s.toLowerCase(Locale.ENGLISH);
966    }
967
968    /**
969     * Returns a title-cased version of the string where words start with an uppercase character and the remaining characters are lowercase
970     *
971     * Also known as "capitalize".
972     * @param str The source string
973     * @return The resulting string
974     * @see Character#toTitleCase(char)
975     * @since 17613
976     */
977    public static String title(String str) {
978        // adapted from org.apache.commons.lang3.text.WordUtils.capitalize
979        if (str == null) {
980            return null;
981        }
982        final char[] buffer = str.toCharArray();
983        boolean capitalizeNext = true;
984        for (int i = 0; i < buffer.length; i++) {
985            final char ch = buffer[i];
986            if (Character.isWhitespace(ch)) {
987                capitalizeNext = true;
988            } else if (capitalizeNext) {
989                buffer[i] = Character.toTitleCase(ch);
990                capitalizeNext = false;
991            } else {
992                buffer[i] = Character.toLowerCase(ch);
993            }
994        }
995        return new String(buffer);
996    }
997
998    /**
999     * Trim whitespaces from the string {@code s}.
1000     * @param s The source string
1001     * @return The resulting string
1002     * @see Utils#strip
1003     * @since 11756
1004     */
1005    public static String trim(String s) {
1006        return Utils.strip(s);
1007    }
1008
1009    /**
1010     * Trim whitespaces from the strings {@code strings}.
1011     *
1012     * @param strings The list of strings to strip
1013     * @return The resulting string
1014     * @see Utils#strip
1015     * @since 15591
1016     */
1017    public static List<String> trim_list(List<String> strings) {
1018        return strings.stream().map(Utils::strip).filter(str -> !str.isEmpty()).collect(Collectors.toList());
1019    }
1020
1021    /**
1022     * Check if two strings are similar, but not identical, i.e., have a Levenshtein distance of 1 or 2.
1023     * @param string1 first string to compare
1024     * @param string2 second string to compare
1025     * @return true if the normalized strings are different but only a "little bit"
1026     * @see Utils#isSimilar
1027     * @since 14371
1028     */
1029    public static boolean is_similar(String string1, String string2) {
1030        return Utils.isSimilar(string1, string2);
1031    }
1032
1033    /**
1034     * Percent-decode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
1035     * This is especially useful for wikipedia titles
1036     * @param s url-encoded string
1037     * @return the decoded string, or original in case of an error
1038     * @since 11756
1039     */
1040    public static String URL_decode(String s) {
1041        if (s == null) return null;
1042        try {
1043            return Utils.decodeUrl(s);
1044        } catch (IllegalStateException e) {
1045            Logging.debug(e);
1046            return s;
1047        }
1048    }
1049
1050    /**
1051     * Percent-encode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
1052     * This is especially useful for data urls, e.g.
1053     * <code>concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
1054     * @param s arbitrary string
1055     * @return the encoded string
1056     */
1057    public static String URL_encode(String s) {
1058        return s == null ? null : Utils.encodeUrl(s);
1059    }
1060
1061    /**
1062     * XML-encode a string.
1063     *
1064     * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
1065     * @param s arbitrary string
1066     * @return the encoded string
1067     */
1068    public static String XML_encode(String s) {
1069        return s == null ? null : XmlWriter.encode(s);
1070    }
1071
1072    /**
1073     * Calculates the CRC32 checksum from a string (based on RFC 1952).
1074     * @param s the string
1075     * @return long value from 0 to 2^32-1
1076     */
1077    public static long CRC32_checksum(String s) {
1078        CRC32 cs = new CRC32();
1079        cs.update(s.getBytes(StandardCharsets.UTF_8));
1080        return cs.getValue();
1081    }
1082
1083    /**
1084     * check if there is right-hand traffic at the current location
1085     * @param env the environment
1086     * @return true if there is right-hand traffic
1087     * @since 7193
1088     */
1089    public static boolean is_right_hand_traffic(Environment env) {
1090        return RightAndLefthandTraffic.isRightHandTraffic(center(env));
1091    }
1092
1093    /**
1094     * Determines whether the way is {@link Geometry#isClockwise closed and oriented clockwise},
1095     * or non-closed and the {@link Geometry#angleIsClockwise 1st, 2nd and last node are in clockwise order}.
1096     *
1097     * @param env the environment
1098     * @return true if the way is closed and oriented clockwise
1099     */
1100    public static boolean is_clockwise(Environment env) {
1101        if (!(env.osm instanceof Way)) {
1102            return false;
1103        }
1104        final Way way = (Way) env.osm;
1105        return (way.isClosed() && Geometry.isClockwise(way))
1106            || (!way.isClosed() && way.getNodesCount() > 2 && Geometry.angleIsClockwise(way.getNode(0), way.getNode(1), way.lastNode()));
1107    }
1108
1109    /**
1110     * Determines whether the way is {@link Geometry#isClockwise closed and oriented anticlockwise},
1111     * or non-closed and the {@link Geometry#angleIsClockwise 1st, 2nd and last node are in anticlockwise order}.
1112     *
1113     * @param env the environment
1114     * @return true if the way is closed and oriented clockwise
1115     */
1116    public static boolean is_anticlockwise(Environment env) {
1117        if (!(env.osm instanceof Way)) {
1118            return false;
1119        }
1120        final Way way = (Way) env.osm;
1121        return (way.isClosed() && !Geometry.isClockwise(way))
1122            || (!way.isClosed() && way.getNodesCount() > 2 && !Geometry.angleIsClockwise(way.getNode(0), way.getNode(1), way.lastNode()));
1123    }
1124
1125    /**
1126     * Prints the object to the command line (for debugging purpose).
1127     * @param o the object
1128     * @return the same object, unchanged
1129     */
1130    @NullableArguments
1131    public static Object print(Object o) {
1132        System.out.print(o == null ? "none" : o.toString());
1133        return o;
1134    }
1135
1136    /**
1137     * Prints the object to the command line, with new line at the end
1138     * (for debugging purpose).
1139     * @param o the object
1140     * @return the same object, unchanged
1141     */
1142    @NullableArguments
1143    public static Object println(Object o) {
1144        System.out.println(o == null ? "none" : o.toString());
1145        return o;
1146    }
1147
1148    /**
1149     * Get the number of tags for the current primitive.
1150     * @param env the environment
1151     * @return number of tags
1152     */
1153    public static int number_of_tags(Environment env) {
1154        return env.osm.getNumKeys();
1155    }
1156
1157    /**
1158     * Get value of a setting.
1159     * @param env the environment
1160     * @param key setting key (given as layer identifier, e.g. setting::mykey {...})
1161     * @return the value of the setting (calculated when the style is loaded)
1162     */
1163    public static Object setting(Environment env, String key) {
1164        return env.source.settingValues.get(key);
1165    }
1166
1167    /**
1168     * Returns the center of the environment OSM primitive.
1169     * @param env the environment
1170     * @return the center of the environment OSM primitive
1171     * @since 11247
1172     */
1173    public static LatLon center(Environment env) {
1174        return env.osm instanceof Node ? ((Node) env.osm).getCoor() : env.osm.getBBox().getCenter();
1175    }
1176
1177    /**
1178     * Determines if the object is inside territories matching given ISO3166 codes.
1179     * @param env the environment
1180     * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
1181     * @return {@code true} if the object is inside territory matching given ISO3166 codes
1182     * @since 11247
1183     */
1184    public static boolean inside(Environment env, String codes) {
1185        return Arrays.stream(codes.toUpperCase(Locale.ENGLISH).split(",", -1))
1186                .anyMatch(code -> Territories.isIso3166Code(code.trim(), center(env)));
1187    }
1188
1189    /**
1190     * Determines if the object is outside territories matching given ISO3166 codes.
1191     * @param env the environment
1192     * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
1193     * @return {@code true} if the object is outside territory matching given ISO3166 codes
1194     * @since 11247
1195     */
1196    public static boolean outside(Environment env, String codes) {
1197        return !inside(env, codes);
1198    }
1199
1200    /**
1201     * Determines if the object centroid lies at given lat/lon coordinates.
1202     * @param env the environment
1203     * @param lat latitude, i.e., the north-south position in degrees
1204     * @param lon longitude, i.e., the east-west position in degrees
1205     * @return {@code true} if the object centroid lies at given lat/lon coordinates
1206     * @since 12514
1207     */
1208    public static boolean at(Environment env, double lat, double lon) {
1209        return new LatLon(lat, lon).equalsEpsilon(center(env));
1210    }
1211
1212    /**
1213     * Parses the string argument as a boolean.
1214     * @param value the {@code String} containing the boolean representation to be parsed
1215     * @return the boolean represented by the string argument
1216     * @see Boolean#parseBoolean
1217     * @since 16110
1218     */
1219    public static boolean to_boolean(String value) {
1220        return Boolean.parseBoolean(value);
1221    }
1222
1223    /**
1224     * Parses the string argument as a byte.
1225     * @param value the {@code String} containing the byte representation to be parsed
1226     * @return the byte represented by the string argument
1227     * @see Byte#parseByte
1228     * @since 16110
1229     */
1230    public static byte to_byte(String value) {
1231        return Byte.parseByte(value);
1232    }
1233
1234    /**
1235     * Parses the string argument as a short.
1236     * @param value the {@code String} containing the short representation to be parsed
1237     * @return the short represented by the string argument
1238     * @see Short#parseShort
1239     * @since 16110
1240     */
1241    public static short to_short(String value) {
1242        return Short.parseShort(value);
1243    }
1244
1245    /**
1246     * Parses the string argument as an int.
1247     * @param value the {@code String} containing the int representation to be parsed
1248     * @return the int represented by the string argument
1249     * @see Integer#parseInt
1250     * @since 16110
1251     */
1252    public static int to_int(String value) {
1253        return Integer.parseInt(value);
1254    }
1255
1256    /**
1257     * Parses the string argument as a long.
1258     * @param value the {@code String} containing the long representation to be parsed
1259     * @return the long represented by the string argument
1260     * @see Long#parseLong
1261     * @since 16110
1262     */
1263    public static long to_long(String value) {
1264        return Long.parseLong(value);
1265    }
1266
1267    /**
1268     * Parses the string argument as a float.
1269     * @param value the {@code String} containing the float representation to be parsed
1270     * @return the float represented by the string argument
1271     * @see Float#parseFloat
1272     * @since 16110
1273     */
1274    public static float to_float(String value) {
1275        return Float.parseFloat(value);
1276    }
1277
1278    /**
1279     * Parses the string argument as a double.
1280     * @param value the {@code String} containing the double representation to be parsed
1281     * @return the double represented by the string argument
1282     * @see Double#parseDouble
1283     * @since 16110
1284     */
1285    public static double to_double(String value) {
1286        return Double.parseDouble(value);
1287    }
1288}