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