Ticket #21930: ParkingLanesConditional.java

File ParkingLanesConditional.java, 10.2 KB (added by riiga_92@…, 4 years ago)

New test

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.Set;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.validation.Severity;
17import org.openstreetmap.josm.data.validation.Test;
18import org.openstreetmap.josm.data.validation.TestError;
19import org.openstreetmap.josm.tools.Logging;
20
21/**
22 * Checks for <a href="http://wiki.openstreetmap.org/wiki/Key:parking:lane">parking lanes</a>
23 * @since 18400
24 */
25public class ParkingLanesConditional extends Test.TagTest {
26
27 private final OpeningHourTest openingHourTest = new OpeningHourTest();
28// private static final Set<String> CONDITIONAL_PARKING_CONDITION_TYPES = new HashSet<>(Arrays.asList("parking:condition:both", "parking:condition:left",
29// "parking:condition:right", "parking:condition:both:maxstay", "parking:condition:left:maxstay", "parking:condition:right:maxstay"));
30 private static final Set<String> CONDITIONAL_PARKING_VALUES = new HashSet<>(Arrays.asList("no", "free", "ticket", "disc", "disabled", "residents",
31 "no_parking", "no_standing", "no_stopping"));
32 private static final Set<String> TRANSPORT_TYPES = new HashSet<>(Arrays.asList("vehicle", "bicycle", "carriage", "trailer", "caravan", "motor_vehicle",
33 "motorcycle", "moped", "mofa", "motorcar", "motorhome", "psv", "bus", "taxi", "tourist_bus", "goods", "hgv", "agricultural", "atv", "snowmobile",
34 "hgv_articulated", "coach"));
35
36 private static final Pattern CONDITIONAL_PATTERN;
37 static {
38 final String part = Pattern.compile("([^@\\p{Space}][^@]*?)"
39 + "\\s*@\\s*" + "(\\([^)\\p{Space}][^)]+?\\)|[^();\\p{Space}][^();]*?)\\s*").toString();
40 CONDITIONAL_PATTERN = Pattern.compile('(' + part + ")(;\\s*" + part + ")*");
41 }
42 private static final Pattern MAXSTAY_PATTERN = Pattern.compile("^(([1-9][0-9]*(\.[0-9]+)?( (minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years))))$");
43
44 /**
45 * Constructs a new {@code ParkingLanes}.
46 */
47 public ParkingLanesConditional() {
48 super(tr("Parking Lanes"), tr("Tests for the correct usage of ''*:conditional'' tags in parking lanes."));
49 }
50
51 @Override
52 public void initialize() throws Exception {
53 super.initialize();
54 openingHourTest.initialize();
55 }
56
57 /**
58 * Check if the value is a valid restriction value
59 * @param part The value
60 * @return <code>true</code> for allowed restriction values
61 */
62 public static boolean isConditionalParkingValue(String part) {
63 return CONDITIONAL_PARKING_VALUES.contains(part);
64 }
65
66 /**
67 * Checks if the key denotes a
68 * <a href="http://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">transport access mode restriction</a>
69 * @param part The key (or the restriction part of it, e.g. for lanes)
70 * @return <code>true</code> if it is a restriction
71 */
72 public static boolean isTransportationMode(String part) {
73 return TRANSPORT_TYPES.contains(part);
74 }
75
76 /**
77 * Check if a key part is a valid side
78 * @param part The part of the key
79 * @return <code>true</code> if it is a side
80 */
81 public static boolean isSide(String part) {
82 return "left".equals(part) || "right".equals(part) || "both".equals(part);
83 }
84
85 /**
86 * Check if a key part is the maxstay value
87 * @param part The part of the key
88 * @return <code>true</code> if is the maxstay string
89 */
90 public static boolean isMaxstay(String part) {
91 return "maxstay".equals(part);
92 }
93
94 /**
95 * Checks if a given key is a valid access key
96 * @param key The conditional key
97 * @return <code>true</code> if the key is valid
98 */
99 public boolean isKeyValid(String key) {
100 // parking:condition:<side>[:<transportation mode>]:conditional or
101 // parking:condition:<side>[:<transportation mode>]:maxstay:conditional
102 if (!key.startsWith("parking:condition:") && !key.endsWith(":conditional")) {
103 return false;
104 }
105 final String[] parts = key.replace("parking:condition:", "").replace(":conditional", "").split(":", -1);
106 return isKeyValid3Parts(parts) || isKeyValid1Part(parts) || isKeyValid2Parts(parts);
107 }
108
109 private static boolean isKeyValid3Parts(String... parts) {
110 return parts.length == 3 && isSide(parts[0]) && isTransportationMode(parts[1]) && isMaxstay(parts[2]);
111 }
112
113 private static boolean isKeyValid2Parts(String... parts) {
114 return parts.length == 2 && ((isSide(parts[0]) && isTransportationMode(parts[1])) || (isSide(parts[0]) && isMaxstay(parts[1])));
115 }
116
117 private static boolean isKeyValid1Part(String... parts) {
118 return parts.length == 1 && (isSide(parts[0]));
119 }
120
121 /**
122 * Check if a value is valid
123 * @param key The key the value is for
124 * @param value The value
125 * @return <code>true</code> if it is valid
126 */
127 public boolean isValueValid(String key, String value) {
128 return validateValue(key, value) == null;
129 }
130
131 static class ConditionalParsingException extends RuntimeException {
132 ConditionalParsingException(String message) {
133 super(message);
134 }
135 }
136
137 /**
138 * A conditional value is a value for the access restriction tag that depends on conditions (time, ...)
139 */
140 public static class ConditionalValue {
141 /**
142 * The value the tag should have if the condition matches
143 */
144 public final String conditionalParkingValue;
145 /**
146 * The conditions for {@link #conditionalParkingValue}
147 */
148 public final Collection<String> conditions;
149
150 /**
151 * Create a new {@link ConditionalValue}
152 * @param conditionalParkingValue The value the tag should have if the condition matches
153 * @param conditions The conditions for that value
154 */
155 public ConditionalValue(String conditionalParkingValue, Collection<String> conditions) {
156 this.conditionalParkingValue = conditionalParkingValue;
157 this.conditions = conditions;
158 }
159
160 /**
161 * Parses the condition values as string.
162 * @param value value, must match {@code <restriction-value> @ <condition>[;<restriction-value> @ <condition>]} pattern
163 * @return list of {@code ConditionalValue}s
164 * @throws ConditionalParsingException if {@code value} does not match expected pattern
165 */
166 public static List<ConditionalValue> parse(String value) {
167 // <restriction-value> @ <condition>[;<restriction-value> @ <condition>]
168 final List<ConditionalValue> r = new ArrayList<>();
169 final Matcher m = CONDITIONAL_PATTERN.matcher(value);
170 // FIXME: Add MAXSTAY_PATTERN somehow
171 if (!m.matches()) {
172 throw new ConditionalParsingException(tr("Does not match pattern ''restriction value @ condition''"));
173 } else {
174 int i = 2;
175 while (i + 1 <= m.groupCount() && m.group(i + 1) != null) {
176 final String restrictionValue = m.group(i);
177 final String[] conditions = m.group(i + 1).replace("(", "").replace(")", "").split("\\s+(AND|and)\\s+", -1);
178 r.add(new ConditionalValue(restrictionValue, Arrays.asList(conditions)));
179 i += 3;
180 }
181 }
182 return r;
183 }
184 }
185
186 /**
187 * Validate a key/value pair
188 * @param key The key
189 * @param value The value
190 * @return The error message for that value or <code>null</code> to indicate valid
191 */
192 public String validateValue(String key, String value) {
193 try {
194 for (final ConditionalValue conditional : ConditionalValue.parse(value)) {
195 // validate restriction value
196 if (!isConditionalParkingValue(conditional.conditionalParkingValue)) {
197 return tr("{0} is not a valid parking condition value", conditional.conditionalParkingValue);
198 }
199 // validate opening hour if the value contains an hour (heuristic)
200 for (final String condition : conditional.conditions) {
201 if (condition.matches(".*[0-9]:[0-9]{2}.*")) {
202 final List<TestError> errors = openingHourTest.checkOpeningHourSyntax("", condition);
203 if (!errors.isEmpty()) {
204 return errors.get(0).getDescription();
205 }
206 }
207 }
208 }
209 } catch (ConditionalParsingException ex) {
210 Logging.debug(ex);
211 return ex.getMessage();
212 }
213 return null;
214 }
215
216 /**
217 * Validate a primitive
218 * @param p The primitive
219 * @return The errors for that primitive or an empty list if there are no errors.
220 */
221 public List<TestError> validatePrimitive(OsmPrimitive p) {
222 final List<TestError> errors = new ArrayList<>();
223 final Pattern pattern = Pattern.compile(":conditional(:.*)?$");
224 p.visitKeys((primitive, key, value) -> {
225 // Only validate parking condition keys
226 if (!key.startsWith("parking:condition") || !pattern.matcher(key).find()) {
227 return;
228 }
229 if (!isKeyValid(key)) {
230 errors.add(TestError.builder(this, Severity.WARNING, 4101)
231 .message(tr("Wrong syntax in {0} key", key))
232 .primitives(p)
233 .build());
234 return;
235 }
236 final String error = validateValue(key, value);
237 if (error != null) {
238 errors.add(TestError.builder(this, Severity.WARNING, 4102)
239 .message(tr("Error in {0} value: {1}", key, error))
240 .primitives(p)
241 .build());
242 }
243 });
244 return errors;
245 }
246
247 @Override
248 public void check(OsmPrimitive p) {
249 if (p.isTagged()) {
250 errors.addAll(validatePrimitive(p));
251 }
252 }
253}