source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java@ 15978

Last change on this file since 15978 was 15978, checked in by simon04, 4 years ago

fix #18140 - Switch to OpeningHoursParser

  • Property svn:eol-style set to native
File size: 10.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.HashSet;
10import java.util.List;
11import java.util.Locale;
12import java.util.Set;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import org.openstreetmap.josm.data.osm.OsmPrimitive;
17import org.openstreetmap.josm.data.validation.Severity;
18import org.openstreetmap.josm.data.validation.Test;
19import org.openstreetmap.josm.data.validation.TestError;
20import org.openstreetmap.josm.tools.Logging;
21import org.openstreetmap.josm.tools.SubclassFilteredCollection;
22
23/**
24 * Checks for <a href="http://wiki.openstreetmap.org/wiki/Conditional_restrictions">conditional restrictions</a>
25 * @since 6605
26 */
27public class ConditionalKeys extends Test.TagTest {
28
29 private final OpeningHourTest openingHourTest = new OpeningHourTest();
30 private static final Set<String> RESTRICTION_TYPES = new HashSet<>(Arrays.asList("oneway", "toll", "noexit", "maxspeed", "minspeed",
31 "maxstay", "maxweight", "maxaxleload", "maxheight", "maxwidth", "maxlength", "overtaking", "maxgcweight", "maxgcweightrating",
32 "fee", "restriction", "interval", "duration"));
33 private static final Set<String> RESTRICTION_VALUES = new HashSet<>(Arrays.asList("yes", "official", "designated", "destination",
34 "delivery", "customers", "permissive", "private", "agricultural", "forestry", "no"));
35 private static final Set<String> TRANSPORT_MODES = new HashSet<>(Arrays.asList("access", "foot", "ski", "inline_skates", "ice_skates",
36 "horse", "vehicle", "bicycle", "carriage", "trailer", "caravan", "motor_vehicle", "motorcycle", "moped", "mofa",
37 "motorcar", "motorhome", "psv", "bus", "taxi", "tourist_bus", "goods", "hgv", "agricultural", "atv", "snowmobile",
38 "hgv_articulated", "ski:nordic", "ski:alpine", "ski:telemark", "coach", "golf_cart"
39 /*,"minibus","share_taxi","hov","car_sharing","emergency","hazmat","disabled"*/));
40
41 private static final Pattern CONDITIONAL_PATTERN;
42 static {
43 final String part = Pattern.compile("([^@\\p{Space}][^@]*?)"
44 + "\\s*@\\s*" + "(\\([^)\\p{Space}][^)]+?\\)|[^();\\p{Space}][^();]*?)\\s*").toString();
45 CONDITIONAL_PATTERN = Pattern.compile('(' + part + ")(;\\s*" + part + ")*");
46 }
47
48 /**
49 * Constructs a new {@code ConditionalKeys}.
50 */
51 public ConditionalKeys() {
52 super(tr("Conditional Keys"), tr("Tests for the correct usage of ''*:conditional'' tags."));
53 }
54
55 @Override
56 public void initialize() throws Exception {
57 super.initialize();
58 openingHourTest.initialize();
59 }
60
61 /**
62 * Check if the key is a key for an access restriction
63 * @param part The key (or the restriction part of it, e.g. for lanes)
64 * @return <code>true</code> if it is a restriction
65 */
66 public static boolean isRestrictionType(String part) {
67 return RESTRICTION_TYPES.contains(part);
68 }
69
70 /**
71 * Check if the value is a valid restriction value
72 * @param part The value
73 * @return <code>true</code> for allowed restriction values
74 */
75 public static boolean isRestrictionValue(String part) {
76 return RESTRICTION_VALUES.contains(part);
77 }
78
79 /**
80 * Checks if the key denotes a
81 * <a href="http://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">transport access mode restriction</a>
82 * @param part The key (or the restriction part of it, e.g. for lanes)
83 * @return <code>true</code> if it is a restriction
84 */
85 public static boolean isTransportationMode(String part) {
86 return TRANSPORT_MODES.contains(part);
87 }
88
89 /**
90 * Check if a key part is a valid direction
91 * @param part The part of the key
92 * @return <code>true</code> if it is a direction
93 */
94 public static boolean isDirection(String part) {
95 return "forward".equals(part) || "backward".equals(part);
96 }
97
98 /**
99 * Checks if a given key is a valid access key
100 * @param key The conditional key
101 * @return <code>true</code> if the key is valid
102 */
103 public boolean isKeyValid(String key) {
104 // <restriction-type>[:<transportation mode>][:<direction>]:conditional
105 // -- or -- <transportation mode> [:<direction>]:conditional
106 if (!key.endsWith(":conditional")) {
107 return false;
108 }
109 final String[] parts = key.replace(":conditional", "").split(":");
110 return isKeyValid3Parts(parts) || isKeyValid1Part(parts) || isKeyValid2Parts(parts);
111 }
112
113 private static boolean isKeyValid3Parts(String... parts) {
114 return parts.length == 3 && isRestrictionType(parts[0]) && isTransportationMode(parts[1]) && isDirection(parts[2]);
115 }
116
117 private static boolean isKeyValid2Parts(String... parts) {
118 return parts.length == 2 && ((isRestrictionType(parts[0]) && (isTransportationMode(parts[1]) || isDirection(parts[1])))
119 || (isTransportationMode(parts[0]) && isDirection(parts[1])));
120 }
121
122 private static boolean isKeyValid1Part(String... parts) {
123 return parts.length == 1 && (isRestrictionType(parts[0]) || isTransportationMode(parts[0]));
124 }
125
126 /**
127 * Check if a value is valid
128 * @param key The key the value is for
129 * @param value The value
130 * @return <code>true</code> if it is valid
131 */
132 public boolean isValueValid(String key, String value) {
133 return validateValue(key, value) == null;
134 }
135
136 static class ConditionalParsingException extends RuntimeException {
137 ConditionalParsingException(String message) {
138 super(message);
139 }
140 }
141
142 /**
143 * A conditional value is a value for the access restriction tag that depends on conditions (time, ...)
144 */
145 public static class ConditionalValue {
146 /**
147 * The value the tag should have if the condition matches
148 */
149 public final String restrictionValue;
150 /**
151 * The conditions for {@link #restrictionValue}
152 */
153 public final Collection<String> conditions;
154
155 /**
156 * Create a new {@link ConditionalValue}
157 * @param restrictionValue The value the tag should have if the condition matches
158 * @param conditions The conditions for that value
159 */
160 public ConditionalValue(String restrictionValue, Collection<String> conditions) {
161 this.restrictionValue = restrictionValue;
162 this.conditions = conditions;
163 }
164
165 /**
166 * Parses the condition values as string.
167 * @param value value, must match {@code <restriction-value> @ <condition>[;<restriction-value> @ <condition>]} pattern
168 * @return list of {@code ConditionalValue}s
169 * @throws ConditionalParsingException if {@code value} does not match expected pattern
170 */
171 public static List<ConditionalValue> parse(String value) {
172 // <restriction-value> @ <condition>[;<restriction-value> @ <condition>]
173 final List<ConditionalValue> r = new ArrayList<>();
174 final Matcher m = CONDITIONAL_PATTERN.matcher(value);
175 if (!m.matches()) {
176 throw new ConditionalParsingException(tr("Does not match pattern ''restriction value @ condition''"));
177 } else {
178 int i = 2;
179 while (i + 1 <= m.groupCount() && m.group(i + 1) != null) {
180 final String restrictionValue = m.group(i);
181 final String[] conditions = m.group(i + 1).replace("(", "").replace(")", "").split("\\s+(AND|and)\\s+");
182 r.add(new ConditionalValue(restrictionValue, Arrays.asList(conditions)));
183 i += 3;
184 }
185 }
186 return r;
187 }
188 }
189
190 /**
191 * Validate a key/value pair
192 * @param key The key
193 * @param value The value
194 * @return The error message for that value or <code>null</code> to indicate valid
195 */
196 public String validateValue(String key, String value) {
197 try {
198 for (final ConditionalValue conditional : ConditionalValue.parse(value)) {
199 // validate restriction value
200 if (isTransportationMode(key.split(":")[0]) && !isRestrictionValue(conditional.restrictionValue)) {
201 return tr("{0} is not a valid restriction value", conditional.restrictionValue);
202 }
203 // validate opening hour if the value contains an hour (heuristic)
204 for (final String condition : conditional.conditions) {
205 if (condition.matches(".*[0-9]:[0-9]{2}.*")) {
206 final List<OpeningHourTest.OpeningHoursTestError> errors = openingHourTest.checkOpeningHourSyntax(
207 "", condition, true, Locale.getDefault());
208 if (!errors.isEmpty()) {
209 return errors.get(0).getMessage();
210 }
211 }
212 }
213 }
214 } catch (ConditionalParsingException ex) {
215 Logging.debug(ex);
216 return ex.getMessage();
217 }
218 return null;
219 }
220
221 /**
222 * Validate a primitive
223 * @param p The primitive
224 * @return The errors for that primitive or an empty list if there are no errors.
225 */
226 public List<TestError> validatePrimitive(OsmPrimitive p) {
227 final List<TestError> errors = new ArrayList<>();
228 for (final String key : SubclassFilteredCollection.filter(p.keySet(),
229 Pattern.compile(":conditional(:.*)?$").asPredicate())) {
230 if (!isKeyValid(key)) {
231 errors.add(TestError.builder(this, Severity.WARNING, 3201)
232 .message(tr("Wrong syntax in {0} key", key))
233 .primitives(p)
234 .build());
235 continue;
236 }
237 final String value = p.get(key);
238 final String error = validateValue(key, value);
239 if (error != null) {
240 errors.add(TestError.builder(this, Severity.WARNING, 3202)
241 .message(tr("Error in {0} value: {1}", key, error))
242 .primitives(p)
243 .build());
244 }
245 }
246 return errors;
247 }
248
249 @Override
250 public void check(OsmPrimitive p) {
251 errors.addAll(validatePrimitive(p));
252 }
253}
Note: See TracBrowser for help on using the repository browser.