Index: src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 15542)
+++ src/org/openstreetmap/josm/data/validation/OsmValidator.java	(working copy)
@@ -60,6 +60,7 @@
 import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
 import org.openstreetmap.josm.data.validation.tests.RelationChecker;
 import org.openstreetmap.josm.data.validation.tests.RightAngleBuildingTest;
+import org.openstreetmap.josm.data.validation.tests.RoutingIslandsTest;
 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
 import org.openstreetmap.josm.data.validation.tests.SharpAngles;
 import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
@@ -150,6 +151,7 @@
         PublicTransportRouteTest.class, // 3600 .. 3699
         RightAngleBuildingTest.class, // 3700 .. 3799
         SharpAngles.class, // 3800 .. 3899
+        RoutingIslandsTest.class, // 3900 .. 3999
     };
 
     /**
Index: src/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTest.java	(nonexistent)
+++ src/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTest.java	(working copy)
@@ -0,0 +1,292 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.spi.preferences.Config;
+
+/**
+ * A test for routing islands
+ *
+ * @author Taylor Smock
+ * @since xxx
+ */
+public class RoutingIslandsTest extends Test {
+
+    private static final int ROUTING_ISLAND = 3900;
+    /**
+     * This is mostly as a sanity check, and to avoid infinite recursion (shouldn't
+     * happen, but still)
+     */
+    private static final int MAX_LOOPS = 1000;
+    private Set<Way> potentialWays;
+
+    // Duplicates from ConditionalKeys (should probably change them to public,
+    // possibly use Collections.unmodifiableSet TODO iterate through the transport
+    // modes
+    private static final Set<String> RESTRICTION_VALUES = new HashSet<>(Arrays.asList("yes", "official", "designated",
+            "destination", "delivery", "customers", "permissive", "private", "agricultural", "forestry", "no"));
+    private static final Set<String> TRANSPORT_MODES = new HashSet<>(
+            Arrays.asList("access", "foot", "ski", "inline_skates", "ice_skates", "horse", "vehicle", "bicycle",
+                    "carriage", "trailer", "caravan", "motor_vehicle", "motorcycle", "moped", "mofa", "motorcar",
+                    "motorhome", "psv", "bus", "taxi", "tourist_bus", "goods", "hgv", "agricultural", "atv",
+                    "snowmobile", "hgv_articulated", "ski:nordic", "ski:alpine", "ski:telemark", "coach", "golf_cart"
+            /*
+             * ,"minibus","share_taxi","hov","car_sharing","emergency","hazmat","disabled"
+             */));
+
+    /**
+     * Constructs a new {@code RightAngleBuildingTest} test.
+     */
+    public RoutingIslandsTest() {
+        super(tr("Routing islands"), tr("Checks for roads that cannot be reached or left."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor) {
+        super.startTest(monitor);
+        potentialWays = new HashSet<>();
+    }
+
+    @Override
+    public void endTest() {
+        runTest(null);
+        super.endTest();
+    }
+
+    /**
+     * TODO Turn restrictions, loop through transport types
+     */
+
+    /**
+     * May connect to: 1) A road leaving the fully downloaded BBox 2) A
+     * dock/ferry/other waterway loading point 3) An aeroport
+     */
+
+    @Override
+    public void visit(Way way) {
+        if (way.hasKey("highway") && way.isUsable()
+                && way.getDataSet().getDataSources().parallelStream()
+                        .filter(source -> source.bounds.toBBox().intersects(way.getBBox())).map(bound -> bound.origin)
+                        .filter(Objects::nonNull).filter(string -> !string.trim().isEmpty())
+                        .anyMatch(string -> string.contains("openstreetmap.org")))
+            potentialWays.add(way);
+    }
+
+    private void runTest(String currentTransportMode) {
+        Set<Way> incomingWays = new HashSet<>();
+        Set<Way> outgoingWays = new HashSet<>();
+        for (Way way : potentialWays) {
+            if (way.isUsable() && way.isOutsideDownloadArea()) {
+                if (isOneway(way, currentTransportMode) != 0
+                        && firstNode(way, currentTransportMode).isOutsideDownloadArea())
+                    incomingWays.add(way);
+                if (isOneway(way, currentTransportMode) != 0
+                        && lastNode(way, currentTransportMode).isOutsideDownloadArea()) {
+                    outgoingWays.add(way);
+                }
+                if (isOneway(way, currentTransportMode) == 0
+                        && (way.firstNode().isOutsideDownloadArea() || way.lastNode().isOutsideDownloadArea())) {
+                    incomingWays.add(way);
+                    outgoingWays.add(way);
+                }
+            }
+        }
+        Set<Way> toCheck = potentialWays.parallelStream()
+                .filter(way -> !incomingWays.contains(way) && !outgoingWays.contains(way)).collect(Collectors.toSet());
+        checkForUnconnectedWays(incomingWays, outgoingWays, currentTransportMode);
+        List<Set<Way>> problematic = collectConnected(toCheck.parallelStream()
+                .filter(way -> !incomingWays.contains(way) || !outgoingWays.contains(way)).collect(Collectors.toSet()));
+        createErrors(problematic);
+    }
+
+    private static List<Set<Way>> collectConnected(Collection<Way> ways) {
+        ArrayList<Set<Way>> collected = new ArrayList<>();
+        ArrayList<Way> listOfWays = new ArrayList<>(ways);
+        final int maxLoop = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
+        for (int i = 0; i < listOfWays.size(); i++) {
+            Way initial = listOfWays.get(i);
+            Set<Way> connected = new HashSet<>();
+            connected.add(initial);
+            int loopCounter = 0;
+            while (!getConnected(connected) && loopCounter < maxLoop) {
+                loopCounter++;
+            }
+            if (listOfWays.removeAll(connected))
+                i--; // not an issue -- this ensures that everything is accounted for, only triggers
+                     // when ways removed
+            collected.add(connected);
+        }
+        return collected;
+    }
+
+    private static boolean getConnected(Collection<Way> ways) {
+        return ways.addAll(ways.parallelStream().flatMap(way -> way.getNodes().parallelStream())
+                .flatMap(node -> node.getReferrers().parallelStream()).filter(Way.class::isInstance)
+                .map(Way.class::cast).collect(Collectors.toSet()));
+    }
+
+    private void createErrors(List<Set<Way>> problematic) {
+        problematic.forEach(
+                way -> errors.add(TestError.builder(this, Severity.OTHER, ROUTING_ISLAND).message(tr("Routing island"))
+                        .primitives(way).build()));
+    }
+
+    /**
+     * Check for unconnected ways
+     *
+     * @param incoming             The current incoming ways (will be modified)
+     * @param outgoing             The current outgoing ways (will be modified)
+     * @param currentTransportMode The transport mode we are investigating (may be
+     *                             {@code null})
+     */
+    public static void checkForUnconnectedWays(Collection<Way> incoming,
+            Collection<Way> outgoing, String currentTransportMode) {
+        int loopCount = 0;
+        int maxLoops = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
+        do {
+            loopCount++;
+        } while (loopCount <= maxLoops && getWaysFor(incoming, currentTransportMode,
+                (way, oldWay) -> oldWay.containsNode(firstNode(way, currentTransportMode))));
+        loopCount = 0;
+        do {
+            loopCount++;
+        } while (loopCount <= maxLoops && getWaysFor(outgoing, currentTransportMode,
+                (way, oldWay) -> oldWay.containsNode(lastNode(way, currentTransportMode))));
+    }
+
+    private static boolean getWaysFor(Collection<Way> directional, String currentTransportMode,
+            BiPredicate<Way, Way> predicate) {
+        Set<Way> toAdd = new HashSet<>();
+        for (Way way : directional) {
+            for (Node node : way.getNodes()) {
+                Set<Way> referrers = node.getReferrers(true).parallelStream().filter(Way.class::isInstance)
+                        .map(Way.class::cast).filter(tWay -> !directional.contains(tWay)).collect(Collectors.toSet());
+                for (Way tWay : referrers) {
+                    if (isOneway(tWay, currentTransportMode) == 0 || predicate.test(tWay, way)
+                            || tWay.hasKey("junction")) {
+                        toAdd.add(tWay);
+                    }
+                }
+            }
+        }
+        return directional.addAll(toAdd);
+    }
+
+    /**
+     * Check if I can get to way to from way from (currently doesn't work with via
+     * ways)
+     *
+     * @param from                 The from way
+     * @param to                   The to way
+     * @param currentTransportMode The specific transport mode to check
+     * @return {@code true} if the to way can be accessed from the from way TODO
+     *         clean up and work with via ways
+     */
+    public static boolean checkAccessibility(Way from, Way to, String currentTransportMode) {
+        boolean isAccessible = true;
+
+        List<Relation> relations = from.getReferrers().parallelStream().distinct().filter(Relation.class::isInstance)
+                .map(Relation.class::cast).filter(relation -> "restriction".equals(relation.get("type")))
+                .collect(Collectors.toList());
+        for (Relation relation : relations) {
+            if ((relation.hasKey("except") && relation.get("except").contains(currentTransportMode)
+                    || (currentTransportMode == null || currentTransportMode.trim().isEmpty()))
+                    && relation.getMembersFor(Collections.singleton(from)).parallelStream()
+                    .anyMatch(member -> "from".equals(member.getRole()))
+                    && relation.getMembersFor(Collections.singleton(to)).parallelStream()
+                            .anyMatch(member -> "to".equals(member.getRole()))) {
+                isAccessible = false;
+            }
+        }
+
+        return isAccessible;
+    }
+
+    /**
+     * Check if a node connects to the outside world
+     *
+     * @param node The node to check
+     * @return true if outside download area, connects to an aeroport, or a water
+     *         transport
+     */
+    public static Boolean outsideConnections(Node node) {
+        boolean outsideConnections = false;
+        if (node.isOutsideDownloadArea() || node.hasTag("amenity", "parking_entrance", "parking", "parking_space",
+                "motorcycle_parking", "ferry_terminal"))
+            outsideConnections = true;
+        return outsideConnections;
+    }
+
+    /**
+     * Check if a way is oneway for a specific transport type
+     *
+     * @param way           The way to look at
+     * @param transportType The specific transport type
+     * @return See {@link Way#isOneway} (but may additionally return {@code null} if
+     *         the transport type cannot route down that way)
+     */
+    public static Integer isOneway(Way way, String transportType) {
+        if (transportType == null || transportType.trim().isEmpty()) {
+            return way.isOneway();
+        }
+        String forward = transportType.concat(":forward");
+        String backward = transportType.concat(":backward");
+        boolean possibleForward = "yes".equals(way.get(forward))
+                || (!way.hasKey(forward) && way.isOneway() != -1);
+        boolean possibleBackward = "yes".equals(way.get(backward))
+                || (!way.hasKey(backward) && way.isOneway() != 1);
+        if (possibleForward && !possibleBackward) {
+            return 1;
+        } else if (!possibleForward && possibleBackward) {
+            return -1;
+        } else if (!possibleBackward) {
+            return null;
+        }
+        return 0;
+    }
+
+    /**
+     * Get the first node of a way respecting the oneway for a transport type
+     *
+     * @param way           The way to get the node from
+     * @param transportType The transport type
+     * @return The first node for the specified transport type, or the first node of
+     *         the way
+     */
+    public static Node firstNode(Way way, String transportType) {
+        Integer oneway = isOneway(way, transportType);
+        return (Integer.valueOf(-1).equals(oneway)) ? way.lastNode() : way.firstNode();
+    }
+
+    /**
+     * Get the last node of a way respecting the oneway for a transport type
+     *
+     * @param way           The way to get the node from
+     * @param transportType The transport type
+     * @return The last node for the specified transport type, or the last node of
+     *         the way
+     */
+    public static Node lastNode(Way way, String transportType) {
+        Integer oneway = isOneway(way, transportType);
+        return (Integer.valueOf(-1).equals(oneway)) ? way.firstNode() : way.lastNode();
+    }
+}
Index: test/unit/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTestTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTestTest.java	(nonexistent)
+++ test/unit/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTestTest.java	(working copy)
@@ -0,0 +1,325 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Test class for {@link RoutingIslandsTest}
+ *
+ * @author Taylor Smock
+ * @since xxx
+ */
+public class RoutingIslandsTestTest {
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules rule = new JOSMTestRules().projection().preferences();
+
+    /**
+     * Test method for {@link RoutingIslandsTest#RoutingIslandsTest()} and the
+     * testing apparatus
+     */
+    @Test
+    public void testRoutingIslandsTest() {
+        RoutingIslandsTest test = new RoutingIslandsTest();
+        test.startTest(null);
+        test.endTest();
+        assertTrue(test.getErrors().isEmpty());
+
+        DataSet ds = new DataSet();
+
+        Way way1 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
+        Way way2 = TestUtils.newWay("highway=residential", new Node(new LatLon(-1, 0)), way1.firstNode());
+        addToDataSet(ds, way1);
+        addToDataSet(ds, way2);
+
+        ds.addDataSource(new DataSource(new Bounds(0, 0, 1, 1), "openstreetmap.org"));
+
+        test.clear();
+        test.startTest(null);
+        test.visit(ds.allPrimitives());
+        test.endTest();
+        assertTrue(test.getErrors().isEmpty());
+
+        ds.addDataSource(new DataSource(new Bounds(-5, -5, 5, 5), "openstreetmap.org"));
+        test.clear();
+        test.startTest(null);
+        test.visit(ds.allPrimitives());
+        test.endTest();
+        assertEquals(1, test.getErrors().size());
+        assertEquals(2, test.getErrors().get(0).getPrimitives().size());
+
+        ds.clear();
+        way1 = TestUtils.newWay("highway=motorway oneway=yes", new Node(new LatLon(39.1156655, -108.5465434)),
+                new Node(new LatLon(39.1157251, -108.5496874)), new Node(new LatLon(39.11592, -108.5566841)));
+        way2 = TestUtils.newWay("highway=motorway oneway=yes", new Node(new LatLon(39.1157244, -108.55674)),
+                new Node(new LatLon(39.1155548, -108.5496901)), new Node(new LatLon(39.1154827, -108.5462431)));
+        addToDataSet(ds, way1);
+        addToDataSet(ds, way2);
+        ds.addDataSource(
+                new DataSource(new Bounds(new LatLon(39.1161391, -108.5516083), new LatLon(39.1136949, -108.5489166)),
+                        "openstreetmap.org"));
+        test.clear();
+        test.startTest(null);
+        test.visit(ds.allPrimitives());
+        test.endTest();
+        assertTrue(test.getErrors().isEmpty());
+        Way way3 = TestUtils.newWay("highway=service", way1.getNode(1), way2.getNode(2));
+        addToDataSet(ds, way3);
+        test.startTest(null);
+        test.visit(ds.allPrimitives());
+        test.endTest();
+        assertTrue(test.getErrors().isEmpty());
+    }
+
+    /**
+     * Test roundabouts
+     */
+    @Test
+    public void testRoundabouts() {
+        RoutingIslandsTest test = new RoutingIslandsTest();
+        Way roundabout = TestUtils.newWay("highway=residential junction=roundabout oneway=yes",
+                new Node(new LatLon(39.119582, -108.5262686)), new Node(new LatLon(39.1196494, -108.5260935)),
+                new Node(new LatLon(39.1197572, -108.5260784)), new Node(new LatLon(39.1197929, -108.526391)),
+                new Node(new LatLon(39.1196595, -108.5264047)));
+        roundabout.addNode(roundabout.firstNode()); // close it up
+        DataSet ds = new DataSet();
+        addToDataSet(ds, roundabout);
+        ds.addDataSource(
+                new DataSource(new Bounds(new LatLon(39.1182025, -108.527574), new LatLon(39.1210588, -108.5251112)),
+                        "openstreetmap.org"));
+        Way incomingFlare = TestUtils.newWay("highway=residential oneway=yes",
+                new Node(new LatLon(39.1196377, -108.5257567)), roundabout.getNode(3));
+        addToDataSet(ds, incomingFlare);
+        Way outgoingFlare = TestUtils.newWay("highway=residential oneway=yes", roundabout.getNode(2),
+                incomingFlare.firstNode());
+        addToDataSet(ds, outgoingFlare);
+
+        Way outgoingRoad = TestUtils.newWay("highway=residential", incomingFlare.firstNode(),
+                new Node(new LatLon(39.1175184, -108.5219623)));
+        addToDataSet(ds, outgoingRoad);
+
+        test.startTest(null);
+        test.visit(ds.allPrimitives());
+        test.endTest();
+        assertTrue(test.getErrors().isEmpty());
+    }
+
+    private static void addToDataSet(DataSet ds, Way way) {
+        way.getNodes().parallelStream().distinct().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
+        if (way.getDataSet() == null)
+            ds.addPrimitive(way);
+        Long id = Math.max(ds.allPrimitives().parallelStream().mapToLong(prim -> prim.getId()).max().orElse(0L), 0L);
+        for (OsmPrimitive osm : ds.allPrimitives()) {
+            id++;
+            osm.setOsmId(id, 1);
+        }
+    }
+
+    /**
+     * Test method for
+     * {@link RoutingIslandsTest#checkForUnconnectedWays(Collection, Collection, String)}.
+     */
+    @Test
+    public void testCheckForUnconnectedWaysIncoming() {
+        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), Collections.emptySet(), null);
+        Way way1 = TestUtils.newWay("highway=residential oneway=yes", new Node(new LatLon(0, 0)),
+                new Node(new LatLon(1, 1)));
+        Set<Way> incomingSet = new HashSet<>();
+        DataSet ds = new DataSet();
+        way1.getNodes().forEach(ds::addPrimitive);
+        ds.addPrimitive(way1);
+        incomingSet.add(way1);
+        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
+        assertEquals(1, incomingSet.size());
+        assertSame(way1, incomingSet.iterator().next());
+
+        Way way2 = TestUtils.newWay("highway=residential", way1.firstNode(), new Node(new LatLon(-1, -2)));
+        way2.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
+        ds.addPrimitive(way2);
+
+        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
+        assertEquals(2, incomingSet.size());
+        assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
+
+        Way way3 = TestUtils.newWay("highway=residential", way2.lastNode(), new Node(new LatLon(-2, -1)));
+        way3.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
+        ds.addPrimitive(way3);
+
+        incomingSet.clear();
+        incomingSet.add(way1);
+        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
+        assertEquals(3, incomingSet.size());
+        assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2, way3).contains(way)));
+
+        Config.getPref().putInt("validator.routingislands.maxrecursion", 1);
+        incomingSet.clear();
+        incomingSet.add(way1);
+        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
+        assertEquals(2, incomingSet.size());
+        assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
+    }
+
+    @Test
+    public void testCheckForUnconnectedWaysOutgoing() {
+        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), Collections.emptySet(), null);
+        Way way1 = TestUtils.newWay("highway=residential oneway=yes", new Node(new LatLon(0, 0)),
+                new Node(new LatLon(1, 1)));
+        Set<Way> outgoingSet = new HashSet<>();
+        DataSet ds = new DataSet();
+        way1.getNodes().forEach(ds::addPrimitive);
+        ds.addPrimitive(way1);
+        outgoingSet.add(way1);
+        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
+        assertEquals(1, outgoingSet.size());
+        assertSame(way1, outgoingSet.iterator().next());
+
+        Way way2 = TestUtils.newWay("highway=residential", way1.firstNode(), new Node(new LatLon(-1, -2)));
+        way2.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
+        ds.addPrimitive(way2);
+
+        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
+        assertEquals(2, outgoingSet.size());
+        assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
+
+        Way way3 = TestUtils.newWay("highway=residential", way2.lastNode(), new Node(new LatLon(-2, -1)));
+        way3.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
+        ds.addPrimitive(way3);
+
+        outgoingSet.clear();
+        outgoingSet.add(way1);
+        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
+        assertEquals(3, outgoingSet.size());
+        assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2, way3).contains(way)));
+
+        Config.getPref().putInt("validator.routingislands.maxrecursion", 1);
+        outgoingSet.clear();
+        outgoingSet.add(way1);
+        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
+        assertEquals(2, outgoingSet.size());
+        assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
+    }
+
+    /**
+     * Test method for {@link RoutingIslandsTest#outsideConnections(Node)}.
+     */
+    @Test
+    public void testOutsideConnections() {
+        Node node = new Node(new LatLon(0, 0));
+        DataSet ds = new DataSet(node);
+        ds.addDataSource(new DataSource(new Bounds(-0.1, -0.1, -0.01, -0.01), "Test bounds"));
+        node.setOsmId(1, 1);
+        assertTrue(RoutingIslandsTest.outsideConnections(node));
+        ds.addDataSource(new DataSource(new Bounds(-0.1, -0.1, 0.1, 0.1), "Test bounds"));
+        assertFalse(RoutingIslandsTest.outsideConnections(node));
+        node.put("amenity", "parking_entrance");
+        assertTrue(RoutingIslandsTest.outsideConnections(node));
+    }
+
+    /**
+     * Test method for {@link RoutingIslandsTest#isOneway(Way, String)}.
+     */
+    @Test
+    public void testIsOneway() {
+        Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
+        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, null));
+        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, " "));
+        way.put("oneway", "yes");
+        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, null));
+        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, " "));
+        way.put("oneway", "-1");
+        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, null));
+        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, " "));
+
+        way.put("vehicle:forward", "yes");
+        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, "vehicle"));
+        way.put("vehicle:backward", "no");
+        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, "vehicle"));
+        way.put("vehicle:forward", "no");
+        assertNull(RoutingIslandsTest.isOneway(way, "vehicle"));
+        way.put("vehicle:backward", "yes");
+        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, "vehicle"));
+
+        way.put("oneway", "yes");
+        way.remove("vehicle:backward");
+        way.remove("vehicle:forward");
+        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, "vehicle"));
+        way.remove("oneway");
+        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, "vehicle"));
+
+        way.put("oneway", "-1");
+        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, "vehicle"));
+    }
+
+    /**
+     * Test method for {@link RoutingIslandsTest#firstNode(Way, String)}.
+     */
+    @Test
+    public void testFirstNode() {
+        Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
+        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, null));
+        way.put("oneway", "yes");
+        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, null));
+        way.put("oneway", "-1");
+        assertEquals(way.lastNode(), RoutingIslandsTest.firstNode(way, null));
+
+        way.put("vehicle:forward", "yes");
+        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
+        way.put("vehicle:backward", "no");
+        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
+        way.put("vehicle:forward", "no");
+        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
+        way.put("vehicle:backward", "yes");
+        assertEquals(way.lastNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
+    }
+
+    /**
+     * Test method for {@link RoutingIslandsTest#lastNode(Way, String)}.
+     */
+    @Test
+    public void testLastNode() {
+        Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
+        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, null));
+        way.put("oneway", "yes");
+        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, null));
+        way.put("oneway", "-1");
+        assertEquals(way.firstNode(), RoutingIslandsTest.lastNode(way, null));
+
+        way.put("vehicle:forward", "yes");
+        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
+        way.put("vehicle:backward", "no");
+        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
+        way.put("vehicle:forward", "no");
+        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
+        way.put("vehicle:backward", "yes");
+        assertEquals(way.firstNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
+    }
+
+}
