Repository: graphhopper/graphhopper
Pull request #982 (2017-03-09 16:20:23)

MMapDataAccess should synchronize on its ByteBuffers by itself
- MMapDataAccess now synchronizes on its ByteBuffers by itself, and is now as thread-safe as the other implementations. (That is, mostly, except for create/close/ensureCapacity and such.)
- Remove the dimension of "synched" or "not synched" from DataAccess.
- Remove tests which tested this dimension.
- Remove some unused code.

Benefits:

- One whole concept less.
- Clarifies that we do read/write locking externally, i.e. on the layer of GraphHopper, not on the layer of DataAccess.
- Cannot accidentally be made incorrect by using more than one (reading) thread without using "_SYNC".

Drawbacks:

- Possibility of being slower when running single-threaded because of the overhead of synchronized{} blocks _as such_, without thread contention. No idea how likely that is.


Counter-argument against drawback:

- That is not as bad as being *generally* thread-safe *except* with certain parameters.


How can I test Android performance?

karussell: Thanks, this looks better.

> How can I test Android performance?

That is tough. I have not done this properly/scientific. Also there are so many devices where we could test. So I would just route from two identical places a few times and repeat the same for the change or write a bit code to do this in the demo (e.g. when click&pressing some device button). And then @devemux86 could repeat. Or we could see if there are CI's that run Android somehow deterministically where we could include this in the build process.
karussell: BTW: For the JVM I would exclude any problems due to synchronized. It is VERY fast even with synch. But for dalvik I have no idea. (What a mess Google&Oracle introduced with this ...)

Patch:
diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java
index ccb77a559..f701d93b7 100644
--- a/core/src/main/java/com/graphhopper/GraphHopper.java
+++ b/core/src/main/java/com/graphhopper/GraphHopper.java
@@ -1161,9 +1161,6 @@ public class GraphHopper implements GraphHopperAPI {
         if (tmpPrepare) {
             ensureWriteAccess();
 
-            if (chFactoryDecorator.getPreparationThreads() > 1 && dataAccessType.isMMap() && !dataAccessType.isSynched())
-                throw new IllegalStateException("You cannot execute CH preparation in parallel for MMAP without synching! Specify MMAP_SYNC or use 1 thread only");
-
             ghStorage.freeze();
             chFactoryDecorator.prepare(ghStorage.getProperties());
             ghStorage.getProperties().put(CH.PREPARE + "done", true);
diff --git a/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java
index 96e9f953e..a25951c91 100644
--- a/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java
+++ b/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java
@@ -24,10 +24,7 @@ import com.graphhopper.routing.util.spatialrules.SpatialRule;
 import com.graphhopper.routing.util.spatialrules.SpatialRuleLookup;
 import com.graphhopper.routing.util.spatialrules.TransportationMode;
 import com.graphhopper.routing.weighting.GenericWeighting;
-import com.graphhopper.util.ConfigMap;
-import com.graphhopper.util.EdgeIteratorState;
-import com.graphhopper.util.Helper;
-import com.graphhopper.util.PMap;
+import com.graphhopper.util.*;
 import com.graphhopper.util.shapes.GHPoint;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -77,6 +74,7 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
     private final Map<String, Integer> transportModeMap = new HashMap<>();
     private final int transportModeTunnelValue;
     private final int transportModeBridgeValue;
+    private final int transportModeFordValue;
     private long bit0;
     private EncodedDoubleValue carFwdMaxspeedEncoder;
     private EncodedDoubleValue carBwdMaxspeedEncoder;
@@ -141,6 +139,7 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         }
         transportModeTunnelValue = transportModeMap.get("tunnel");
         transportModeBridgeValue = transportModeMap.get("bridge");
+        transportModeFordValue = transportModeMap.get("ford");
 
         List<String> surfaceList = Arrays.asList("_default", "asphalt", "unpaved", "paved", "gravel",
                 "ground", "dirt", "grass", "concrete", "paving_stones", "sand", "compacted", "cobblestone", "mud", "ice");
@@ -273,9 +272,9 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
             }
         }
 
