001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.HashSet;
007import java.util.Locale;
008import java.util.Map;
009import java.util.Objects;
010import java.util.Set;
011
012import org.openstreetmap.josm.data.coor.LatLon;
013import org.openstreetmap.josm.tools.CheckParameterUtil;
014import org.openstreetmap.josm.tools.TextTagParser;
015
016/**
017 * Utility methods/constants that are useful for generic OSM tag handling.
018 */
019public final class OsmUtils {
020
021    private static final Set<String> TRUE_VALUES = new HashSet<>(Arrays
022            .asList("true", "yes", "1", "on"));
023    private static final Set<String> FALSE_VALUES = new HashSet<>(Arrays
024            .asList("false", "no", "0", "off"));
025    private static final Set<String> REVERSE_VALUES = new HashSet<>(Arrays
026            .asList("reverse", "-1"));
027
028    /**
029     * A value that should be used to indicate true
030     * @since 12186
031     */
032    public static final String TRUE_VALUE = "yes";
033    /**
034     * A value that should be used to indicate false
035     * @since 12186
036     */
037    public static final String FALSE_VALUE = "no";
038    /**
039     * A value that should be used to indicate that a property applies reversed on the way
040     * @since 12186
041     */
042    public static final String REVERSE_VALUE = "-1";
043
044    /**
045     * Discouraged synonym for {@link #TRUE_VALUE}
046     */
047    public static final String trueval = TRUE_VALUE;
048    /**
049     * Discouraged synonym for {@link #FALSE_VALUE}
050     */
051    public static final String falseval = FALSE_VALUE;
052    /**
053     * Discouraged synonym for {@link #REVERSE_VALUE}
054     */
055    public static final String reverseval = REVERSE_VALUE;
056
057    private OsmUtils() {
058        // Hide default constructor for utils classes
059    }
060
061    /**
062     * Converts a string to a boolean value
063     * @param value The string to convert
064     * @return {@link Boolean#TRUE} if that string represents a true value,
065     *         {@link Boolean#FALSE} if it represents a false value,
066     *         <code>null</code> otherwise.
067     */
068    public static Boolean getOsmBoolean(String value) {
069        if (value == null) return null;
070        String lowerValue = value.toLowerCase(Locale.ENGLISH);
071        if (TRUE_VALUES.contains(lowerValue)) return Boolean.TRUE;
072        if (FALSE_VALUES.contains(lowerValue)) return Boolean.FALSE;
073        return null;
074    }
075
076    /**
077     * Normalizes the OSM boolean value
078     * @param value The tag value
079     * @return The best true/false value or the old value if the input cannot be converted.
080     * @see #TRUE_VALUE
081     * @see #FALSE_VALUE
082     */
083    public static String getNamedOsmBoolean(String value) {
084        Boolean res = getOsmBoolean(value);
085        return res == null ? value : (res ? trueval : falseval);
086    }
087
088    /**
089     * Check if the value is a value indicating that a property applies reversed.
090     * @param value The value to check
091     * @return true if it is reversed.
092     */
093    public static boolean isReversed(String value) {
094        return REVERSE_VALUES.contains(value);
095    }
096
097    /**
098     * Check if a tag value represents a boolean true value
099     * @param value The value to check
100     * @return true if it is a true value.
101     */
102    public static boolean isTrue(String value) {
103        return TRUE_VALUES.contains(value);
104    }
105
106    /**
107     * Check if a tag value represents a boolean false value
108     * @param value The value to check
109     * @return true if it is a true value.
110     */
111    public static boolean isFalse(String value) {
112        return FALSE_VALUES.contains(value);
113    }
114
115    /**
116     * Creates a new OSM primitive according to the given assertion. Originally written for unit tests,
117     * this can also be used in another places like validation of local MapCSS validator rules.
118     * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail")
119     * @return a new OSM primitive according to the given assertion
120     * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it
121     * @since 7356
122     */
123    public static OsmPrimitive createPrimitive(String assertion) {
124        CheckParameterUtil.ensureParameterNotNull(assertion, "assertion");
125        final String[] x = assertion.split("\\s+", 2);
126        final OsmPrimitive p = "n".equals(x[0]) || "node".equals(x[0])
127                ? new Node(LatLon.ZERO)
128                : "w".equals(x[0]) || "way".equals(x[0]) || /*for MapCSS related usage*/ "area".equals(x[0])
129                ? new Way()
130                : "r".equals(x[0]) || "relation".equals(x[0])
131                ? new Relation()
132                : null;
133        if (p == null) {
134            throw new IllegalArgumentException("Expecting n/node/w/way/r/relation/area, but got '" + x[0] + '\'');
135        }
136        if (x.length > 1) {
137            for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) {
138                p.put(i.getKey(), i.getValue());
139            }
140        }
141        return p;
142    }
143
144    /**
145     * Returns the layer value of primitive (null for layer 0).
146     * @param w OSM primitive
147     * @return the value of "layer" key, or null if absent or set to 0 (default value)
148     * @since 12986
149     * @since 13637 (signature)
150     */
151    public static String getLayer(IPrimitive w) {
152        String layer1 = w.get("layer");
153        if ("0".equals(layer1)) {
154            layer1 = null; // 0 is default value for layer.
155        }
156        return layer1;
157    }
158
159    /**
160     * Determines if the given collection contains primitives, and that none of them belong to a locked layer.
161     * @param collection collection of OSM primitives
162     * @return {@code true} if the given collection is not empty and does not contain any primitive in a locked layer.
163     * @since 13611
164     * @since 13957 (signature)
165     */
166    public static boolean isOsmCollectionEditable(Collection<? extends IPrimitive> collection) {
167        return collection != null && !collection.isEmpty()
168            && collection.stream().map(IPrimitive::getDataSet).filter(Objects::nonNull).noneMatch(OsmData::isLocked);
169    }
170}