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