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