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}