/* * Copyright (C) 2008 Steve Ratcliffe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * Author: Steve Ratcliffe * Create date: 29-Nov-2008 */ package uk.me.parabola.mkgmap.osmstyle; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import java.util.Set; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.mkgmap.general.LevelInfo; import uk.me.parabola.mkgmap.reader.osm.Element; import uk.me.parabola.mkgmap.reader.osm.FeatureKind; import uk.me.parabola.mkgmap.reader.osm.GType; import uk.me.parabola.mkgmap.reader.osm.Rule; import uk.me.parabola.mkgmap.reader.osm.TypeResult; import uk.me.parabola.mkgmap.reader.osm.Way; import uk.me.parabola.mkgmap.scan.SyntaxException; import func.lib.StringStyleFileLoader; import org.junit.Test; import static func.lib.TestUtils.makeRuleSet; import static org.junit.Assert.*; public class RuleFileReaderTest { /** * Test of a file containing a number of different rules, with varying * formatting and including comments. */ @Test public void testLoad() { RuleSet rs = makeRuleSet("highway=footway & type=rough [0x2 level 2]\n" + "highway=footway | highway = path\n" + " [0x3]\n# comment here\n" + "foo=\nbar & bar=two [0x4]\n" + "highway=* & oneway=true [0x6 level 1]\n" + ""); Element el = new Way(1); el.addTag("highway", "footway"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals("plain footway", "[0x3 level 0]", type.toString()); el.addTag("type", "rough"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("rough footway", "[0x2 level 2]", type.toString()); } /** * Test for non-standard level specification. You can give a range * of levels, rather than defaulting the max end to 0. */ @Test public void testLevel() { RuleSet rs = makeRuleSet( "highway=primary [0x1 level 1-3]" ); Element el = new Way(1); el.addTag("highway", "primary"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals("min level", 1, type.getMinLevel()); assertEquals("max level", 3, type.getMaxLevel()); } /** * Try out arithmetic comparisons and mixtures of 'and' and 'or'. */ @Test public void testComplexExpressions() { String str = "a=b & (c=d | e=f) & x>10 [0x1]\n"; RuleSet rs = makeRuleSet(str); Element el = new Way(1); el.addTag("a", "b"); el.addTag("c", "d"); el.addTag("x", "11"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals("expression ok", 1, type.getType()); // fails with x less than 10 el.addTag("x", "9"); type = getFirstType(rs, el); assertNull("x too low", type); // also fails with x equal to 10 el.addTag("x", "10"); type = getFirstType(rs, el); assertNull("x too low", type); // OK with x > 10 el.addTag("x", "100"); el.addTag("e", "f"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("c and e set", 1, type.getType()); el.addTag("c", ""); el.addTag("e", ""); type = getFirstType(rs, el); assertNull("none of c and e set", type); el.addTag("e", "f"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("e is set to f", 1, type.getType()); } /** * Test based on email on the mailing list at: * http://www.mkgmap.org.uk/pipermail/mkgmap-dev/2009q3/003009.html * See that email for an explanation. */ @Test public void testComparasons() { String str = "highway=null_null & layer<0 [0x01 resolution 10]\n" + "highway=null_null & layer=0 [0x02 resolution 10]\n" + "highway=null_null & layer>0 [0x03 resolution 10]\n" + "highway=null_null & layer='-1' [0x04 resolution 10]\n" + "highway=null_null & layer='0' [0x05 resolution 10]\n" + "highway=null_null & layer='1' [0x06 resolution 10]\n" + "highway=null_null & layer='+1' [0x07 resolution 10]\n" + "highway=null_null [0x08 resolution 10]"; RuleSet rs = makeRuleSet(str); // 9902 Element el = new Way(1); el.addTag("highway", "null_null"); el.addTag("layer", "-1"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals("9902 layer = -1", 0x1, type.getType()); // 9912 el.addTag("layer", "0"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("9912 layer = 0", 0x2, type.getType()); // 9922 el.deleteTag("layer"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("9922 no layer tag", 0x8, type.getType()); // 9932 el.addTag("layer", "1"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("9932 layer is 1", 0x3, type.getType()); // 9952 el.addTag("layer", "+1"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("9952 layer is +1", 0x3, type.getType()); } @Test public void testMultipleActions() { String rstr = "highway=footway {add access = no; add foot = yes} [0x16 road_class=0 road_speed=0 resolution 23]"; RuleSet rs = makeRuleSet(rstr); Element el = new Way(1); el.addTag("highway", "footway"); getFirstType(rs, el); assertEquals("access set", "no", el.getTag("access")); assertEquals("access set", "yes", el.getTag("foot")); } /** * You can now have a wild card at the top level. */ @Test public void testWildcardTop() { RuleSet rs = makeRuleSet("highway=* {set a=fred} [0x1]\n"); assertNotNull("rule found", rs); Element el = new Way(1); el.addTag("highway", "secondary"); GType type = getFirstType(rs, el); assertNotNull("can find match", type); assertEquals("correct type", 1, type.getType()); assertEquals("tag set", "fred", el.getTag("a")); } /** * Deal with cases such as * (a = b | a = c) & d!=* * where there is no key at the top level. This gets converted * to: (a=b & d!=*) | (a=c & d!= *) which can then be used. * * This is applied recursively, so you can have chains of any length. */ @Test public void testLeftSideOr() { RuleSet rs = makeRuleSet("(a = b | a = c | a=d) & e!=* [0x2]" + "a=c & e!=* [0x1]"); assertNotNull("a=b chain", rs); assertNotNull("a=c chain", rs); assertNotNull("a=d chain", rs); // get the a=c chain and look at it more closely Element el = new Way(1); el.addTag("a", "c"); GType type = getFirstType(rs, el); assertNotNull("match e not existing", type); assertEquals("correct type", 2, type.getType()); el = new Way(2); el.addTag("a", "d"); assertNotNull("match e not existing", type); assertEquals("correct type", 2, type.getType()); } /** * You can now have a wild card at the top level, here we have & between * two of them. */ @Test public void testWildcard2() { RuleSet rs = makeRuleSet("highway=* & z=* {set a=square} [0x1]\n"); assertNotNull("rule found", rs); Element el = new Way(1); el.addTag("highway", "secondary"); GType type = getFirstType(rs, el); assertNull("type not found with no z tag", type); // now add z el.addTag("z", "1"); type = getFirstType(rs, el); assertNotNull("found match", type); assertEquals("correct type", 1, type.getType()); assertEquals("tag set", "square", el.getTag("a")); } /** * Tests for the road classification and other parts of the GType. */ @Test public void testGType() { RuleSet rs = makeRuleSet("highway=motorway " + "[0x1 road_class=4 road_speed=7 default_name='motor way']\n"); Element el = new Way(1); el.addTag("highway", "motorway"); GType type = getFirstType(rs, el); assertNotNull(type); // Check that the correct class and speed are returned. assertEquals("class", 4, type.getRoadClass()); assertEquals("class", 7, type.getRoadSpeed()); assertEquals("default name", "motor way", type.getDefaultName()); } /** * Check for the regexp handling. */ @Test public void testRegexp() { RuleSet rs = makeRuleSet("highway=* & name ~ 'blue.*' [0x2]\n"); assertNotNull("rule found", rs); // Set up element with matching name Element el = new Way(1); el.addTag("highway", "secondary"); el.addTag("name", "blue sq"); GType type = getFirstType(rs, el); assertNotNull("matched regexp", type); assertEquals("matched type", 2, type.getType()); // change name to one that should not match el.addTag("name", "yellow"); type = getFirstType(rs, el); assertNull("no match for yello", type); } @Test public void testRegex2() { RuleSet rs = makeRuleSet("a=b & (smoothness ~ '.*(bad|horrible|impassable)' | sac_scale ~ '.*(mountain|alpine)_hiking') [0x1]" + "a = '>=' & b = '>' [0x2]"); assertNotNull(rs); Element el = new Way(1); el.addTag("a", "b"); el.addTag("smoothness", "zzzbad"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals("matched .*bad", 1, type.getType()); el = new Way(1); el.addTag("a", "b"); el.addTag("sac_scale", "zzz alpine_hiking"); type = getFirstType(rs, el); assertNotNull(type); el = new Way(1); el.addTag("a", "b"); el.addTag("sac_scale", "zzz alp_hiking"); type = getFirstType(rs, el); assertNull(type); el = new Way(1); el.addTag("a", ">="); el.addTag("b", ">"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("match string that is the same as an operator", 2, type.getType()); } /** * Some operations could not originally be used by themselves but now they are converted * into expressions that can be handled automatically. The following few tests verify this. */ @Test public void testRegexAtTop() { RuleSet rs = makeRuleSet("QUOTA ~ ' [05]00\\.0+' [0x2]"); Element el = new Way(1); el.addTag("QUOTA", " 500.0"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); } @Test public void testNEAtTop() { RuleSet rs = makeRuleSet("QUOTA != 'fred' [0x2]"); Element el = new Way(1); el.addTag("QUOTA", "tom"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); } @Test public void testNEAtTopWithRE() { RuleSet rs = makeRuleSet("a != 'fred' & a ~ '.*' [0x2]"); Element el = new Way(1); el.addTag("a", "tom"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); } @Test public void testNumberOpAtTop() { RuleSet rs = makeRuleSet("QUOTA > 10 [0x1] QUOTA < 6 [0x2]"); Element el = new Way(1); el.addTag("QUOTA", "2"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); } /** * Failure of the optimiser to promote the correct term to the front. * Example from mailing list. */ @Test public void testOptimizeWithOr() { String s = "highway ~ '(secondary|tertiary|unclassified|residential|minor|living_street|service)' " + "& oneway=* " + "& (cycleway=opposite | cycleway=opposite_lane | cycleway=opposite_track )" + "[0x2 ]"; RuleSet rs = makeRuleSet(s); Element el = new Way(1); el.addTag("highway", "tertiary"); el.addTag("oneway", "1"); el.addTag("cycleway", "opposite_track"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); el.addTag("cycleway", "fred"); type = getFirstType(rs, el); assertNull(type); el.addTag("cycleway", "opposite"); type = getFirstType(rs, el); assertNotNull(type); el = el.copy(); // Copy for LinkedOp which remembers the last matched element el.addTag("cycleway", "opposite_lane"); type = getFirstType(rs, el); assertNotNull(type); el.addTag("highway", "fred"); type = getFirstType(rs, el); assertNull(type); } /** * Test is a simplified version of a rule in the floodblocker style. */ @Test public void testOptimizeWithOr2() { String s = "highway=*" + "& tunnel!=*" + "& (layer!=* | layer=0)" + " [0x02]\n" ; RuleSet rs = makeRuleSet(s); Element el = new Way(1); el.addTag("highway", "primary"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); el.addTag("layer", "0"); type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); el.addTag("layer", "1"); type = getFirstType(rs, el); assertNull(type); } @Test public void testOptimizeWithOr3() throws Exception { String s = "highway=* & bridge!=* & " + " (mtb:scale>0 | mtb:scale='0+' | tracktype ~ 'grade[2-6]' |" + " sac_scale ~ '.*(mountain|alpine)_hiking' |" + " sport=via_ferrata) [0x3]"; RuleSet rs = makeRuleSet(s); Element el = new Way(1); el.addTag("highway", "primary"); el.addTag("mtb:scale", "0+"); GType type = getFirstType(rs, el); assertNotNull(type); } /** * This simply is to make sure that actions that affect their own * conditions do not hang. There are no defined semantics for this. */ @Test public void testSelfReference() { RuleSet rs = makeRuleSet("iii=* { set iii=no }"); //Rule rule = rs.getMap().get("foot=*"); Way el = new Way(1); el.addTag("foot", "yes"); el.addTag("iii", "xyz"); getFirstType(rs, el); } /** * Test the not operator. */ @Test public void testNot() { RuleSet rs = makeRuleSet("tunnel=yes & !(route=mtb | route=bicycle) [0x1]"); //RuleSet rs = makeRuleSet("tunnel=yes & (route!=mtb & route!=bicycle) [0x1]"); Way el = new Way(1); el.addTag("tunnel", "yes"); el.addTag("route", "abc"); getFirstType(rs, el); } @Test public void testGTR() { RuleSet rs = makeRuleSet("z=0 & a >= 10 [0x1]"); Way el = new Way(1); el.addTag("z", "0"); el.addTag("a", "9"); GType type = getFirstType(rs, el); assertNull("a less that 10, no result", type); el.addTag("a", "10"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("Valid type returned", 1, type.getType()); el.addTag("a", "11"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("Valid type returned", 1, type.getType()); } @Test public void testLTE() { RuleSet rs = makeRuleSet("z=0 & a <= 10 [0x1]"); Way el = new Way(1); el.addTag("z", "0"); el.addTag("a", "9"); GType type = getFirstType(rs, el); assertNotNull("a less that 10", type); assertEquals("found type for a <= 10", 1, type.getType()); el.addTag("a", "10"); type = getFirstType(rs, el); assertNotNull(type); assertEquals("Found type for a == 10", 1, type.getType()); el.addTag("a", "11"); type = getFirstType(rs, el); assertNull("a is 11, a <= 10 is false", type); } @Test public void testNE() { RuleSet rs = makeRuleSet("z=0 & a != 10 [0x1]"); Way el = new Way(1); el.addTag("z", "0"); el.addTag("a", "9"); GType type = getFirstType(rs, el); assertNotNull("a is 9 so a!=10 is true", type); el.addTag("a", "10"); type = getFirstType(rs, el); assertNull("a is 10, so a!=10 is false", type); } /** * Test values such as 3.5 in comparisons. * Originally non-integer values were not allowed and were not even recognised. */ @Test public void testDecimalValues() { RuleSet rs = makeRuleSet("z=yes & a < 3.5 [0x1]"); Way el = new Way(1); el.addTag("z", "yes"); // Is less than so el.addTag("a", "2"); GType type = getFirstType(rs, el); assertNotNull("a is less than 3.5", type); el.addTag("a", "4"); type = getFirstType(rs, el); assertNull("a is greater than 3.5", type); } @Test public void testDecimalAndDecimalCompare() { RuleSet rs = makeRuleSet("z=yes & a < 3.5 [0x1]"); Way el = new Way(1); el.addTag("z", "yes"); // Is less than so el.addTag("a", "3.49"); GType type = getFirstType(rs, el); assertNotNull("a is less than 3.5", type); el.addTag("a", "3.55"); type = getFirstType(rs, el); assertNull("a is greater than 3.5", type); } /** * A moderately complex set of conditions and substitutions. */ @Test public void testMtbRules() { RuleSet rs = makeRuleSet( "(mtb:scale=* | mtb:scale:uphill=*) & route=mtb" + "{ name 'mtbrt${mtb:scale|def:.}${mtb:scale:uphill|def:.} ${name}' " + " | 'mtbrt${mtb:scale|def:.}${mtb:scale:uphill|def:.}' }" + " (mtb:scale=* | mtb:scale:uphill=*) & route!=mtb " + "{ name 'mtb${mtb:scale|def:.}${mtb:scale:uphill|def:.} ${name}' " + " | 'mtb${mtb:scale|def:.}${mtb:scale:uphill|def:.}' }" ); Way el = new Way(1); el.addTag("route", "mtb"); el.addTag("mtb:scale", "2"); getFirstType(rs, el); assertEquals("mtbrt2.", el.getName()); el = new Way(1); el.addTag("route", "mtb"); el.addTag("mtb:scale:uphill", "3"); getFirstType(rs, el); assertEquals("mtbrt.3", el.getName()); el = new Way(1); el.addTag("name", "myname"); el.addTag("route", "mtb"); el.addTag("mtb:scale:uphill", "3"); getFirstType(rs, el); assertEquals("mtbrt.3 myname", el.getName()); el = new Way(1); el.addTag("mtb:scale:uphill", "3"); getFirstType(rs, el); assertEquals("mtb.3", el.getName()); } /** * Appending to an existing tag. */ @Test public void testTagAppend() { RuleSet rs = makeRuleSet( "highway=*{set fullname='${ref}';" + "set fullname='${fullname} ${name}';" + "set fullname='${fullname} ${name1}';" + "set fullname='${fullname} ${name2}';" + "name '${fullname}'}" ); Way el = new Way(1); el.addTag("highway", "road"); el.addTag("ref", "A1"); el.addTag("name", "long lane"); el.addTag("name1", "foo"); el.addTag("name2", "bar"); getFirstType(rs, el); assertEquals("appended name", "A1 long lane foo bar", el.getName()); } @Test public void testExists() { RuleSet rs = makeRuleSet("highway=* & maxspeed=40 {set mcssl=40}" + "highway=primary & mcssl=40 [0x2 ]" + "highway=* & mcssl=40 [0x3]"); Way el = new Way(1); el.addTag("ref", "A123"); el.addTag("name", "Long Lane"); el.addTag("highway", "primary"); el.addTag("maxspeed", "40"); GType type = getFirstType(rs, el); assertNotNull("finds the type", type); assertEquals("resulting type", 2, type.getType()); } /** * Test the continue keyword. If a type is marked with this word, then * further matches are performed and this might result in more types * being added. */ @Test public void testContinue() { RuleSet rs = makeRuleSet("highway=primary [0x1 continue]" + "highway=primary [0x2 continue]" + "highway=primary [0x3]" + "highway=primary [0x4]" ); Way el = new Way(1); el.addTag("highway", "primary"); final List<GType> list = new ArrayList<>(); rs.resolveType(el, new TypeResult() { public void add(Element el, GType type) { list.add(type); } }); GType type = list.get(0); assertEquals("first type", 1, type.getType()); assertEquals("continue search", true, type.isContinueSearch()); assertEquals("number of result types", 3, list.size()); assertEquals("type of first", 1, list.get(0).getType()); assertEquals("type of second", 2, list.get(1).getType()); assertEquals("type of third", 3, list.get(2).getType()); } @Test public void testContinueRepeat() { RuleSet rs = makeRuleSet("highway=primary [0x1 continue]" + "highway=primary [0x2 continue]" + "highway=primary [0x3]" + "highway=primary [0x4]" ); Way el = new Way(1); el.addTag("highway", "primary"); for (int i = 0; i < 3; i++) { GType type = getFirstType(rs, el); assertNotNull(type); assertEquals("first type", 1, type.getType()); assertEquals("continue search", true, type.isContinueSearch()); } } /** * The main point of this test is to ensure that all the examples compile. */ @Test public void testComplexRegex() { RuleSet rs = makeRuleSet( //"a~b [0x0]" + "a~b & c=d [0x1]" + "a~b & c~d & e=f [0x2]" + "(a~b | c~d) & e=f [0x3]" + "(a~b | c~d) & e=f & g=h [0x4]" + "((a~b | c~d) & e=f) & g=h [0x5]" + "e=f & g=h & (a~b | c~'d.*') [0x6]" + "(e=f & g=h) & (a~b | c~'d.*') [0x7]" + "a=* & b=* & c=d [0x8]" + "a=* & (b=* | c=d) [0x9]" + "" ); Way el = new Way(1); el.addTag("c", "df"); el.addTag("g", "h"); el.addTag("e", "f"); GType type = getFirstType(rs, el); assertNotNull("matches a rule", type); } @Test public void testTagsUsed() { RuleSet rs = makeRuleSet("highway=primary & surface=good [0x1]" + "A=B | C=D & E~'f.*' & G!=9 & K=* & L!=* [0x2]"); Set<String> tags = rs.getUsedTags(); assertEquals("number of tags used", 8, tags.size()); assertTrue("has highway", tags.contains("highway")); assertTrue("has surface", tags.contains("surface")); assertTrue("has A", tags.contains("A")); assertTrue("has C", tags.contains("C")); assertTrue("has E", tags.contains("E")); assertTrue("has G", tags.contains("G")); assertTrue("has K", tags.contains("K")); assertTrue("has L", tags.contains("L")); } /** * There is a case where a tag is only used in an action but not in any * expression. If we dropped the tags it would not be available for the * action. A typical example might be name. */ @Test public void testTagsUsedInActions() { RuleSet rs = makeRuleSet("A=B { set t='${C}'; add t='${D} p ${E}'; name '${F} ${G}'; rename K L"); Set<String> tags = rs.getUsedTags(); assertTrue("has A", tags.contains("A")); assertTrue("has C", tags.contains("C")); assertTrue("has D", tags.contains("D")); assertTrue("has E", tags.contains("E")); assertTrue("has F", tags.contains("F")); assertTrue("has G", tags.contains("G")); assertTrue("has K", tags.contains("K")); } @Test public void testIncludeAsTagName() { RuleSet rs = makeRuleSet("include=yes [0x2]"); Way way = new Way(1); way.addTag("include", "yes"); GType type = getFirstType(rs, way); assertNotNull(type); assertEquals(2, type.getType()); } @Test public void testIncludeAsTagName2() { RuleSet rs = makeRuleSet("include = yes [0x2]"); Way way = new Way(1); way.addTag("include", "yes"); GType type = getFirstType(rs, way); assertNotNull(type); assertEquals(2, type.getType()); } @Test public void testIncludeFile() { StyleFileLoader loader = new StringStyleFileLoader(new String[][] { {"lines", "include incfile;"}, {"incfile", "highway=secondary [0x3]"}, }); RuleSet rs = makeRuleSet(loader); Element el = new Way(1); el.addTag("highway", "secondary"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(3, type.getType()); } @Test public void testIncludeFileQuoted() { StyleFileLoader loader = new StringStyleFileLoader(new String[][] { {"lines", "include \n 'inc file' \n;"}, {"inc file", "highway=secondary [0x3]"}, }); RuleSet rs = makeRuleSet(loader); Element el = new Way(1); el.addTag("highway", "secondary"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(3, type.getType()); } /** * Test an include file within an include file. */ @Test public void testNestedIncludes() { StyleFileLoader loader = new StringStyleFileLoader(new String[][] { {"lines", "a=1 [0x1] include 'first'; a=2 [0x2]"}, {"first", "b=1 [0x1] include 'second'; b=2 [0x2 ]"}, {"second", "c=1 [0x1] c=2 [0x2 ]"}, }); RuleSet rs = makeRuleSet(loader); Element el = new Way(1); el.addTag("a", "2"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); el = new Way(2); el.addTag("c", "1"); type = getFirstType(rs, el); assertNotNull(type); assertEquals(1, type.getType()); el = new Way(2); el.addTag("c", "2"); type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); } /** * Bug when the first statement of an include file is itself an include statement. * As luck would have the test tested the supposedly more difficult case of an * include statement in the middle of the file. */ @Test public void testNestedIncludeAndImmediateInclude() { StyleFileLoader loader = new StringStyleFileLoader(new String[][] { {"lines", "a=1 [0x1] include 'first'; a=2 [0x2]"}, {"first", "include 'second'; b=2 [0x2 ]"}, {"second", "c=1 [0x1] c=2 [0x2 ]"}, }); RuleSet rs = makeRuleSet(loader); Element el = new Way(1); el.addTag("a", "2"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); el = new Way(2); el.addTag("c", "1"); type = getFirstType(rs, el); assertNotNull(type); assertEquals(1, type.getType()); el = new Way(2); el.addTag("c", "2"); type = getFirstType(rs, el); assertNotNull(type); assertEquals(2, type.getType()); } @Test public void testIncludeFrom() { // NOTE: this test uses the default style, which could change. StyleFileLoader loader = new StringStyleFileLoader(new String[][] { {"lines", "include 'lines' from default;\n"}, }); RuleSet rs = makeRuleSet(loader); Way way = new Way(1); way.addTag("highway", "motorway"); GType type = getFirstType(rs, way); assertNotNull("Check type not null", type); assertEquals(1, type.getType()); } @Test public void testLengthFunction() { // Its less than 92m RuleSet rs = makeRuleSet("A=B & length() < 92 [0x5]"); Way el = getWayWithLength(); el.addTag("A", "B"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(5, type.getType()); } @Test public void testLengthFunction2() { // Its more than 91m RuleSet rs = makeRuleSet("A=B & length() > 91 [0x5]"); Way el = getWayWithLength(); el.addTag("A", "B"); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(5, type.getType()); } @Test public void testFunctionWithSpaces() { RuleSet rs = makeRuleSet("A=B & length ( \n) > 91 & length\n()\n < 92 [0x5]"); Way el = getWayWithLength(); el.addTag("A", "B"); GType type = getFirstType(rs, el); assertNotNull(type); } @Test(expected = SyntaxException.class) public void testFunctionWithParameters() { // a parameter in a function is not allowed yet // this should throw a SyntaxException makeRuleSet("A=B & length(a) > 91 [0x5]"); assertTrue("Function with parameters are not allowed", false); } @Test public void testIsClosedFunction() { RuleSet rs = makeRuleSet("A=B & is_closed() = true [0x5]"); Way el = getWayForClosedCompleteCheck(true, true); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(5, type.getType()); Way el2 = getWayForClosedCompleteCheck(false, true); RuleSet rs2 = makeRuleSet("A=B & is_closed() = false [0x5]"); GType type2 = getFirstType(rs2, el2); assertNotNull(type2); assertEquals(5, type2.getType()); } @Test public void testIsCompleteFunction() { RuleSet rs = makeRuleSet("A=B & is_complete() = true [0x5]"); Way el = getWayForClosedCompleteCheck(false, true); GType type = getFirstType(rs, el); assertNotNull(type); assertEquals(5, type.getType()); Way el2 = getWayForClosedCompleteCheck(false, false); RuleSet rs2 = makeRuleSet("A=B & is_complete() = false [0x5]"); GType type2 = getFirstType(rs2, el2); assertNotNull(type2); assertEquals(5, type2.getType()); } @Test(expected=SyntaxException.class) public void testNoFunctionParameters() { // a parameter in a function is not allowed for the length() function // this should throw a SyntaxException makeRuleSet("A=B & length(a) > 91 [0x5]"); assertTrue("Function with parameters are not allowed", false); } /** You can't use length as the only term */ @Test(expected=SyntaxException.class) public void testStandAloneLength() { // a parameter in a function is not allowed for the length() function // this should throw a SyntaxException makeRuleSet("length() > 91 [0x5]"); } @Test(expected = SyntaxException.class) public void testFunctionDoesNotExist() { makeRuleSet("A=B & non_existing_function() > 10 [0x5]"); } /** * Functions can be restricted to certain files. Eg length() does not make sense on a point. */ @Test(expected = SyntaxException.class) public void testLengthInPoints() { StringStyleFileLoader loader = new StringStyleFileLoader(new String[][] { {"points", "A=B & length() < 100"} }); RuleSet rs = new RuleSet(); RuleFileReader rr = new RuleFileReader(FeatureKind.POINT, LevelInfo.createFromString("0:24 1:20 2:18 3:16 4:14"), rs, false, null); try { rr.load(loader, "points"); } catch (FileNotFoundException e) { throw new AssertionError("Failed to open file: lines"); } } /** * A test between something that is not not a value should be caught as a syntax * error. */ @Test(expected = SyntaxException.class) public void testWithNonValue() { RuleSet rs = makeRuleSet("c=b & a=!* [0x5]"); Way w = getWayWithLength(); w.addTag("c", "b"); getFirstType(rs, w); } @Test(expected = SyntaxException.class) public void testLessThanWithNonValue() { RuleSet rs = makeRuleSet("c=b & a<!* [0x5]"); Way w = getWayWithLength(); w.addTag("c", "b"); getFirstType(rs, w); } /** * Test the syntax to get a tag value on the RHS of the expression. */ @Test public void testGetTagValueEquality() { RuleSet rs = makeRuleSet("a=b & a=$c [0x5] a=b [0x6]"); Way w = new Way(1); w.addTag("a", "b"); w.addTag("c", "b"); GType type = getFirstType(rs, w); assertNotNull(type); assertEquals(5, type.getType()); w.addTag("c", "x"); type = getFirstType(rs, w); assertNotNull(type); assertEquals(6, type.getType()); } @Test public void testGetTagValueNotFound() { RuleSet rs = makeRuleSet("a=b & b<$c [0x5] a=b [0x6]"); Way w = new Way(1); w.addTag("a", "b"); w.addTag("b", "50"); GType type = getFirstType(rs, w); assertNotNull(type); assertEquals(6, type.getType()); } @Test public void testGetTagValueAlone() { RuleSet rs = makeRuleSet("a<$b [0x5] a=b [0x6]"); Way w = new Way(1); w.addTag("a", "1"); w.addTag("b", "2"); GType type = getFirstType(rs, w); assertNotNull(type); assertEquals(5, type.getType()); } @Test public void test_X3NOT_Error() { // Bug caused an Error: X3:NOT syntax exception to be thrown. RuleSet rs = makeRuleSet("(a=1 | b=2) & !(c=1) & d!=3 [0x8]"); Way w = new Way(1); w.addTag("b", "1"); GType type = getFirstType(rs, w); assertNull(type); w.addTag("b", "2"); type = getFirstType(rs, w); assertNotNull(type); w.addTag("d", "3"); type = getFirstType(rs, w); assertNull(type); w.addTag("d", "2"); type = getFirstType(rs, w); assertNotNull(type); } @Test public void testBugOrWithAndOnLeft() { RuleSet rs = makeRuleSet("((a=1&b=2) | a=2) & c!=4 [0x2]"); Way w = new Way(1); w.addTag("a", "2"); GType type = getFirstType(rs, w); assertNotNull(type); } @Test public void testBugOrWithAndOnLeft2() { RuleSet rs = makeRuleSet("(((a=1 | a=5)&b=2) | a=2) & c!=4 [0x2]"); Way w = new Way(1); w.addTag("a", "2"); GType type = getFirstType(rs, w); assertNotNull(type); assertEquals(type.getType(), 2); } @Test public void testBugOr() { String s = "maxspeed=*\n" + " & ( maxspeedkmh()>120 | maxspeed = none )\n" + " & ( highway = motorway | highway = trunk )\n" + "[0x4]\n"; RuleSet rs = makeRuleSet(s); Way w = new Way(1); w.addTag("highway", "trunk"); w.addTag("maxspeed", "122"); GType type = getFirstType(rs, w); assertNotNull(type); } /** * Get a way with a few points for testing length. * * The length of this segment was independently confirmed to be around 91m. */ private Way getWayWithLength() { Way el = new Way(1); el.addPoint(new Coord(51.6124376, -0.1777185)); el.addPoint(new Coord(51.6127816, -0.1775029)); el.addPoint(new Coord(51.6132048, -0.1772467)); return el; } /** * Get a way with a few points for testing the closed and complete flag. * @param closed way should be closed * @param complete way should not be complete */ private Way getWayForClosedCompleteCheck(boolean closed, boolean complete) { Way el = new Way(1); el.addTag("A","B"); el.addPoint(new Coord(1000,1000)); el.addPoint(new Coord(1000,2000)); el.addPoint(new Coord(2000,2000)); el.addPoint(new Coord(2000,1000)); if (closed) el.addPoint(el.getPoints().get(0)); el.setComplete(complete); el.setClosedInOSM(true); return el; } /** * Resolve the rule set with the given element and get the first * resolved type. */ private GType getFirstType(Rule rs, Element el) { final List<GType> types = new ArrayList<>(); rs.resolveType(el, new TypeResult() { public void add(Element el, GType type) { types.add(type); } }); if (types.isEmpty()) return null; else return types.get(0); } }