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