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

Last change on this file since 12390 was 12390, checked in by michael2402, 21 months ago

See #14794: Document data.validation package and subpackages.

  • Property svn:eol-style set to native
File size: 10.2 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.Set;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14
15import org.openstreetmap.josm.Main;
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.LanguageInfo;
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"));
33    private static final Set<String> RESTRICTION_VALUES = new HashSet<>(Arrays.asList("yes", "official", "designated", "destination",
34            "delivery", "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            /*,"hov","emergency","hazmat","disabled"*/));
39
40    /**
41     * Constructs a new {@code ConditionalKeys}.
42     */
43    public ConditionalKeys() {
44        super(tr("Conditional Keys"), tr("Tests for the correct usage of ''*:conditional'' tags."));
45    }
46
47    @Override
48    public void initialize() throws Exception {
49        super.initialize();
50        openingHourTest.initialize();
51    }
52
53    /**
54     * Check if the key is a key for an access restriction
55     * @param part The key (or the restriction part of it, e.g. for lanes)
56     * @return <code>true</code> if it is a restriction
57     */
58    public static boolean isRestrictionType(String part) {
59        return RESTRICTION_TYPES.contains(part);
60    }
61
62    /**
63     * Check if the value is a valid restriction value
64     * @param part The value
65     * @return <code>true</code> for allowed restriction values
66     */
67    public static boolean isRestrictionValue(String part) {
68        return RESTRICTION_VALUES.contains(part);
69    }
70
71    /**
72     * Checks if the key denotes a <a href="http://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">transport access mode restriction</a>
73     * @param part The key (or the restriction part of it, e.g. for lanes)
74     * @return <code>true</code> if it is a restriction
75     */
76    public static boolean isTransportationMode(String part) {
77        return TRANSPORT_MODES.contains(part);
78    }
79
80    /**
81     * Check if a key part is a valid direction
82     * @param part The part of the key
83     * @return <code>true</code> if it is a direction
84     */
85    public static boolean isDirection(String part) {
86        return "forward".equals(part) || "backward".equals(part);
87    }
88
89    /**
90     * Checks if a given key is a valid access key
91     * @param key The conditional key
92     * @return <code>true</code> if the key is valid
93     */
94    public boolean isKeyValid(String key) {
95        // <restriction-type>[:<transportation mode>][:<direction>]:conditional
96        // -- or --            <transportation mode> [:<direction>]:conditional
97        if (!key.endsWith(":conditional")) {
98            return false;
99        }
100        final String[] parts = key.replaceAll(":conditional", "").split(":");
101        return isKeyValid3Parts(parts) || isKeyValid1Part(parts) || isKeyValid2Parts(parts);
102    }
103
104    private static boolean isKeyValid3Parts(String... parts) {
105        return parts.length == 3 && isRestrictionType(parts[0]) && isTransportationMode(parts[1]) && isDirection(parts[2]);
106    }
107
108    private static boolean isKeyValid2Parts(String... parts) {
109        return parts.length == 2 && ((isRestrictionType(parts[0]) && (isTransportationMode(parts[1]) || isDirection(parts[1])))
110                                  || (isTransportationMode(parts[0]) && isDirection(parts[1])));
111    }
112
113    private static boolean isKeyValid1Part(String... parts) {
114        return parts.length == 1 && (isRestrictionType(parts[0]) || isTransportationMode(parts[0]));
115    }
116
117    /**
118     * Check if a value is valid
119     * @param key The key the value is for
120     * @param value The value
121     * @return <code>true</code> if it is valid
122     */
123    public boolean isValueValid(String key, String value) {
124        return validateValue(key, value) == null;
125    }
126
127    static class ConditionalParsingException extends RuntimeException {
128        ConditionalParsingException(String message) {
129            super(message);
130        }
131    }
132
133    /**
134     * A conditional value is a value for the access restriction tag that depends on conditions (time, ...)
135     */
136    public static class ConditionalValue {
137        /**
138         * The value the tag should have if the condition matches
139         */
140        public final String restrictionValue;
141        /**
142         * The conditions for {@link #restrictionValue}
143         */
144        public final Collection<String> conditions;
145
146        /**
147         * Create a new {@link ConditionalValue}
148         * @param restrictionValue The value the tag should have if the condition matches
149         * @param conditions The conditions for that value
150         */
151        public ConditionalValue(String restrictionValue, Collection<String> conditions) {
152            this.restrictionValue = restrictionValue;
153            this.conditions = conditions;
154        }
155
156        /**
157         * Parses the condition values as string.
158         * @param value value, must match {@code <restriction-value> @ <condition>[;<restriction-value> @ <condition>]} pattern
159         * @return list of {@code ConditionalValue}s
160         * @throws ConditionalParsingException if {@code value} does not match expected pattern
161         */
162        public static List<ConditionalValue> parse(String value) {
163            // <restriction-value> @ <condition>[;<restriction-value> @ <condition>]
164            final List<ConditionalValue> r = new ArrayList<>();
165            final String part = Pattern.compile("([^@\\p{Space}][^@]*?)"
166                    + "\\s*@\\s*" + "(\\([^)\\p{Space}][^)]+?\\)|[^();\\p{Space}][^();]*?)\\s*").toString();
167            final Matcher m = Pattern.compile('(' + part + ")(;\\s*" + part + ")*").matcher(value);
168            if (!m.matches()) {
169                throw new ConditionalParsingException(tr("Does not match pattern ''restriction value @ condition''"));
170            } else {
171                int i = 2;
172                while (i + 1 <= m.groupCount() && m.group(i + 1) != null) {
173                    final String restrictionValue = m.group(i);
174                    final String[] conditions = m.group(i + 1).replace("(", "").replace(")", "").split("\\s+(AND|and)\\s+");
175                    r.add(new ConditionalValue(restrictionValue, Arrays.asList(conditions)));
176                    i += 3;
177                }
178            }
179            return r;
180        }
181    }
182
183    /**
184     * Validate a key/value pair
185     * @param key The key
186     * @param value The value
187     * @return The error message for that value or <code>null</code> to indicate valid
188     */
189    public String validateValue(String key, String value) {
190        try {
191            for (final ConditionalValue conditional : ConditionalValue.parse(value)) {
192                // validate restriction value
193                if (isTransportationMode(key.split(":")[0]) && !isRestrictionValue(conditional.restrictionValue)) {
194                    return tr("{0} is not a valid restriction value", conditional.restrictionValue);
195                }
196                // validate opening hour if the value contains an hour (heuristic)
197                for (final String condition : conditional.conditions) {
198                    if (condition.matches(".*[0-9]:[0-9]{2}.*")) {
199                        final List<OpeningHourTest.OpeningHoursTestError> errors = openingHourTest.checkOpeningHourSyntax(
200                                "", condition, OpeningHourTest.CheckMode.TIME_RANGE, true, LanguageInfo.getJOSMLocaleCode());
201                        if (!errors.isEmpty()) {
202                            return errors.get(0).getMessage();
203                        }
204                    }
205                }
206            }
207        } catch (ConditionalParsingException ex) {
208            Main.debug(ex);
209            return ex.getMessage();
210        }
211        return null;
212    }
213
214    /**
215     * Validate a primitive
216     * @param p The primitive
217     * @return The errors for that primitive or an empty list if there are no errors.
218     */
219    public List<TestError> validatePrimitive(OsmPrimitive p) {
220        final List<TestError> errors = new ArrayList<>();
221        for (final String key : SubclassFilteredCollection.filter(p.keySet(),
222                Pattern.compile(":conditional(:.*)?$").asPredicate())) {
223            if (!isKeyValid(key)) {
224                errors.add(TestError.builder(this, Severity.WARNING, 3201)
225                        .message(tr("Wrong syntax in {0} key", key))
226                        .primitives(p)
227                        .build());
228                continue;
229            }
230            final String value = p.get(key);
231            final String error = validateValue(key, value);
232            if (error != null) {
233                errors.add(TestError.builder(this, Severity.WARNING, 3202)
234                        .message(tr("Error in {0} value: {1}", key, error))
235                        .primitives(p)
236                        .build());
237            }
238        }
239        return errors;
240    }
241
242    @Override
243    public void check(OsmPrimitive p) {
244        errors.addAll(validatePrimitive(p));
245    }
246}
Note: See TracBrowser for help on using the repository browser.