001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.data.validation.tests;
003
004import org.openstreetmap.josm.Main;
005import org.openstreetmap.josm.data.osm.OsmPrimitive;
006import org.openstreetmap.josm.data.validation.Severity;
007import org.openstreetmap.josm.data.validation.Test;
008import org.openstreetmap.josm.data.validation.TestError;
009import org.openstreetmap.josm.tools.Predicates;
010import org.openstreetmap.josm.tools.Utils;
011
012import java.util.Collection;
013import java.util.HashSet;
014import java.util.Set;
015import java.util.regex.Pattern;
016
017import static org.openstreetmap.josm.tools.I18n.tr;
018
019/**
020 * Test that validates {@code lane:} tags.
021 * @since 6592
022 */
023public class Lanes extends Test.TagTest {
024
025    /**
026     * Constructs a new {@code Lanes} test.
027     */
028    public Lanes() {
029        super(tr("Lane tags"), tr("Test that validates ''lane:'' tags."));
030    }
031
032    static int getLanesCount(String value) {
033        return value.isEmpty() ? 0 : value.replaceAll("[^|]", "").length() + 1;
034    }
035
036    protected void checkNumberOfLanesByKey(final OsmPrimitive p, String lanesKey, String message) {
037        final Collection<String> keysForPattern = Utils.filter(p.keySet(),
038                Predicates.stringContainsPattern(Pattern.compile(":" + lanesKey + "$")));
039        if (keysForPattern.size() < 1) {
040            // nothing to check
041            return;
042        }
043        final Set<Integer> lanesCount = new HashSet<>(Utils.transform(keysForPattern, new Utils.Function<String, Integer>() {
044            @Override
045            public Integer apply(String key) {
046                return getLanesCount(p.get(key));
047            }
048        }));
049        if (lanesCount.size() > 1) {
050            // if not all numbers are the same
051            errors.add(new TestError(this, Severity.WARNING, message, 3100, p));
052        } else if (lanesCount.size() == 1 && p.hasKey(lanesKey)) {
053            // ensure that lanes <= *:lanes
054            try {
055                if (Integer.parseInt(p.get(lanesKey)) > lanesCount.iterator().next()) {
056                    errors.add(new TestError(this, Severity.WARNING, tr("Number of {0} greater than {1}", lanesKey, "*:" + lanesKey), 3100, p));
057                }
058            } catch (NumberFormatException ignore) {
059                Main.debug(ignore.getMessage());
060            }
061        }
062    }
063
064    protected void checkNumberOfLanes(final OsmPrimitive p) {
065        final String lanes = p.get("lanes");
066        if (lanes == null) return;
067        final String forward = Utils.firstNonNull(p.get("lanes:forward"), "0");
068        final String backward = Utils.firstNonNull(p.get("lanes:backward"), "0");
069        try {
070        if (Integer.parseInt(lanes) < Integer.parseInt(forward) + Integer.parseInt(backward)) {
071            errors.add(new TestError(this, Severity.WARNING,
072                    tr("Number of {0} greater than {1}", tr("{0}+{1}", "lanes:forward", "lanes:backward"), "lanes"), 3101, p));
073        }
074        } catch (NumberFormatException ignore) {
075            Main.debug(ignore.getMessage());
076        }
077    }
078
079    @Override
080    public void check(OsmPrimitive p) {
081        checkNumberOfLanesByKey(p, "lanes", tr("Number of lane dependent values inconsistent"));
082        checkNumberOfLanesByKey(p, "lanes:forward", tr("Number of lane dependent values inconsistent in forward direction"));
083        checkNumberOfLanesByKey(p, "lanes:backward", tr("Number of lane dependent values inconsistent in backward direction"));
084        checkNumberOfLanes(p);
085    }
086}