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.text.MessageFormat; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.HashSet; 011import java.util.Set; 012 013import org.openstreetmap.josm.data.osm.OsmPrimitive; 014import org.openstreetmap.josm.data.osm.OsmUtils; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.Way; 017import org.openstreetmap.josm.data.validation.Severity; 018import org.openstreetmap.josm.data.validation.Test; 019import org.openstreetmap.josm.data.validation.TestError; 020 021/** 022 * Check area type ways for errors 023 * 024 * @author stoecker 025 * @since 3669 026 */ 027public class UnclosedWays extends Test { 028 029 /** 030 * Constructs a new {@code UnclosedWays} test. 031 */ 032 public UnclosedWays() { 033 super(tr("Unclosed Ways"), tr("This tests if ways which should be circular are closed.")); 034 } 035 036 /** 037 * A check performed by UnclosedWays test. 038 * @since 6390 039 */ 040 private class UnclosedWaysCheck { 041 /** The unique numeric code for this check */ 042 public final int code; 043 /** The OSM key checked */ 044 public final String key; 045 /** The English message */ 046 private final String engMessage; 047 /** The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false */ 048 private final Set<String> specialValues; 049 /** The boolean indicating if special values must be ignored or considered only */ 050 private final boolean ignore; 051 052 /** 053 * Constructs a new {@code UnclosedWaysCheck}. 054 * @param code The unique numeric code for this check 055 * @param key The OSM key checked 056 * @param engMessage The English message 057 */ 058 UnclosedWaysCheck(int code, String key, String engMessage) { 059 this(code, key, engMessage, Collections.<String>emptySet()); 060 } 061 062 /** 063 * Constructs a new {@code UnclosedWaysCheck}. 064 * @param code The unique numeric code for this check 065 * @param key The OSM key checked 066 * @param engMessage The English message 067 * @param ignoredValues The ignored values. 068 */ 069 UnclosedWaysCheck(int code, String key, String engMessage, Set<String> ignoredValues) { 070 this(code, key, engMessage, ignoredValues, true); 071 } 072 073 /** 074 * Constructs a new {@code UnclosedWaysCheck}. 075 * @param code The unique numeric code for this check 076 * @param key The OSM key checked 077 * @param engMessage The English message 078 * @param specialValues The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false 079 * @param ignore indicates if special values must be ignored or considered only 080 */ 081 UnclosedWaysCheck(int code, String key, String engMessage, Set<String> specialValues, boolean ignore) { 082 this.code = code; 083 this.key = key; 084 this.engMessage = engMessage; 085 this.specialValues = specialValues; 086 this.ignore = ignore; 087 } 088 089 /** 090 * Returns the test error of the given way, if any. 091 * @param w The way to check 092 * @return The test error if the way is erroneous, {@code null} otherwise 093 */ 094 public final TestError getTestError(Way w) { 095 String value = w.get(key); 096 if (isValueErroneous(value)) { 097 // CHECKSTYLE.OFF: SingleSpaceSeparator 098 String type = engMessage.contains("{0}") ? tr(engMessage, tr(value)) : tr(engMessage); 099 String etype = engMessage.contains("{0}") ? MessageFormat.format(engMessage, value) : engMessage; 100 // CHECKSTYLE.ON: SingleSpaceSeparator 101 return new TestError(UnclosedWays.this, Severity.WARNING, tr("Unclosed way"), 102 type, etype, code, Arrays.asList(w), 103 // The important parts of an unclosed way are the first and 104 // the last node which should be connected, therefore we highlight them 105 Arrays.asList(w.firstNode(), w.lastNode())); 106 } 107 return null; 108 } 109 110 protected boolean isValueErroneous(String value) { 111 return value != null && ignore != specialValues.contains(value); 112 } 113 } 114 115 /** 116 * A check performed by UnclosedWays test where the key is treated as boolean. 117 * @since 6390 118 */ 119 private final class UnclosedWaysBooleanCheck extends UnclosedWaysCheck { 120 121 /** 122 * Constructs a new {@code UnclosedWaysBooleanCheck}. 123 * @param code The unique numeric code for this check 124 * @param key The OSM key checked 125 * @param engMessage The English message 126 */ 127 UnclosedWaysBooleanCheck(int code, String key, String engMessage) { 128 super(code, key, engMessage); 129 } 130 131 @Override 132 protected boolean isValueErroneous(String value) { 133 Boolean btest = OsmUtils.getOsmBoolean(value); 134 // Not a strict boolean comparison to handle building=house like a building=yes 135 return (btest != null && btest) || (btest == null && value != null); 136 } 137 } 138 139 private final UnclosedWaysCheck[] checks = { 140 // CHECKSTYLE.OFF: SingleSpaceSeparator 141 new UnclosedWaysCheck(1101, "natural", marktr("natural type {0}"), 142 new HashSet<>(Arrays.asList("cave", "coastline", "cliff", "tree_row", "ridge", "valley", "arete", "gorge"))), 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"))), 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 UnclosedWaysBooleanCheck(1120, "building", marktr("building")), 156 new UnclosedWaysBooleanCheck(1130, "area", marktr("area")), 157 // CHECKSTYLE.ON: SingleSpaceSeparator 158 }; 159 160 /** 161 * Returns the set of checked OSM keys. 162 * @return The set of checked OSM keys. 163 * @since 6390 164 */ 165 public Set<String> getCheckedKeys() { 166 Set<String> keys = new HashSet<>(); 167 for (UnclosedWaysCheck c : checks) { 168 keys.add(c.key); 169 } 170 return keys; 171 } 172 173 @Override 174 public void visit(Way w) { 175 176 if (!w.isUsable() || w.isArea()) 177 return; 178 179 for (OsmPrimitive parent: w.getReferrers()) { 180 if (parent instanceof Relation && ((Relation) parent).isMultipolygon()) 181 return; 182 } 183 184 for (UnclosedWaysCheck c : checks) { 185 TestError error = c.getTestError(w); 186 if (error != null) { 187 errors.add(error); 188 return; 189 } 190 } 191 } 192}