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.data.osm.OsmPrimitive;
011import org.openstreetmap.josm.data.osm.Way;
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.Logging;
016import org.openstreetmap.josm.tools.Utils;
017
018/**
019 * Test that validates {@code lane:} tags.
020 * @since 6592
021 */
022public class Lanes extends Test.TagTest {
023
024    private static final String[] BLACKLIST = {
025        "source:lanes",
026        "note:lanes",
027        "proposed:lanes",
028        "source:proposed:lanes",
029        "piste:lanes",
030    };
031
032    /**
033     * Constructs a new {@code Lanes} test.
034     */
035    public Lanes() {
036        super(tr("Lane tags"), tr("Test that validates ''lane:'' tags."));
037    }
038
039    static int getLanesCount(String value) {
040        return value.isEmpty() ? 0 : value.replaceAll("[^|]", "").length() + 1;
041    }
042
043    protected void checkNumberOfLanesByKey(final OsmPrimitive p, String lanesKey, String message) {
044        final Set<Integer> lanesCount =
045                p.keys()
046                .filter(x -> x.endsWith(":" + lanesKey))
047                .filter(x -> !Arrays.asList(BLACKLIST).contains(x))
048                .map(key -> getLanesCount(p.get(key)))
049                .collect(Collectors.toSet());
050
051        if (lanesCount.size() > 1) {
052            // if not all numbers are the same
053            errors.add(TestError.builder(this, Severity.WARNING, 3100)
054                    .message(message)
055                    .primitives(p)
056                    .build());
057        } else if (lanesCount.size() == 1 && p.hasKey(lanesKey)) {
058            // ensure that lanes <= *:lanes
059            try {
060                if (Integer.parseInt(p.get(lanesKey)) > lanesCount.iterator().next()) {
061                    errors.add(TestError.builder(this, Severity.WARNING, 3100)
062                            .message(tr("Number of {0} greater than {1}", lanesKey, "*:" + lanesKey))
063                            .primitives(p)
064                            .build());
065                }
066            } catch (NumberFormatException ignore) {
067                Logging.debug(ignore.getMessage());
068            }
069        }
070    }
071
072    protected void checkNumberOfLanes(final OsmPrimitive p) {
073        final String lanes = p.get("lanes");
074        if (lanes == null) return;
075        final String forward = Utils.firstNonNull(p.get("lanes:forward"), "0");
076        final String backward = Utils.firstNonNull(p.get("lanes:backward"), "0");
077        try {
078        if (Integer.parseInt(lanes) < Integer.parseInt(forward) + Integer.parseInt(backward)) {
079            errors.add(TestError.builder(this, Severity.WARNING, 3101)
080                    .message(tr("Number of {0} greater than {1}", tr("{0}+{1}", "lanes:forward", "lanes:backward"), "lanes"))
081                    .primitives(p)
082                    .build());
083        }
084        } catch (NumberFormatException ignore) {
085            Logging.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
097    @Override
098    public boolean isPrimitiveUsable(OsmPrimitive p) {
099        return p.isTagged() && p instanceof Way && p.hasTag("highway") && super.isPrimitiveUsable(p);
100    }
101}