filebot/source/net/filebot/util/prefs/PropertyFileBackingStore.java

167 lines
4.1 KiB
Java

package net.filebot.util.prefs;
import static java.nio.charset.StandardCharsets.*;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
public class PropertyFileBackingStore {
private static final char nodeSeparatorChar = '/';
private static final Pattern nodeSeparatorPattern = Pattern.compile(String.valueOf(nodeSeparatorChar), Pattern.LITERAL);
private Path store;
private int modCount = 0;
private Map<String, Map<String, String>> nodes = new HashMap<String, Map<String, String>>();
public PropertyFileBackingStore(Path store) {
this.store = store;
}
private Map<String, String> newKeyValueMap(String node) {
return new HashMap<String, String>();
}
public synchronized String setValue(String node, String key, String value) {
modCount++;
return nodes.computeIfAbsent(node, this::newKeyValueMap).put(key, value);
}
public synchronized String getValue(String node, String key) {
Map<String, String> values = nodes.get(node);
if (values != null) {
return values.get(key);
}
return null;
}
public synchronized void removeValue(String node, String key) {
Map<String, String> values = nodes.get(node);
if (values != null) {
modCount++;
values.remove(key);
}
}
public synchronized void removeNode(String node) {
modCount++;
nodes.remove(node);
}
public synchronized String[] getKeys(String node) {
Map<String, String> values = nodes.get(node);
if (values != null) {
return values.keySet().toArray(new String[0]);
}
return new String[0];
}
public synchronized String[] getChildren(String node) {
HashSet<String> keys = new HashSet<String>();
String[] path = node.isEmpty() ? new String[0] : nodeSeparatorPattern.split(node);
for (String n : nodes.keySet()) {
String[] p = nodeSeparatorPattern.split(n);
if (p.length > path.length) {
if (path.length == 0 || IntStream.range(0, path.length).allMatch(i -> p[i].equals(path[i]))) {
keys.add(p[path.length]);
}
}
}
return keys.toArray(new String[0]);
}
public synchronized Properties toProperties() {
Properties props = new Properties();
nodes.forEach((node, values) -> {
values.forEach((key, value) -> {
props.put(node + nodeSeparatorChar + key, value);
});
});
return props;
}
public synchronized void mergeNodes(Map<String, Map<String, String>> n) {
n.forEach((node, values) -> {
nodes.merge(node, values, (m1, m2) -> {
Map<String, String> m = newKeyValueMap(node);
m.putAll(m1);
m.putAll(m2);
return m;
});
});
}
public void sync() throws IOException {
if (!Files.exists(store)) {
return;
}
byte[] bytes = Files.readAllBytes(store);
StringReader buffer = new StringReader(new String(bytes, UTF_8));
Properties props = new Properties();
props.load(buffer);
Map<String, Map<String, String>> n = new HashMap<String, Map<String, String>>();
props.forEach((k, v) -> {
String propertyKey = k.toString();
int s = propertyKey.lastIndexOf(nodeSeparatorChar);
String node = propertyKey.substring(0, s);
String key = propertyKey.substring(s + 1);
n.computeIfAbsent(node, this::newKeyValueMap).put(key, v.toString());
});
mergeNodes(n);
}
public void flush() throws IOException {
if (modCount == 0) {
return;
}
StringWriter buffer = new StringWriter();
toProperties().store(buffer, null);
ByteBuffer data = UTF_8.encode(CharBuffer.wrap(buffer.getBuffer()));
try (FileChannel out = FileChannel.open(store, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
try (FileLock lock = out.lock()) {
out.write(data);
out.truncate(out.position());
}
}
modCount = 0; // reset
}
@Override
public synchronized String toString() {
return nodes.toString();
}
}