-        if (spatialRuleLookupEnabled() && accessValue == 0) {
+        if (isSpatialRuleLookupEnabled() && accessValue == 0) {
             // TODO Fix transportation mode when adding other forms of transportation
-            switch (getSpatialRule(way).getAccessValue(way.getTag("highway", ""), TransportationMode.MOTOR_VEHICLE, AccessValue.ACCESSIBLE)){
+            switch (getSpatialRule(way).getAccessValue(way.getTag("highway", ""), TransportationMode.MOTOR_VEHICLE, AccessValue.ACCESSIBLE)) {
                 case ACCESSIBLE:
                     accessValue = accessMap.get("yes");
                     break;
@@ -325,7 +324,7 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
 
             // MAXSPEED
             double maxSpeed = parseSpeed(way.getTag("maxspeed"));
-            if (spatialRuleLookupEnabled() && maxSpeed < 0) {
+            if (isSpatialRuleLookupEnabled() && maxSpeed < 0) {
                 // TODO What if no maxspeed is set, but only forward and backward, and both are higher than the usually allowed?
                 maxSpeed = getSpatialRule(way).getMaxSpeed(way.getTag("highway", ""), maxSpeed);
             }
@@ -413,7 +412,7 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
             flags = accessEncoder.setValue(flags, getAccessValue(way));
 
 
-            if(spatialRuleLookupEnabled()){
+            if (isSpatialRuleLookupEnabled()) {
                 GHPoint estimatedCenter = way.getTag("estimated_center", null);
                 if (estimatedCenter != null) {
                     SpatialRule rule = spatialRuleLookup.lookupRule(estimatedCenter);
@@ -427,7 +426,7 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         }
     }
 
-    private boolean spatialRuleLookupEnabled(){
+    private boolean isSpatialRuleLookupEnabled() {
         if (spatialRules > 0) {
             if (spatialRuleLookup == null)
                 throw new IllegalStateException("This encoder was asked to store spatial IDs for every edge, " +
@@ -631,6 +630,10 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         return transportModeEncoder.getValue(edge.getFlags()) == this.transportModeBridgeValue;
     }
 
+    public boolean isTransportModeFord(long flags) {
+        return transportModeEncoder.getValue(flags) == this.transportModeFordValue;
+    }
+
     public String getTransportModeAsString(EdgeIteratorState edge) {
         int val = getTransportMode(edge);
         for (Entry<String, Integer> e : transportModeMap.entrySet()) {
@@ -842,6 +845,16 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         return this;
     }
 
+    @Override
+    public InstructionAnnotation getAnnotation(long flags, Translation tr) {
+        if (isTransportModeFord(flags)) {
+            return new InstructionAnnotation(1, tr.tr("way_contains_ford"));
+        }
+
+        return super.getAnnotation(flags, tr);
+    }
+
+
     @Override
     protected String getPropertiesString() {
         return super.getPropertiesString() +
diff --git a/core/src/main/java/com/graphhopper/storage/DAType.java b/core/src/main/java/com/graphhopper/storage/DAType.java
index 3d95dd37e..20bdf4591 100644
--- a/core/src/main/java/com/graphhopper/storage/DAType.java
+++ b/core/src/main/java/com/graphhopper/storage/DAType.java
@@ -27,55 +27,48 @@ public class DAType {
     /**
      * The DA object is hold entirely in-memory. Loading and flushing is a no-op. See RAMDataAccess.
      */
-    public static final DAType RAM = new DAType(MemRef.HEAP, false, false, true, false);
+    public static final DAType RAM = new DAType(MemRef.HEAP, false, false, true);
     /**
      * Optimized RAM DA type for integer access. The set and getBytes methods cannot be used.
      */
-    public static final DAType RAM_INT = new DAType(MemRef.HEAP, false, true, true, false);
+    public static final DAType RAM_INT = new DAType(MemRef.HEAP, false, true, true);
     /**
      * The DA object is hold entirely in-memory. It will read load disc and flush to it if they
      * equivalent methods are called. See RAMDataAccess.
      */
-    public static final DAType RAM_STORE = new DAType(MemRef.HEAP, true, false, true, false);
+    public static final DAType RAM_STORE = new DAType(MemRef.HEAP, true, false, true);
     /**
      * Optimized RAM_STORE DA type for integer access. The set and getBytes methods cannot be used.
      */
-    public static final DAType RAM_INT_STORE = new DAType(MemRef.HEAP, true, true, true, false);
+    public static final DAType RAM_INT_STORE = new DAType(MemRef.HEAP, true, true, true);
     /**
-     * Memory mapped DA object. See MMapDataAccess. To make it read and write thread-safe you need
-     * to use 'new DAType(MMAP, true)'
+     * Memory mapped DA object. See MMapDataAccess.
      */
-    public static final DAType MMAP = new DAType(MemRef.MMAP, true, false, true, false);
+    public static final DAType MMAP = new DAType(MemRef.MMAP, true, false, true);
 
     /**
      * Read-only memory mapped DA object. To avoid write access useful for reading on mobile or
      * embedded data stores.
      */
-    public static final DAType MMAP_RO = new DAType(MemRef.MMAP, true, false, false, false);
+    public static final DAType MMAP_RO = new DAType(MemRef.MMAP, true, false, false);
     /**
      * Experimental API. Do not use yet.
      */
-    public static final DAType UNSAFE_STORE = new DAType(MemRef.UNSAFE, true, false, true, false);
+    public static final DAType UNSAFE_STORE = new DAType(MemRef.UNSAFE, true, false, true);
     private final MemRef memRef;
     private final boolean storing;
     private final boolean integ;
-    private final boolean synched;
     private final boolean allowWrites;
 
-    public DAType(DAType type, boolean synched) {
-        this(type.getMemRef(), type.isStoring(), type.isInteg(), type.isAllowWrites(), synched);
-        if (!synched)
-            throw new IllegalStateException("constructor can only be used with synched=true");
-        if (type.isSynched())
-            throw new IllegalStateException("something went wrong as DataAccess object is already synched!?");
+    public DAType(DAType type) {
+        this(type.getMemRef(), type.isStoring(), type.isInteg(), type.isAllowWrites());
     }
 
-    public DAType(MemRef memRef, boolean storing, boolean integ, boolean allowWrites, boolean synched) {
+    public DAType(MemRef memRef, boolean storing, boolean integ, boolean allowWrites) {
         this.memRef = memRef;
         this.storing = storing;
         this.integ = integ;
         this.allowWrites = allowWrites;
-        this.synched = synched;
     }
 
     public static DAType fromString(String dataAccess) {
@@ -89,9 +82,6 @@ public class DAType {
             type = DAType.RAM_STORE;
         else
             type = DAType.RAM;
-
-        if (dataAccess.contains("SYNC"))
-            type = new DAType(type, true);
         return type;
     }
 
@@ -131,15 +121,6 @@ public class DAType {
         return integ;
     }
 
-    /**
-     * Synchronized access wrapper around DataAccess objects? default is false and so an in-memory
-     * DataAccess object is only read-thread safe where a memory mapped one is not even
-     * read-threadsafe!
-     */
-    public boolean isSynched() {
-        return synched;
-    }
-
     @Override
     public String toString() {
         String str;
@@ -154,8 +135,6 @@ public class DAType {
             str += "_INT";
         if (isStoring())
             str += "_STORE";
-        if (isSynched())
-            str += "_SYNC";
         return str;
     }
 
@@ -165,7 +144,6 @@ public class DAType {
         hash = 59 * hash + 37 * this.memRef.hashCode();
         hash = 59 * hash + (this.storing ? 1 : 0);
         hash = 59 * hash + (this.integ ? 1 : 0);
-        hash = 59 * hash + (this.synched ? 1 : 0);
         return hash;
     }
 
@@ -182,8 +160,6 @@ public class DAType {
             return false;
         if (this.integ != other.integ)
             return false;
-        if (this.synched != other.synched)
-            return false;
         return true;
     }
 
diff --git a/core/src/main/java/com/graphhopper/storage/GHDirectory.java b/core/src/main/java/com/graphhopper/storage/GHDirectory.java
index 5d289bd54..953985ad9 100644
--- a/core/src/main/java/com/graphhopper/storage/GHDirectory.java
+++ b/core/src/main/java/com/graphhopper/storage/GHDirectory.java
@@ -117,9 +117,6 @@ public class GHDirectory implements Directory {
             da = new UnsafeDataAccess(name, location, byteOrder);
         }
 
-        if (type.isSynched())
-            da = new SynchedDAWrapper(da);
-
         map.put(name, da);
         return da;
     }
diff --git a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java
index 4bc07d939..63ff8333b 100644
--- a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java
+++ b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java
@@ -19,7 +19,6 @@ package com.graphhopper.storage;
 
 import com.graphhopper.util.Constants;
 import com.graphhopper.util.Helper;
-import com.graphhopper.util.NotThreadSafe;
 
 import java.io.File;
 import java.io.IOException;
@@ -32,29 +31,33 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * This is a data structure which uses the operating system to synchronize between disc and memory.
- * Use {@link SynchedDAWrapper} if you intent to use this from multiple threads!
+ * A DataAccess implementation using a memory-mapped file, i.e. a facility of the
+ * operating system to access a file like an area of RAM.
+ *
+ * Java presents the mapped memory as a ByteBuffer, and ByteBuffer is not
+ * thread-safe, which means that access to a ByteBuffer must be externally
+ * synchronized.
+ *
+ * This class itself is intended to be as thread-safe as other DataAccess
+ * implementations are.
+ *
+ * The exact behavior of memory-mapping is reported to be wildly platform-dependent.
+ *
  * <p>
  *
  * @author Peter Karich
+ * @author Michael Zilske
  */
-@NotThreadSafe
-public class MMapDataAccess extends AbstractDataAccess {
+public final class MMapDataAccess extends AbstractDataAccess {
     private final boolean allowWrites;
     private RandomAccessFile raFile;
-    private List<ByteBuffer> segments = new ArrayList<ByteBuffer>();
-    private boolean cleanAndRemap = false;
+    private List<ByteBuffer> segments = new ArrayList<>();
 
     MMapDataAccess(String name, String location, ByteOrder order, boolean allowWrites) {
         super(name, location, order);
         this.allowWrites = allowWrites;
     }
 
-    MMapDataAccess cleanAndRemap(boolean cleanAndRemap) {
-        this.cleanAndRemap = cleanAndRemap;
-        return this;
-    }
-
     private void initRandomAccessFile() {
         if (raFile != null) {
             return;
@@ -92,10 +95,10 @@ public class MMapDataAccess extends AbstractDataAccess {
 
     @Override
     public boolean ensureCapacity(long bytes) {
-        return mapIt(HEADER_OFFSET, bytes, true);
+        return mapIt(HEADER_OFFSET, bytes);
     }
 
-    protected boolean mapIt(long offset, long byteCount, boolean clearNew) {
+    private boolean mapIt(long offset, long byteCount) {
         if (byteCount < 0)
             throw new IllegalArgumentException("new capacity has to be strictly positive");
 
@@ -120,20 +123,13 @@ public class MMapDataAccess extends AbstractDataAccess {
         try {
             // ugly remapping
             // http://stackoverflow.com/q/14011919/194609
-            if (cleanAndRemap) {
-                newSegments = segmentsToMap;
-                clean(0, segments.size());
-                Helper.cleanHack();
-                segments.clear();
-            } else {
-                // This approach is probably problematic but a bit faster if done often.
-                // Here we rely on the OS+file system that increasing the file 
-                // size has no effect on the old mappings!
-                bufferStart += segments.size() * longSegmentSize;
-                newSegments = segmentsToMap - segments.size();
-            }
+            // This approach is probably problematic but a bit faster if done often.
+            // Here we rely on the OS+file system that increasing the file
+            // size has no effect on the old mappings!
+            bufferStart += segments.size() * longSegmentSize;
+            newSegments = segmentsToMap - segments.size();
             // rely on automatically increasing when mapping
-//            raFile.setLength(newFileLength);
+            // raFile.setLength(newFileLength);
             for (; i < newSegments; i++) {
                 segments.add(newByteBuffer(bufferStart, longSegmentSize));
                 bufferStart += longSegmentSize;
@@ -181,18 +177,6 @@ public class MMapDataAccess extends AbstractDataAccess {
         }
 
         buf.order(byteOrder);
-
-        boolean tmp = false;
-        if (tmp) {
-            int count = (int) (byteCount / EMPTY.length);
-            for (int i = 0; i < count; i++) {
-                buf.put(EMPTY);
-            }
-            int len = (int) (byteCount % EMPTY.length);
-            if (len > 0) {
-                buf.put(EMPTY, count * EMPTY.length, len);
-            }
-        }
         return buf;
     }
 
@@ -214,7 +198,7 @@ public class MMapDataAccess extends AbstractDataAccess {
             if (byteCount < 0)
                 return false;
 
-            mapIt(HEADER_OFFSET, byteCount - HEADER_OFFSET, false);
+            mapIt(HEADER_OFFSET, byteCount - HEADER_OFFSET);
             return true;
         } catch (IOException ex) {
             throw new RuntimeException("Problem while loading " + getFullName(), ex);
@@ -265,47 +249,64 @@ public class MMapDataAccess extends AbstractDataAccess {
     public final void setInt(long bytePos, int value) {
         int bufferIndex = (int) (bytePos >> segmentSizePower);
         int index = (int) (bytePos & indexDivisor);
-        segments.get(bufferIndex).putInt(index, value);
+        ByteBuffer byteBuffer = segments.get(bufferIndex);
+        synchronized (byteBuffer) {
+            byteBuffer.putInt(index, value);
+        }
     }
 
     @Override
     public final int getInt(long bytePos) {
         int bufferIndex = (int) (bytePos >> segmentSizePower);
         int index = (int) (bytePos & indexDivisor);
-        return segments.get(bufferIndex).getInt(index);
+        ByteBuffer byteBuffer = segments.get(bufferIndex);
+        synchronized (byteBuffer) {
+            return byteBuffer.getInt(index);
+        }
     }
 
     @Override
     public final void setShort(long bytePos, short value) {
         int bufferIndex = (int) (bytePos >>> segmentSizePower);
         int index = (int) (bytePos & indexDivisor);
-        segments.get(bufferIndex).putShort(index, value);
+        ByteBuffer byteBuffer = segments.get(bufferIndex);
+        synchronized (byteBuffer) {
+            byteBuffer.putShort(index, value);
+        }
     }
 
     @Override
     public final short getShort(long bytePos) {
         int bufferIndex = (int) (bytePos >>> segmentSizePower);
         int index = (int) (bytePos & indexDivisor);
-        return segments.get(bufferIndex).getShort(index);
+        ByteBuffer byteBuffer = segments.get(bufferIndex);
+        synchronized (byteBuffer) {
+            return byteBuffer.getShort(index);
+        }
     }
 
     @Override
     public void setBytes(long bytePos, byte[] values, int length) {
         assert length <= segmentSizeInBytes : "the length has to be smaller or equal to the segment size: " + length + " vs. " + segmentSizeInBytes;
-        int bufferIndex = (int) (bytePos >>> segmentSizePower);
-        int index = (int) (bytePos & indexDivisor);
-        ByteBuffer bb = segments.get(bufferIndex);
-        bb.position(index);
-
-        int delta = index + length - segmentSizeInBytes;
+        final int bufferIndex = (int) (bytePos >>> segmentSizePower);
+        final int index = (int) (bytePos & indexDivisor);
+        final int delta = index + length - segmentSizeInBytes;
+        final ByteBuffer bb1 = segments.get(bufferIndex);
+        synchronized (bb1) {
+            bb1.position(index);
+            if (delta > 0) {
+                length -= delta;
+                bb1.put(values, 0, length);
+            } else {
+                bb1.put(values, 0, length);
+            }
+        }
         if (delta > 0) {
-            length -= delta;
-            bb.put(values, 0, length);
-            bb = segments.get(bufferIndex + 1);
-            bb.position(0);
-            bb.put(values, length, delta);
-        } else {
-            bb.put(values, 0, length);
+            final ByteBuffer bb2 = segments.get(bufferIndex + 1);
+            synchronized (bb2) {
+                bb2.position(0);
+                bb2.put(values, length, delta);
+            }
         }
     }
 
@@ -314,17 +315,23 @@ public class MMapDataAccess extends AbstractDataAccess {
         assert length <= segmentSizeInBytes : "the length has to be smaller or equal to the segment size: " + length + " vs. " + segmentSizeInBytes;
         int bufferIndex = (int) (bytePos >>> segmentSizePower);
         int index = (int) (bytePos & indexDivisor);
-        ByteBuffer bb = segments.get(bufferIndex);
-        bb.position(index);
         int delta = index + length - segmentSizeInBytes;
+        final ByteBuffer bb1 = segments.get(bufferIndex);
+        synchronized (bb1) {
+            bb1.position(index);
+            if (delta > 0) {
+                length -= delta;
+                bb1.get(values, 0, length);
+            } else {
+                bb1.get(values, 0, length);
+            }
+        }
         if (delta > 0) {
-            length -= delta;
-            bb.get(values, 0, length);
-            bb = segments.get(bufferIndex + 1);
-            bb.position(0);
-            bb.get(values, length, delta);
-        } else {
-            bb.get(values, 0, length);
+            final ByteBuffer bb2 = segments.get(bufferIndex + 1);
+            synchronized (bb2) {
+                bb2.position(0);
+                bb2.get(values, length, delta);
+            }
         }
     }
 
@@ -332,7 +339,9 @@ public class MMapDataAccess extends AbstractDataAccess {
     public long getCapacity() {
         long cap = 0;
         for (ByteBuffer bb : segments) {
-            cap += bb.capacity();
+            synchronized (bb) {
+                cap += bb.capacity();
+            }
         }
         return cap;
     }
@@ -370,7 +379,7 @@ public class MMapDataAccess extends AbstractDataAccess {
 
         clean(remainingSegNo, segments.size());
         Helper.cleanHack();
-        segments = new ArrayList<ByteBuffer>(segments.subList(0, remainingSegNo));
+        segments = new ArrayList<>(segments.subList(0, remainingSegNo));
 
         try {
             // windows does not allow changing the length of an open files
@@ -383,18 +392,6 @@ public class MMapDataAccess extends AbstractDataAccess {
         }
     }
 
-    boolean releaseSegment(int segNumber) {
-        ByteBuffer segment = segments.get(segNumber);
-        if (segment instanceof MappedByteBuffer) {
-            ((MappedByteBuffer) segment).force();
-        }
-
-        Helper.cleanMappedByteBuffer(segment);
-        segments.set(segNumber, null);
-        Helper.cleanHack();
-        return true;
-    }
-
     @Override
     public void rename(String newName) {
         if (!checkBeforeRename(newName)) {
diff --git a/core/src/main/java/com/graphhopper/storage/SynchedDAWrapper.java b/core/src/main/java/com/graphhopper/storage/SynchedDAWrapper.java
deleted file mode 100644
index 0c257ce22..000000000
--- a/core/src/main/java/com/graphhopper/storage/SynchedDAWrapper.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for 
- *  additional information regarding copyright ownership.
- * 
- *  GraphHopper GmbH licenses this file to you under the Apache License, 
- *  Version 2.0 (the "License"); you may not use this file except in 
- *  compliance with the License. You may obtain a copy of the License at
- * 
- *       http://www.apache.org/licenses/LICENSE-2.0
- * 
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-package com.graphhopper.storage;
-
-/**
- * A simple wrapper to synchronize every DataAccess object.
- * <p>
- *
- * @author Peter Karich
- */
-class SynchedDAWrapper implements DataAccess {
-    private final DataAccess inner;
-    private final DAType type;
-
-    public SynchedDAWrapper(DataAccess inner) {
-        this.inner = inner;
-        this.type = new DAType(inner.getType(), true);
-    }
-
-    @Override
-    public synchronized String getName() {
-        return inner.getName();
-    }
-
-    @Override
-    public synchronized void rename(String newName) {
-        inner.rename(newName);
-    }
-
-    @Override
-    public synchronized void setInt(long bytePos, int value) {
-        inner.setInt(bytePos, value);
-    }
-
-    @Override
-    public synchronized int getInt(long bytePos) {
-        return inner.getInt(bytePos);
-    }
-
-    @Override
-    public synchronized void setShort(long bytePos, short value) {
-        inner.setShort(bytePos, value);
-    }
-
-    @Override
-    public synchronized short getShort(long bytePos) {
-        return inner.getShort(bytePos);
-    }
-
-    @Override
-    public synchronized void setBytes(long bytePos, byte[] values, int length) {
-        inner.setBytes(bytePos, values, length);
-    }
-
-    @Override
-    public synchronized void getBytes(long bytePos, byte[] values, int length) {
-        inner.getBytes(bytePos, values, length);
-    }
-
-    @Override
-    public synchronized void setHeader(int bytePos, int value) {
-        inner.setHeader(bytePos, value);
-    }
-
-    @Override
-    public synchronized int getHeader(int bytePos) {
-        return inner.getHeader(bytePos);
-    }
-
-    @Override
-    public synchronized DataAccess create(long bytes) {
-        return inner.create(bytes);
-    }
-
-    @Override
-    public synchronized boolean ensureCapacity(long bytes) {
-        return inner.ensureCapacity(bytes);
-    }
-
-    @Override
-    public synchronized void trimTo(long bytes) {
-        inner.trimTo(bytes);
-    }
-
-    @Override
-    public synchronized DataAccess copyTo(DataAccess da) {
-        return inner.copyTo(da);
-    }
-
-    @Override
-    public synchronized DataAccess setSegmentSize(int bytes) {
-        return inner.setSegmentSize(bytes);
-    }
-
-    @Override
-    public synchronized int getSegmentSize() {
-        return inner.getSegmentSize();
-    }
-
-    @Override
-    public synchronized int getSegments() {
-        return inner.getSegments();
-    }
-
-    @Override
-    public synchronized boolean loadExisting() {
-        return inner.loadExisting();
-    }
-
-    @Override
-    public synchronized void flush() {
-        inner.flush();
-    }
-
-    @Override
-    public synchronized void close() {
-        inner.close();
-    }
-
-    @Override
-    public boolean isClosed() {
-        return inner.isClosed();
-    }
-
-    @Override
-    public synchronized long getCapacity() {
-        return inner.getCapacity();
-    }
-
-    @Override
-    public DAType getType() {
-        return type;
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/util/Helper.java b/core/src/main/java/com/graphhopper/util/Helper.java
index b6c3178c5..dd1ab0e2c 100644
--- a/core/src/main/java/com/graphhopper/util/Helper.java
+++ b/core/src/main/java/com/graphhopper/util/Helper.java
@@ -28,6 +28,7 @@ import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
 import java.nio.MappedByteBuffer;
@@ -388,21 +389,18 @@ public class Helper {
                         }
                     }
 
-                    // <=JDK8 class DirectByteBuffer { sun.misc.Cleaner cleaner(Buffer buf) }
-                    //        then call sun.misc.Cleaner.clean
-                    try {
-                        if (buffer.getClass().getSimpleName().equals("MappedByteBufferAdapter")) {
-                            if (!Constants.ANDROID)
-                                throw new RuntimeException("MappedByteBufferAdapter only supported for Android at the moment");
-
-                            // Regarding MappedByteBufferAdapter on Android 4.1, see #914
-                            final Class<?> directByteBufferClass = Class.forName("java.nio.MappedByteBufferAdapter");
-                            final Method dbbFreeMethod = directByteBufferClass.getMethod("free");
-                            dbbFreeMethod.setAccessible(true);
-                            // call: ((MappedByteBufferAdapter)buffer).free()
-                            dbbFreeMethod.invoke(buffer);
-                        } else {
-                            final Class<?> directByteBufferClass = Class.forName("java.nio.DirectByteBuffer");
+                    if (buffer.getClass().getSimpleName().equals("MappedByteBufferAdapter")) {
+                        if (!Constants.ANDROID)
+                            throw new RuntimeException("MappedByteBufferAdapter only supported for Android at the moment");
+
+                        // For Android 4.1 call ((MappedByteBufferAdapter)buffer).free() see #914
+                        Class<?> directByteBufferClass = Class.forName("java.nio.MappedByteBufferAdapter");
+                        callBufferFree(buffer, directByteBufferClass);
+                    } else {
+                        // <=JDK8 class DirectByteBuffer { sun.misc.Cleaner cleaner(Buffer buf) }
+                        //        then call sun.misc.Cleaner.clean
+                        final Class<?> directByteBufferClass = Class.forName("java.nio.DirectByteBuffer");
+                        try {
                             final Method dbbCleanerMethod = directByteBufferClass.getMethod("cleaner");
                             dbbCleanerMethod.setAccessible(true);
                             // call: cleaner = ((DirectByteBuffer)buffer).cleaner()
@@ -414,11 +412,16 @@ public class Helper {
                                 // call: ((sun.misc.Cleaner)cleaner).clean()
                                 cleanMethod.invoke(cleaner);
                             }
+                        } catch (NoSuchMethodException ex2) {
+                            if (Constants.ANDROID)
+                                // For Android 5.1.1 call ((DirectByteBuffer)buffer).free() see #933
+                                callBufferFree(buffer, directByteBufferClass);
+                            else
+                                // ignore if method cleaner or clean is not available
+                                LOGGER.warn("NoSuchMethodException | " + Constants.JAVA_VERSION, ex2);
                         }
-                    } catch (NoSuchMethodException ex2) {
-                        // ignore if method cleaner or clean is not available
-                        LOGGER.warn("NoSuchMethodException | " + Constants.JAVA_VERSION, ex2);
                     }
+
                     return null;
                 }
             });
@@ -427,6 +430,17 @@ public class Helper {
         }
     }
 
+    private static void callBufferFree(ByteBuffer buffer, Class<?> directByteBufferClass)
+            throws InvocationTargetException, IllegalAccessException {
+        try {
+            final Method dbbFreeMethod = directByteBufferClass.getMethod("free");
+            dbbFreeMethod.setAccessible(true);
+            dbbFreeMethod.invoke(buffer);
+        } catch (NoSuchMethodException ex2) {
+            LOGGER.warn("NoSuchMethodException | " + Constants.JAVA_VERSION, ex2);
+        }
+    }
+
     /**
      * Trying to force the release of the mapped ByteBuffer. See
      * http://stackoverflow.com/q/2972986/194609 and use only if you know what you are doing.
diff --git a/core/src/main/resources/com/graphhopper/util/ar.txt b/core/src/main/resources/com/graphhopper/util/ar.txt
index 249f18341..7902c30c4 100644
--- a/core/src/main/resources/com/graphhopper/util/ar.txt
+++ b/core/src/main/resources/com/graphhopper/util/ar.txt
@@ -50,6 +50,7 @@ roundabout_exit=في الدوران ، أتخذ مخرج   %1$s
 roundabout_exit_onto=في الدوران ، أتخذ مخرج   %1$s من خلال %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/ast.txt b/core/src/main/resources/com/graphhopper/util/ast.txt
index 644cb329c..e6aa25677 100644
--- a/core/src/main/resources/com/graphhopper/util/ast.txt
+++ b/core/src/main/resources/com/graphhopper/util/ast.txt
@@ -50,6 +50,7 @@ roundabout_exit=Na rotonda, toma la salida %1$s
 roundabout_exit_onto=Na rotonda, toma la salida %1$s haza %2$s
 total_ascend=%1$s d'ascensu total
 total_descend=%1$s de descensu total
+way_contains_ford=
 web.start_label=Principiu
 web.intermediate_label=Intermediu
 web.end_label=Final
diff --git a/core/src/main/resources/com/graphhopper/util/bg.txt b/core/src/main/resources/com/graphhopper/util/bg.txt
index 9a419d78d..a2b71c955 100644
--- a/core/src/main/resources/com/graphhopper/util/bg.txt
+++ b/core/src/main/resources/com/graphhopper/util/bg.txt
@@ -50,6 +50,7 @@ roundabout_exit=На кръговото кръстовище използвай
 roundabout_exit_onto=На кръговото кръстовище използвай изход %1$s по %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/ca.txt b/core/src/main/resources/com/graphhopper/util/ca.txt
index 9438eabf8..4049e28bb 100644
--- a/core/src/main/resources/com/graphhopper/util/ca.txt
+++ b/core/src/main/resources/com/graphhopper/util/ca.txt
@@ -50,6 +50,7 @@ roundabout_exit=A la rotonda, agafa la %1$s sortida
 roundabout_exit_onto=A la rotonda, agafa la sortida %1$s cap a %2$s
 total_ascend=%1$s pujada total
 total_descend=%1$s baixada total
+way_contains_ford=
 web.start_label=Punt d'inici
 web.intermediate_label=Punt intermig
 web.end_label=Punt final
diff --git a/core/src/main/resources/com/graphhopper/util/cs_CZ.txt b/core/src/main/resources/com/graphhopper/util/cs_CZ.txt
index 944b00816..b359aec53 100644
--- a/core/src/main/resources/com/graphhopper/util/cs_CZ.txt
+++ b/core/src/main/resources/com/graphhopper/util/cs_CZ.txt
@@ -50,6 +50,7 @@ roundabout_exit=Na kruhovém objezdu použijte %1$s. výjezd
 roundabout_exit_onto=Na kruhovém objezdu použijte %1$s. výjezd, směrem na %2$s
 total_ascend=Celkové stoupání %1$s
 total_descend=Celkové klesání %1$s
+way_contains_ford=
 web.start_label=Start
 web.intermediate_label=Zastávka
 web.end_label=Cíl
diff --git a/core/src/main/resources/com/graphhopper/util/da_DK.txt b/core/src/main/resources/com/graphhopper/util/da_DK.txt
index 60293ba81..13bd5f222 100644
--- a/core/src/main/resources/com/graphhopper/util/da_DK.txt
+++ b/core/src/main/resources/com/graphhopper/util/da_DK.txt
@@ -50,6 +50,7 @@ roundabout_exit=I rundkørslen, tag udkørsel %1$s
 roundabout_exit_onto=I rundskørslen, ta udkørsel %1$s ind på %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/de_DE.txt b/core/src/main/resources/com/graphhopper/util/de_DE.txt
index 1b35c214a..1d9acd2e9 100644
--- a/core/src/main/resources/com/graphhopper/util/de_DE.txt
+++ b/core/src/main/resources/com/graphhopper/util/de_DE.txt
@@ -50,6 +50,7 @@ roundabout_exit=Im Kreisverkehr Ausfahrt %1$s nehmen
 roundabout_exit_onto=Im Kreisverkehr Ausfahrt %1$s auf %2$s nehmen
 total_ascend=%1$s Gesamtaufstieg
 total_descend=%1$s Gesamtabstieg
+way_contains_ford=es liegt eine Furt auf dem Weg
 web.start_label=Start
 web.intermediate_label=Zwischenziel
 web.end_label=Ziel
diff --git a/core/src/main/resources/com/graphhopper/util/el.txt b/core/src/main/resources/com/graphhopper/util/el.txt
index 429a2e426..b5339804d 100644
--- a/core/src/main/resources/com/graphhopper/util/el.txt
+++ b/core/src/main/resources/com/graphhopper/util/el.txt
@@ -50,6 +50,7 @@ roundabout_exit=Στον κυκλικό κόμβο βγείτε στην έξο
 roundabout_exit_onto=Στον κυκλικό κόμβο βγείτε στην έξοδο %1$s στην %2$s
 total_ascend=%1$s συνολική ανάβαση
 total_descend=%1$s συνολική κατάβαση
+way_contains_ford=
 web.start_label=Αφετηρία
 web.intermediate_label=Ενδιάμεσο σημείο
 web.end_label=Προορισμός
diff --git a/core/src/main/resources/com/graphhopper/util/en_US.txt b/core/src/main/resources/com/graphhopper/util/en_US.txt
index 8532012f6..16926d5f9 100644
--- a/core/src/main/resources/com/graphhopper/util/en_US.txt
+++ b/core/src/main/resources/com/graphhopper/util/en_US.txt
@@ -50,6 +50,7 @@ roundabout_exit=At roundabout, take exit %1$s
 roundabout_exit_onto=At roundabout, take exit %1$s onto %2$s
 total_ascend=%1$s total ascend
 total_descend=%1$s total descend
+way_contains_ford=there is ford along the way
 web.start_label=Start
 web.intermediate_label=Intermediate
 web.end_label=End
diff --git a/core/src/main/resources/com/graphhopper/util/es.txt b/core/src/main/resources/com/graphhopper/util/es.txt
index 5a149d410..c686c2f9d 100644
--- a/core/src/main/resources/com/graphhopper/util/es.txt
+++ b/core/src/main/resources/com/graphhopper/util/es.txt
@@ -50,6 +50,7 @@ roundabout_exit=En la rotonda, toma la %1$sª salida
 roundabout_exit_onto=En la rotonda, toma la %1$sª salida hacia %2$s
 total_ascend=Ascender %1$s en total
 total_descend=Descender %1$s en total
+way_contains_ford=
 web.start_label=Punto de partida
 web.intermediate_label=Punto intermedio
 web.end_label=Punto de finalización
diff --git a/core/src/main/resources/com/graphhopper/util/fa.txt b/core/src/main/resources/com/graphhopper/util/fa.txt
index 539ea0a66..4915db24d 100644
--- a/core/src/main/resources/com/graphhopper/util/fa.txt
+++ b/core/src/main/resources/com/graphhopper/util/fa.txt
@@ -50,6 +50,7 @@ roundabout_exit=در میدان خروجی  %1$s را انتخاب کنید
 roundabout_exit_onto=در میدان خروجی  %1$s به %2$s را انتخاب کنید
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/fi.txt b/core/src/main/resources/com/graphhopper/util/fi.txt
index 17f101a93..c6f4aca51 100644
--- a/core/src/main/resources/com/graphhopper/util/fi.txt
+++ b/core/src/main/resources/com/graphhopper/util/fi.txt
@@ -50,6 +50,7 @@ roundabout_exit=Liikenneympyrästä poistu %1$s. liittymästä
 roundabout_exit_onto=Liikenneympyrästä poistu %1$s. liittymästä suuntaan %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/fil.txt b/core/src/main/resources/com/graphhopper/util/fil.txt
index 113b43b95..9a80cadf4 100644
--- a/core/src/main/resources/com/graphhopper/util/fil.txt
+++ b/core/src/main/resources/com/graphhopper/util/fil.txt
@@ -50,6 +50,7 @@ roundabout_exit=Sa rotonda, lumabas sa exit %1$s
 roundabout_exit_onto=Sa rotonda, lumabas sa exit papunta %1$s %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/fr_CH.txt b/core/src/main/resources/com/graphhopper/util/fr_CH.txt
index d214de42d..9f0619172 100644
--- a/core/src/main/resources/com/graphhopper/util/fr_CH.txt
+++ b/core/src/main/resources/com/graphhopper/util/fr_CH.txt
@@ -50,6 +50,7 @@ roundabout_exit=Au giratoire, prenez la %1$s sortie
 roundabout_exit_onto=Au giratoire, prenez la %1$s sortie vers %2$s
 total_ascend=%1$s de dénivelé positif
 total_descend=%1$s de dénivelé négatif
+way_contains_ford=
 web.start_label=Départ
 web.intermediate_label=Point intermédiaire
 web.end_label=Arrivée
diff --git a/core/src/main/resources/com/graphhopper/util/fr_FR.txt b/core/src/main/resources/com/graphhopper/util/fr_FR.txt
index 4387cb8ba..834da5f6f 100644
--- a/core/src/main/resources/com/graphhopper/util/fr_FR.txt
+++ b/core/src/main/resources/com/graphhopper/util/fr_FR.txt
@@ -50,6 +50,7 @@ roundabout_exit=Au rond-point, prenez la %1$s sortie
 roundabout_exit_onto=Au rond-point, prenez la %1$s sortie vers %2$s
 total_ascend=%1$s de dénivelé positif
 total_descend=%1$s de dénivelé négatif
+way_contains_ford=
 web.start_label=Départ
 web.intermediate_label=Point intermédiaire
 web.end_label=Arrivée
diff --git a/core/src/main/resources/com/graphhopper/util/gl.txt b/core/src/main/resources/com/graphhopper/util/gl.txt
index 571542efc..946bb68e6 100644
--- a/core/src/main/resources/com/graphhopper/util/gl.txt
+++ b/core/src/main/resources/com/graphhopper/util/gl.txt
@@ -50,6 +50,7 @@ roundabout_exit=Na rotonda tome a saída %1$s
 roundabout_exit_onto=Na rotonda, tome a saída %1$s cara %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/he.txt b/core/src/main/resources/com/graphhopper/util/he.txt
index 22741dda4..4f7d5e0c8 100644
--- a/core/src/main/resources/com/graphhopper/util/he.txt
+++ b/core/src/main/resources/com/graphhopper/util/he.txt
@@ -50,6 +50,7 @@ roundabout_exit=בכיכר, יש לצאת ביציאה %1$s
 roundabout_exit_onto=בכיכר, יש לצאת ביציאה %1$s לתוך %2$s
 total_ascend=עלייה כוללת של %1$s
 total_descend=ירידה כוללת של %1$s
+way_contains_ford=
 web.start_label=התחלה
 web.intermediate_label=אמצע
 web.end_label=סוף
diff --git a/core/src/main/resources/com/graphhopper/util/hr_HR.txt b/core/src/main/resources/com/graphhopper/util/hr_HR.txt
index acef1b8ee..61a238d20 100644
--- a/core/src/main/resources/com/graphhopper/util/hr_HR.txt
+++ b/core/src/main/resources/com/graphhopper/util/hr_HR.txt
@@ -50,6 +50,7 @@ roundabout_exit=Sa kružnog toka izađite na izlaz %1$s
 roundabout_exit_onto=Sa kružnog toka izađite na izlaz %1$s na %2$s
 total_ascend=%1$s ukupni uspon
 total_descend=
+way_contains_ford=
 web.start_label=Početak
 web.intermediate_label=Posrednik
 web.end_label=Kraj
diff --git a/core/src/main/resources/com/graphhopper/util/hsb.txt b/core/src/main/resources/com/graphhopper/util/hsb.txt
index 478391c68..9a02ce531 100644
--- a/core/src/main/resources/com/graphhopper/util/hsb.txt
+++ b/core/src/main/resources/com/graphhopper/util/hsb.txt
@@ -50,6 +50,7 @@ roundabout_exit=we kružnym wobchadźe %1$s. wujězd wzać
 roundabout_exit_onto=we kružnym wobchadźe %1$s. wujězd na %2$s wzać
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/hu_HU.txt b/core/src/main/resources/com/graphhopper/util/hu_HU.txt
index f0d9f80b2..27c916d24 100644
--- a/core/src/main/resources/com/graphhopper/util/hu_HU.txt
+++ b/core/src/main/resources/com/graphhopper/util/hu_HU.txt
@@ -50,6 +50,7 @@ roundabout_exit=Hagyja el a körförgalmat, kihajtó %1$s
 roundabout_exit_onto=Hagyja el a körförgalmat, kihajtó %1$s, aztán hajtson rá %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/it.txt b/core/src/main/resources/com/graphhopper/util/it.txt
index 1d56fe1fe..1840a5397 100644
--- a/core/src/main/resources/com/graphhopper/util/it.txt
+++ b/core/src/main/resources/com/graphhopper/util/it.txt
@@ -50,6 +50,7 @@ roundabout_exit=Nella rotatoria, prendere l'uscita %1$s
 roundabout_exit_onto=Nella rotatoria, prendere l'uscita %1$s su %2$s
 total_ascend=%1$s di dislivello positivo
 total_descend=%1$s di dislivello negativo
+way_contains_ford=
 web.start_label=Partenza
 web.intermediate_label=Punto intermedio
 web.end_label=Arrivo
diff --git a/core/src/main/resources/com/graphhopper/util/ja.txt b/core/src/main/resources/com/graphhopper/util/ja.txt
index c2d5239dd..58cd3aa1d 100644
--- a/core/src/main/resources/com/graphhopper/util/ja.txt
+++ b/core/src/main/resources/com/graphhopper/util/ja.txt
@@ -50,6 +50,7 @@ roundabout_exit=円形交差点の出口%1$sへ
 roundabout_exit_onto=円形交差点の出口%1$sから%2$sへ
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/ko.txt b/core/src/main/resources/com/graphhopper/util/ko.txt
index f2ffb0c84..63dab9af2 100644
--- a/core/src/main/resources/com/graphhopper/util/ko.txt
+++ b/core/src/main/resources/com/graphhopper/util/ko.txt
@@ -50,6 +50,7 @@ roundabout_exit=로터리에서 %1$s 진출
 roundabout_exit_onto=로터리에서 %1$s 진출 후 %2$s 이동
 total_ascend=%1$s 오르막길
 total_descend=%1$s 내리막길
+way_contains_ford=
 web.start_label=출발
 web.intermediate_label=경유
 web.end_label=도착
diff --git a/core/src/main/resources/com/graphhopper/util/lt_LT.txt b/core/src/main/resources/com/graphhopper/util/lt_LT.txt
index dd542bb15..70a06cf34 100644
--- a/core/src/main/resources/com/graphhopper/util/lt_LT.txt
+++ b/core/src/main/resources/com/graphhopper/util/lt_LT.txt
@@ -50,6 +50,7 @@ roundabout_exit=Žiede išvažiuokite %1$s išvažiavime
 roundabout_exit_onto=Žiede išvažiuokite %1$s išvažiavime į  %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/ne.txt b/core/src/main/resources/com/graphhopper/util/ne.txt
index 3f0ff0555..4b35cdc61 100644
--- a/core/src/main/resources/com/graphhopper/util/ne.txt
+++ b/core/src/main/resources/com/graphhopper/util/ne.txt
@@ -50,6 +50,7 @@ roundabout_exit=घुम्तीमा %1$s नम्बर को मोड
 roundabout_exit_onto=घुम्तीमा %1$s नम्बर को मोडबाट निस्केर %2$s मा जानुहोस
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/nl.txt b/core/src/main/resources/com/graphhopper/util/nl.txt
index e28c76528..d8941962d 100644
--- a/core/src/main/resources/com/graphhopper/util/nl.txt
+++ b/core/src/main/resources/com/graphhopper/util/nl.txt
@@ -50,6 +50,7 @@ roundabout_exit=neem afslag %1$s op de rotonde
 roundabout_exit_onto=neem afslag %1$s naar %2$s op de rotonde 
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/pl_PL.txt b/core/src/main/resources/com/graphhopper/util/pl_PL.txt
index 6707732bc..c1f7a9d58 100644
--- a/core/src/main/resources/com/graphhopper/util/pl_PL.txt
+++ b/core/src/main/resources/com/graphhopper/util/pl_PL.txt
@@ -50,6 +50,7 @@ roundabout_exit=Zjedź z ronda %1$s zjazdem
 roundabout_exit_onto=Zjedź z ronda %1$s zjazdem na %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/pt_BR.txt b/core/src/main/resources/com/graphhopper/util/pt_BR.txt
index c29644070..6368daa72 100644
--- a/core/src/main/resources/com/graphhopper/util/pt_BR.txt
+++ b/core/src/main/resources/com/graphhopper/util/pt_BR.txt
@@ -50,6 +50,7 @@ roundabout_exit=Na rotatória, saia na %1$s saída
 roundabout_exit_onto=Na rotatória, saia na %1$s saida em direção a %2$s
 total_ascend=subida de %1$s
 total_descend=descida de %1$s
+way_contains_ford=
 web.start_label=Inicio
 web.intermediate_label=Intermediário
 web.end_label=Fim
diff --git a/core/src/main/resources/com/graphhopper/util/pt_PT.txt b/core/src/main/resources/com/graphhopper/util/pt_PT.txt
index a1510cf6d..538b57bbd 100644
--- a/core/src/main/resources/com/graphhopper/util/pt_PT.txt
+++ b/core/src/main/resources/com/graphhopper/util/pt_PT.txt
@@ -50,6 +50,7 @@ roundabout_exit=Na rotunda, saia na %1$s saída
 roundabout_exit_onto=Na rotunda, saia na %1$s saida em direção a %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/ro.txt b/core/src/main/resources/com/graphhopper/util/ro.txt
index f5793e5d4..c82cbad72 100644
--- a/core/src/main/resources/com/graphhopper/util/ro.txt
+++ b/core/src/main/resources/com/graphhopper/util/ro.txt
@@ -50,6 +50,7 @@ roundabout_exit=În sensul giratoriu folosiți ieșirea %1$s
 roundabout_exit_onto=În sensul giratoriu folosiți ieșirea %1$s către %2$s
 total_ascend=urcare %1$s
 total_descend=coborâre %1$s
+way_contains_ford=
 web.start_label=Pornire
 web.intermediate_label=Intermediar
 web.end_label=Sfârșit
diff --git a/core/src/main/resources/com/graphhopper/util/ru.txt b/core/src/main/resources/com/graphhopper/util/ru.txt
index 85119e60d..dfc676dca 100644
--- a/core/src/main/resources/com/graphhopper/util/ru.txt
+++ b/core/src/main/resources/com/graphhopper/util/ru.txt
@@ -50,6 +50,7 @@ roundabout_exit=У кольца используйте съезд %1$s
 roundabout_exit_onto=У кольца используйте съезд %1$s на %2$s
 total_ascend=подъём на %1$s
 total_descend=спуск на %1$s
+way_contains_ford=
 web.start_label=Начало
 web.intermediate_label=Промежуточная точка
 web.end_label=Конец
diff --git a/core/src/main/resources/com/graphhopper/util/sk.txt b/core/src/main/resources/com/graphhopper/util/sk.txt
index 034ccb97a..4d4319e2f 100644
--- a/core/src/main/resources/com/graphhopper/util/sk.txt
+++ b/core/src/main/resources/com/graphhopper/util/sk.txt
@@ -50,6 +50,7 @@ roundabout_exit=Na kruhovom objazde, ho opustite cez %1$s. výjazd
 roundabout_exit_onto=Na kruhovom objazde, ho opustite cez %1$s. výjazd na %2$s
 total_ascend=%1$s celkové stúpanie
 total_descend=%1$s celkové klesanie
+way_contains_ford=
 web.start_label=Začiatok
 web.intermediate_label=Bod trasy
 web.end_label=Koniec
diff --git a/core/src/main/resources/com/graphhopper/util/sl_SI.txt b/core/src/main/resources/com/graphhopper/util/sl_SI.txt
index 72b9eccd8..8c6306300 100644
--- a/core/src/main/resources/com/graphhopper/util/sl_SI.txt
+++ b/core/src/main/resources/com/graphhopper/util/sl_SI.txt
@@ -50,6 +50,7 @@ roundabout_exit=V krožišču izberite izhod %1$s
 roundabout_exit_onto=V krožišču izberite izhod %1$s na %2$s
 total_ascend=Skupni vzpon: %1$s
 total_descend=Skupni spust: %1$s
+way_contains_ford=
 web.start_label=Začetna točka
 web.intermediate_label=Vmesna točka
 web.end_label=Končna točka
diff --git a/core/src/main/resources/com/graphhopper/util/sv_SE.txt b/core/src/main/resources/com/graphhopper/util/sv_SE.txt
index 6927d6ac8..03c70143d 100644
--- a/core/src/main/resources/com/graphhopper/util/sv_SE.txt
+++ b/core/src/main/resources/com/graphhopper/util/sv_SE.txt
@@ -50,6 +50,7 @@ roundabout_exit=I rondellen, ta avfart %1$s
 roundabout_exit_onto=I rondellen, ta avfart %1$s in på %2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/tr.txt b/core/src/main/resources/com/graphhopper/util/tr.txt
index 8991b1ba9..f07ccdddb 100644
--- a/core/src/main/resources/com/graphhopper/util/tr.txt
+++ b/core/src/main/resources/com/graphhopper/util/tr.txt
@@ -50,6 +50,7 @@ roundabout_exit=kavşaktan %1$s cıkışa gir
 roundabout_exit_onto=kavşaktan %1$s cıkıştan %2$s sokağa gir
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/uk.txt b/core/src/main/resources/com/graphhopper/util/uk.txt
index 957a3ee90..b28ca04da 100644
--- a/core/src/main/resources/com/graphhopper/util/uk.txt
+++ b/core/src/main/resources/com/graphhopper/util/uk.txt
@@ -50,6 +50,7 @@ roundabout_exit=На кільці використовуйте з’їзд %1$s
 roundabout_exit_onto=На кільці використовуйте з’їзд %1$s на %2$s
 total_ascend=%1$s загалом підйому
 total_descend=%1$s загалом спуску
+way_contains_ford=
 web.start_label=Початок
 web.intermediate_label=Проміжна точка
 web.end_label=Кінець
diff --git a/core/src/main/resources/com/graphhopper/util/vi_VI.txt b/core/src/main/resources/com/graphhopper/util/vi_VI.txt
index 8eec781c1..b6c10f232 100644
--- a/core/src/main/resources/com/graphhopper/util/vi_VI.txt
+++ b/core/src/main/resources/com/graphhopper/util/vi_VI.txt
@@ -50,6 +50,7 @@ roundabout_exit=Tại vòng xoay, rẽ lối rẽ %1$s
 roundabout_exit_onto=Tại vòng xoay, rẽ lối rẽ %1$s vào đường %2$s
 total_ascend=Đi tiếp %1$s nữa
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/zh_CN.txt b/core/src/main/resources/com/graphhopper/util/zh_CN.txt
index 6703e1418..3ec087b88 100644
--- a/core/src/main/resources/com/graphhopper/util/zh_CN.txt
+++ b/core/src/main/resources/com/graphhopper/util/zh_CN.txt
@@ -50,6 +50,7 @@ roundabout_exit=在环岛内，使用%1$s出口出环岛
 roundabout_exit_onto=在环岛内，使用%1$s出口出环岛，进入%2$s
 total_ascend=
 total_descend=
+way_contains_ford=
 web.start_label=
 web.intermediate_label=
 web.end_label=
diff --git a/core/src/main/resources/com/graphhopper/util/zh_HK.txt b/core/src/main/resources/com/graphhopper/util/zh_HK.txt
index 38d613980..7f511c8f0 100644
--- a/core/src/main/resources/com/graphhopper/util/zh_HK.txt
+++ b/core/src/main/resources/com/graphhopper/util/zh_HK.txt
@@ -50,6 +50,7 @@ roundabout_exit=使用 %1$s 出口離開迴旋處
 roundabout_exit_onto=使用 %1$s 出口離開迴旋處到 %2$s 
 total_ascend=總共上昇 %1$s
 total_descend=總共下降 %1$s
+way_contains_ford=
 web.start_label=起點
 web.intermediate_label=途經點
 web.end_label=目的地


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java
index 79d61afce..66032dba8 100644
--- a/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java
@@ -7,11 +7,9 @@ import com.graphhopper.json.geo.Geometry;
 import com.graphhopper.json.geo.JsonFeature;
 import com.graphhopper.json.geo.JsonFeatureCollection;
 import com.graphhopper.routing.AbstractRoutingAlgorithmTester;
-import com.graphhopper.routing.util.spatialrules.countries.AustriaSpatialRule;
-import com.graphhopper.util.PMap;
-import org.junit.Ignore;
 import com.graphhopper.routing.util.spatialrules.*;
 import com.graphhopper.routing.util.spatialrules.countries.GermanySpatialRule;
+import com.graphhopper.util.*;
 import com.graphhopper.util.shapes.BBox;
 import com.graphhopper.util.shapes.GHPoint;
 import org.junit.Test;
@@ -19,9 +17,6 @@ import org.junit.Test;
 import com.graphhopper.reader.ReaderWay;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.GraphBuilder;
-import com.graphhopper.util.EdgeIteratorState;
-import com.graphhopper.util.GHUtility;
-import com.graphhopper.util.Helper;
 
 import static org.junit.Assert.*;
 
@@ -137,6 +132,18 @@ public class DataFlagEncoderTest {
         assertTrue(encoder.isTransportModeBridge(edge));
     }
 
+    @Test
+    public void testFord() {
+        ReaderWay osmWay = new ReaderWay(0);
+        osmWay.setTag("highway", "unclassified");
+        osmWay.setTag("ford", "yes");
+        long flags = encoder.handleWayTags(osmWay, 1, 0);
+        EdgeIteratorState edge = GHUtility.createMockedEdgeIteratorState(0, flags);
+        assertEquals("ford", encoder.getTransportModeAsString(edge));
+        assertTrue(encoder.isTransportModeFord(edge.getFlags()));
+        assertTrue(encoder.getAnnotation(edge.getFlags(), TranslationMapTest.SINGLETON.get("en")).getMessage().contains("ford"));
+    }
+
     @Test
     public void testHighwaySpeed() {
         Map<String, Double> map = new LinkedHashMap<>();
diff --git a/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java b/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java
index 61472da13..23159c24e 100644
--- a/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java
+++ b/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java
@@ -25,6 +25,7 @@ import org.junit.Test;
 import java.io.File;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -48,26 +49,6 @@ public abstract class AbstractDirectoryTester {
         Helper.removeDir(new File(location));
     }
 
-    @Test
-    public void testRequestedDataAccessHasToBeTheIdenticalType() {
-        Directory dir = createDir();
-        da = dir.find("testing", new DAType(DAType.MemRef.HEAP, false, false, false, false));
-        try {
-            dir.find("testing", new DAType(DAType.MemRef.HEAP, false, false, false, true));
-            assertFalse(true);
-        } catch (Exception ex) {
-        }
-    }
-
-    @Test
-    public void testSynched() {
-        Directory dir = createDir();
-        DataAccess da1 = dir.find("testing", new DAType(DAType.MemRef.HEAP, false, false, true, false));
-        da = dir.find("testing_synched", new DAType(DAType.MemRef.HEAP, false, false, true, true));
-        assertFalse(da.getClass() == da1.getClass());
-        da1.close();
-    }
-
     @Test
     public void testNoDuplicates() {
         Directory dir = createDir();
diff --git a/core/src/test/java/com/graphhopper/storage/SynchedDAWrapperTest.java b/core/src/test/java/com/graphhopper/storage/SynchedDAWrapperTest.java
deleted file mode 100644
index 93e07a58b..000000000
--- a/core/src/test/java/com/graphhopper/storage/SynchedDAWrapperTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for 
- *  additional information regarding copyright ownership.
- * 
- *  GraphHopper GmbH licenses this file to you under the Apache License, 
- *  Version 2.0 (the "License"); you may not use this file except in 
- *  compliance with the License. You may obtain a copy of the License at
- * 
- *       http://www.apache.org/licenses/LICENSE-2.0
- * 
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-package com.graphhopper.storage;
-
-/**
- * @author Peter Karich
- */
-public class SynchedDAWrapperTest extends DataAccessTest {
-    @Override
-    public DataAccess createDataAccess(String name) {
-        return new SynchedDAWrapper(new RAMDataAccess(name, directory, true, defaultOrder)).setSegmentSize(128);
-    }
-}