Skip to content

Commit 54fd284

Browse files
authored
Properties segments (graphhopper#3129)
* Simplify property saving * Allow to store multiple segments * Allow to load multiple segments * Add test case for high amount of props * Use try-with-resources * bytePos iter * Check capacity
1 parent a2a187b commit 54fd284

File tree

3 files changed

+64
-35
lines changed

3 files changed

+64
-35
lines changed

core/src/main/java/com/graphhopper/storage/StorableProperties.java

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.slf4j.LoggerFactory;
2323

2424
import java.io.*;
25+
import java.util.Arrays;
2526
import java.util.LinkedHashMap;
2627
import java.util.Map;
2728

@@ -51,9 +52,18 @@ public synchronized boolean loadExisting() {
5152
if (!da.loadExisting())
5253
return false;
5354

55+
if (da.getCapacity() > Integer.MAX_VALUE) {
56+
throw new IllegalStateException("Properties file is too large: " + da.getCapacity());
57+
}
5458
int len = (int) da.getCapacity();
5559
byte[] bytes = new byte[len];
56-
da.getBytes(0, bytes, len);
60+
int segmentSize = da.getSegmentSize();
61+
for (int bytePos = 0; bytePos < len; bytePos += segmentSize) {
62+
int partLen = Math.min(bytes.length - bytePos, segmentSize);
63+
byte[] part = new byte[partLen];
64+
da.getBytes(bytePos, part, part.length);
65+
System.arraycopy(part, 0, bytes, bytePos, partLen);
66+
}
5767
try {
5868
loadProperties(map, new StringReader(new String(bytes, UTF_CS)));
5969
return true;
@@ -63,21 +73,23 @@ public synchronized boolean loadExisting() {
6373
}
6474

6575
public synchronized void flush() {
66-
try {
67-
StringWriter sw = new StringWriter();
68-
saveProperties(map, sw);
69-
// TODO at the moment the size is limited to da.segmentSize() !
70-
byte[] bytes = sw.toString().getBytes(UTF_CS);
71-
da.setBytes(0, bytes, bytes.length);
72-
da.flush();
73-
// todo: would not be needed if the properties file used a format that is compatible with common text tools
74-
if (dir.getDefaultType().isStoring()) {
75-
try (BufferedWriter writer = new BufferedWriter(new FileWriter(dir.getLocation() + "/properties.txt"))) {
76-
writer.write(sw.toString());
77-
}
76+
String props = saveProperties(map);
77+
byte[] bytes = props.getBytes(UTF_CS);
78+
da.ensureCapacity(bytes.length);
79+
int segmentSize = da.getSegmentSize();
80+
for (int bytePos = 0; bytePos < bytes.length; bytePos += segmentSize) {
81+
int partLen = Math.min(bytes.length - bytePos, segmentSize);
82+
byte[] part = Arrays.copyOfRange(bytes, bytePos, bytePos + partLen);
83+
da.setBytes(bytePos, part, part.length);
84+
}
85+
da.flush();
86+
// todo: would not be needed if the properties file used a format that is compatible with common text tools
87+
if (dir.getDefaultType().isStoring()) {
88+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(dir.getLocation() + "/properties.txt"))) {
89+
writer.write(props);
90+
} catch (IOException e) {
91+
throw new RuntimeException(e);
7892
}
79-
} catch (IOException ex) {
80-
throw new RuntimeException(ex);
8193
}
8294
}
8395

@@ -147,10 +159,20 @@ public synchronized String toString() {
147159
return da.toString();
148160
}
149161

162+
static String saveProperties(Map<String, String> map) {
163+
StringBuilder builder = new StringBuilder();
164+
for (Map.Entry<String, String> e : map.entrySet()) {
165+
builder.append(e.getKey());
166+
builder.append('=');
167+
builder.append(e.getValue());
168+
builder.append('\n');
169+
}
170+
return builder.toString();
171+
}
172+
150173
static void loadProperties(Map<String, String> map, Reader tmpReader) throws IOException {
151-
BufferedReader reader = new BufferedReader(tmpReader);
152-
String line;
153-
try {
174+
try (BufferedReader reader = new BufferedReader(tmpReader)) {
175+
String line;
154176
while ((line = reader.readLine()) != null) {
155177
if (line.startsWith("//") || line.startsWith("#")) {
156178
continue;
@@ -170,8 +192,6 @@ static void loadProperties(Map<String, String> map, Reader tmpReader) throws IOE
170192
String value = line.substring(index + 1);
171193
map.put(field.trim(), value.trim());
172194
}
173-
} finally {
174-
reader.close();
175195
}
176196
}
177197
}

core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ public void testStore() {
6868
Helper.removeDir(new File(dir));
6969
}
7070

71+
@Test
72+
public void testStoreLarge() {
73+
String dir = "./target/test";
74+
Helper.removeDir(new File(dir));
75+
StorableProperties instance = new StorableProperties(createDir(dir, true));
76+
instance.create(1000);
77+
for (int i = 0; i <= 100_000; i++) {
78+
instance.put(Integer.toString(i), "test." + i);
79+
}
80+
81+
instance.flush();
82+
long bytesWritten = instance.getCapacity();
83+
instance.close();
84+
85+
instance = new StorableProperties(createDir(dir, true));
86+
assertTrue(instance.loadExisting());
87+
assertEquals(bytesWritten, instance.getCapacity());
88+
assertEquals("test.0", instance.get("0"));
89+
assertEquals("test.100000", instance.get("100000"));
90+
instance.close();
91+
92+
Helper.removeDir(new File(dir));
93+
}
94+
7195
@Test
7296
public void testLoadProperties() throws IOException {
7397
Map<String, String> map = new HashMap<>();

web-api/src/main/java/com/graphhopper/util/Helper.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.text.NumberFormat;
2727
import java.text.SimpleDateFormat;
2828
import java.util.*;
29-
import java.util.Map.Entry;
3029

3130
/**
3231
* @author Peter Karich
@@ -65,20 +64,6 @@ public static String toUpperCase(String string) {
6564
return string.toUpperCase(Locale.ROOT);
6665
}
6766

68-
public static void saveProperties(Map<String, String> map, Writer tmpWriter) throws IOException {
69-
BufferedWriter writer = new BufferedWriter(tmpWriter);
70-
try {
71-
for (Entry<String, String> e : map.entrySet()) {
72-
writer.append(e.getKey());
73-
writer.append('=');
74-
writer.append(e.getValue());
75-
writer.append('\n');
76-
}
77-
} finally {
78-
writer.close();
79-
}
80-
}
81-
8267
public static String readJSONFileWithoutComments(String file) throws IOException {
8368
return Helper.readFile(file).stream().
8469
filter(line -> !line.trim().startsWith("//")).

0 commit comments

Comments
 (0)