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