001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.util.Arrays; 008import java.util.Collections; 009import java.util.HashSet; 010import java.util.Set; 011 012import org.openstreetmap.josm.data.osm.OsmPrimitive; 013import org.openstreetmap.josm.data.osm.OsmUtils; 014import org.openstreetmap.josm.data.osm.Relation; 015import org.openstreetmap.josm.data.osm.Way; 016import org.openstreetmap.josm.data.validation.Severity; 017import org.openstreetmap.josm.data.validation.Test; 018import org.openstreetmap.josm.data.validation.TestError; 019 020/** 021 * Check area type ways for errors 022 * 023 * @author stoecker 024 * @since 3669 025 */ 026public class UnclosedWays extends Test { 027 028 /** 029 * Constructs a new {@code UnclosedWays} test. 030 */ 031 public UnclosedWays() { 032 super(tr("Unclosed Ways"), tr("This tests if ways which should be circular are closed.")); 033 } 034 035 /** 036 * A check performed by UnclosedWays test. 037 * @since 6390 038 */ 039 private static class UnclosedWaysCheck { 040 /** The unique numeric code for this check */ 041 public final int code; 042 /** The OSM key checked */ 043 public final String key; 044 /** The English message */ 045 private final String engMessage; 046 /** The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false */ 047 private final Set<String> specialValues; 048 /** The boolean indicating if special values must be ignored or considered only */ 049 private final boolean ignore; 050 051 /** 052 * Constructs a new {@code UnclosedWaysCheck}. 053 * @param code The unique numeric code for this check 054 * @param key The OSM key checked 055 * @param engMessage The English message 056 */ 057 UnclosedWaysCheck(int code, String key, String engMessage) { 058 this(code, key, engMessage, Collections.<String>emptySet()); 059 } 060 061 /** 062 * Constructs a new {@code UnclosedWaysCheck}. 063 * @param code The unique numeric code for this check 064 * @param key The OSM key checked 065 * @param engMessage The English message 066 * @param ignoredValues The ignored values. 067 */ 068 UnclosedWaysCheck(int code, String key, String engMessage, Set<String> ignoredValues) { 069 this(code, key, engMessage, ignoredValues, true); 070 } 071 072 /** 073 * Constructs a new {@code UnclosedWaysCheck}. 074 * @param code The unique numeric code for this check 075 * @param key The OSM key checked 076 * @param engMessage The English message 077 * @param specialValues The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false 078 * @param ignore indicates if special values must be ignored or considered only 079 */ 080 UnclosedWaysCheck(int code, String key, String engMessage, Set<String> specialValues, boolean ignore) { 081 this.code = code; 082 this.key = key; 083 this.engMessage = engMessage; 084 this.specialValues = specialValues; 085 this.ignore = ignore; 086 } 087 088 /** 089 * Returns the test error of the given way, if any. 090 * @param w The way to check 091 * @param test parent test 092 * @return The test error if the way is erroneous, {@code null} otherwise 093 */ 094 public final TestError getTestError(Way w, UnclosedWays test) { 095 String value = w.get(key); 096 if (isValueErroneous(value)) { 097 return TestError.builder(test, Severity.WARNING, code) 098 .message(tr("Unclosed way"), engMessage, engMessage.contains("{0}") ? new Object[]{value} : new Object[]{}) 099 .primitives(w) 100 .highlight(Arrays.asList(w.firstNode(), w.lastNode())) 101 .build(); 102 } 103 return null; 104 } 105 106 protected boolean isValueErroneous(String value) { 107 return value != null && ignore != specialValues.contains(value); 108 } 109 } 110 111 /** 112 * A check performed by UnclosedWays test where the key is treated as boolean. 113 * @since 6390 114 */ 115 private static final class UnclosedWaysBooleanCheck extends UnclosedWaysCheck { 116 117 /** 118 * Constructs a new {@code UnclosedWaysBooleanCheck}. 119 * @param code The unique numeric code for this check 120 * @param key The OSM key checked 121 * @param engMessage The English message 122 */ 123 UnclosedWaysBooleanCheck(int code, String key, String engMessage) { 124 super(code, key, engMessage); 125 } 126 127 @Override 128 protected boolean isValueErroneous(String value) { 129 Boolean btest = OsmUtils.getOsmBoolean(value); 130 // Not a strict boolean comparison to handle building=house like a building=yes 131 return (btest != null && btest) || (btest == null && value != null); 132 } 133 } 134 135 private static final UnclosedWaysCheck[] checks = { 136 // CHECKSTYLE.OFF: SingleSpaceSeparator 137 // list contains natural tag allowed on unclosed ways as well as those only allowed on nodes to avoid 138 // duplicate warnings 139 new UnclosedWaysCheck(1101, "natural", marktr("natural type {0}"), 140 new HashSet<>(Arrays.asList("arete", "cave", "cliff", "coastline", "gorge", "gully", "peak", 141 "ridge", "saddle", "tree", "tree_row", "valley", "volcano"))), 142 143 new UnclosedWaysCheck(1102, "landuse", marktr("landuse type {0}")), 144 new UnclosedWaysCheck(1103, "amenities", marktr("amenities type {0}")), 145 new UnclosedWaysCheck(1104, "sport", marktr("sport type {0}"), 146 new HashSet<>(Arrays.asList("water_slide", "climbing", "skiing", "toboggan", "bobsleigh"))), 147 new UnclosedWaysCheck(1105, "tourism", marktr("tourism type {0}"), 148 new HashSet<>(Arrays.asList("attraction", "artwork"))), 149 new UnclosedWaysCheck(1106, "shop", marktr("shop type {0}")), 150 new UnclosedWaysCheck(1107, "leisure", marktr("leisure type {0}"), 151 new HashSet<>(Arrays.asList("track", "slipway"))), 152 new UnclosedWaysCheck(1108, "waterway", marktr("waterway type {0}"), 153 new HashSet<>(Arrays.asList("riverbank")), false), 154 new UnclosedWaysCheck(1109, "boundary", marktr("boundary type {0}")), 155 new UnclosedWaysCheck(1110, "area:highway", marktr("area:highway type {0}")), 156 new UnclosedWaysBooleanCheck(1120, "building", marktr("building")), 157 new UnclosedWaysBooleanCheck(1130, "area", marktr("area")), 158 // CHECKSTYLE.ON: SingleSpaceSeparator 159 }; 160 161 /** 162 * Returns the set of checked OSM keys. 163 * @return The set of checked OSM keys. 164 * @since 6390 165 */ 166 public Set<String> getCheckedKeys() { 167 Set<String> keys = new HashSet<>(); 168 for (UnclosedWaysCheck c : checks) { 169 keys.add(c.key); 170 } 171 return keys; 172 } 173 174 @Override 175 public void visit(Way w) { 176 177 if (!w.isUsable() || w.isArea()) 178 return; 179 180 for (OsmPrimitive parent: w.getReferrers()) { 181 if (parent instanceof Relation && ((Relation) parent).isMultipolygon()) 182 return; 183 } 184 185 for (UnclosedWaysCheck c : checks) { 186 TestError error = c.getTestError(w, this); 187 if (error != null) { 188 errors.add(error); 189 return; 190 } 191 } 192 } 193}