001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import java.util.Collection; 005import java.util.Locale; 006import java.util.Map; 007import java.util.regex.Pattern; 008import java.util.stream.Stream; 009 010import org.openstreetmap.josm.data.coor.LatLon; 011import org.openstreetmap.josm.tools.CheckParameterUtil; 012import org.openstreetmap.josm.tools.TextTagParser; 013import org.openstreetmap.josm.tools.Utils; 014 015/** 016 * Utility methods/constants that are useful for generic OSM tag handling. 017 */ 018public final class OsmUtils { 019 020 /** 021 * A value that should be used to indicate true 022 * @since 12186 023 */ 024 public static final String TRUE_VALUE = "yes"; 025 /** 026 * A value that should be used to indicate false 027 * @since 12186 028 */ 029 public static final String FALSE_VALUE = "no"; 030 /** 031 * A value that should be used to indicate that a property applies reversed on the way 032 * @since 12186 033 */ 034 public static final String REVERSE_VALUE = "-1"; 035 036 /** 037 * Discouraged synonym for {@link #TRUE_VALUE} 038 */ 039 public static final String trueval = TRUE_VALUE; 040 /** 041 * Discouraged synonym for {@link #FALSE_VALUE} 042 */ 043 public static final String falseval = FALSE_VALUE; 044 /** 045 * Discouraged synonym for {@link #REVERSE_VALUE} 046 */ 047 public static final String reverseval = REVERSE_VALUE; 048 049 private OsmUtils() { 050 // Hide default constructor for utils classes 051 } 052 053 /** 054 * Converts a string to a boolean value 055 * @param value The string to convert 056 * @return {@link Boolean#TRUE} if that string represents a true value, 057 * {@link Boolean#FALSE} if it represents a false value, 058 * <code>null</code> otherwise. 059 */ 060 public static Boolean getOsmBoolean(String value) { 061 if (value == null) return null; 062 String lowerValue = value.toLowerCase(Locale.ENGLISH); 063 if (isTrue(lowerValue)) return Boolean.TRUE; 064 if (isFalse(lowerValue)) return Boolean.FALSE; 065 return null; 066 } 067 068 /** 069 * Normalizes the OSM boolean value 070 * @param value The tag value 071 * @return The best true/false value or the old value if the input cannot be converted. 072 * @see #TRUE_VALUE 073 * @see #FALSE_VALUE 074 */ 075 public static String getNamedOsmBoolean(String value) { 076 Boolean res = getOsmBoolean(value); 077 return res == null ? value : (res ? trueval : falseval); 078 } 079 080 /** 081 * Check if the value is a value indicating that a property applies reversed. 082 * @param value The value to check 083 * @return true if it is reversed. 084 */ 085 public static boolean isReversed(String value) { 086 if (value == null) { 087 return false; 088 } 089 switch (value) { 090 case "reverse": 091 case "-1": 092 return true; 093 default: 094 return false; 095 } 096 } 097 098 /** 099 * Check if a tag value represents a boolean true value 100 * @param value The value to check 101 * @return true if it is a true value. 102 */ 103 public static boolean isTrue(String value) { 104 if (value == null) { 105 return false; 106 } 107 switch (value) { 108 case "true": 109 case "yes": 110 case "1": 111 case "on": 112 return true; 113 default: 114 return false; 115 } 116 } 117 118 /** 119 * Check if a tag value represents a boolean false value 120 * @param value The value to check 121 * @return true if it is a true value. 122 */ 123 public static boolean isFalse(String value) { 124 if (value == null) { 125 return false; 126 } 127 switch (value) { 128 case "false": 129 case "no": 130 case "0": 131 case "off": 132 return true; 133 default: 134 return false; 135 } 136 } 137 138 /** 139 * Creates a new OSM primitive around (0,0) according to the given assertion. Originally written for unit tests, 140 * this can also be used in another places like validation of local MapCSS validator rules. 141 * Ways and relations created using this method are empty. 142 * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail") 143 * @return a new OSM primitive according to the given assertion 144 * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it 145 * @since 7356 146 */ 147 public static OsmPrimitive createPrimitive(String assertion) { 148 return createPrimitive(assertion, LatLon.ZERO, false); 149 } 150 151 /** 152 * Creates a new OSM primitive according to the given assertion. Originally written for unit tests, 153 * this can also be used in another places like validation of local MapCSS validator rules. 154 * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail") 155 * @param around the coordinate at which the primitive will be located 156 * @param enforceLocation if {@code true}, ways and relations will not be empty to force a physical location 157 * @return a new OSM primitive according to the given assertion 158 * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it 159 * @since 14486 160 */ 161 public static OsmPrimitive createPrimitive(String assertion, LatLon around, boolean enforceLocation) { 162 CheckParameterUtil.ensureParameterNotNull(assertion, "assertion"); 163 final String[] x = assertion.split("\\s+", 2); 164 final OsmPrimitive p = "n".equals(x[0]) || "node".equals(x[0]) 165 ? newNode(around) 166 : "w".equals(x[0]) || "way".equals(x[0]) || /*for MapCSS related usage*/ "area".equals(x[0]) 167 ? newWay(around, enforceLocation) 168 : "r".equals(x[0]) || "relation".equals(x[0]) 169 ? newRelation(around, enforceLocation) 170 : null; 171 if (p == null) { 172 throw new IllegalArgumentException( 173 "Expecting n/node/w/way/r/relation/area, but got '" + x[0] + "' for assertion '" + assertion + '\''); 174 } 175 if (x.length > 1) { 176 for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) { 177 p.put(i.getKey(), i.getValue()); 178 } 179 } 180 return p; 181 } 182 183 private static Node newNode(LatLon around) { 184 return new Node(around); 185 } 186 187 private static Way newWay(LatLon around, boolean enforceLocation) { 188 Way w = new Way(); 189 if (enforceLocation) { 190 w.addNode(newNode(new LatLon(around.lat()+0.1, around.lon()))); 191 w.addNode(newNode(new LatLon(around.lat()-0.1, around.lon()))); 192 } 193 return w; 194 } 195 196 private static Relation newRelation(LatLon around, boolean enforceLocation) { 197 Relation r = new Relation(); 198 if (enforceLocation) { 199 r.addMember(new RelationMember(null, newNode(around))); 200 } 201 return r; 202 } 203 204 /** 205 * Returns the layer value of primitive (null for layer 0). 206 * @param w OSM primitive 207 * @return the value of "layer" key, or null if absent or set to 0 (default value) 208 * @since 12986 209 * @since 13637 (signature) 210 */ 211 public static String getLayer(IPrimitive w) { 212 String layer1 = w.get("layer"); 213 if ("0".equals(layer1)) { 214 layer1 = null; // 0 is default value for layer. 215 } 216 return layer1; 217 } 218 219 /** 220 * Determines if the given collection contains primitives, and that none of them belong to a locked layer. 221 * @param collection collection of OSM primitives 222 * @return {@code true} if the given collection is not empty and does not contain any primitive in a locked layer. 223 * @since 13611 224 * @since 13957 (signature) 225 */ 226 public static boolean isOsmCollectionEditable(Collection<? extends IPrimitive> collection) { 227 if (Utils.isEmpty(collection)) { 228 return false; 229 } 230 // see #16510: optimization: only consider the first primitive, as collection always refer to the same dataset 231 OsmData<?, ?, ?, ?> ds = collection.iterator().next().getDataSet(); 232 return ds == null || !ds.isLocked(); 233 } 234 235 /** 236 * Splits a tag value by <a href="https://wiki.openstreetmap.org/wiki/Semi-colon_value_separator">semi-colon value separator</a>. 237 * Spaces around the ; are ignored. 238 * 239 * @param value the value to separate 240 * @return the separated values as Stream 241 * @since 15671 242 */ 243 public static Stream<String> splitMultipleValues(String value) { 244 return Pattern.compile("\\s*;\\s*").splitAsStream(value); 245 } 246}