Skip to content

Instantly share code, notes, and snippets.

@lmihalkovic
Created January 15, 2016 12:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lmihalkovic/5288c576692a4fc07f23 to your computer and use it in GitHub Desktop.
Save lmihalkovic/5288c576692a4fc07f23 to your computer and use it in GitHub Desktop.
A small KeyValues store to replace the internal details of PreferencesMap and PreferencesData
package processing.app;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A {@code KeyValueStore} implementation that delegates to a default store when a
* value is not found locally. Values added to the store are added locally leaving
* the defaults untouched. This is particularly usefull for implementing a tiered
* scheme where different levels of overrides can be read from different files, while
* giving user the impression of a flat persistance store. Alternatively this can
* also be used to implement a commit/rollback scheme where changes can be stored
* locally before being merged into the backing store (usefull for a apply/cancel
* scheme that would work accross multiple screens in a preferences dialog).
*/
public class KeyValueDelegatingStore extends KeyValueStore {
private KeyValueStore defaults;
private final Map<String, String> store;
public KeyValueDelegatingStore(KeyValueStore defaults) {
store = new HashMap<>();
setDefaults(defaults);
}
public KeyValueDelegatingStore(Map<String, String> store, KeyValueStore defaults) {
this.store = store;
setDefaults(defaults);
}
protected void setDefaults(KeyValueStore defaults) {
this.defaults = defaults;
}
@Override
protected Set<String> _keys() {
Set<String> keys = new HashSet<>();
keys.addAll(defaults._keys());
keys.addAll(store.keySet());
return keys;
}
@Override
protected String _get(String key) {
if(store.containsKey(key)) return store.get(key);
return defaults.get(key);
}
@Override
protected String _put(String key, String value) {
return store.put(key, value);
}
@Override
protected boolean _has(String key) {
if(store.containsKey(key)) return true;
return defaults._has(key);
}
}
package processing.app;
import java.io.File;
import java.util.Set;
import java.util.TreeSet;
/**
* Base implementation for a simple Key/Value store. This class defines a low level
* SPI that needs to be implemented by concrete Key/Value stores. Until Java allows
* protected interface members, this is best implemented with a abstract class rather
* than using default methods on an interface.
*/
public abstract class KeyValueStore {
protected abstract Set<String> _keys();
protected abstract String _get(String key);
protected abstract String _put(String key, String value);
protected abstract boolean _has(String key);
protected int _size() { return _keys().size(); }
public String get(String key) {
return _get(key);
}
public String put(String key, String value) {
return _put(key, value);
}
public String get(String key, String defaultValue) {
String value = _get(key);
if (value != null) {
return value;
}
return defaultValue;
}
public boolean getBoolean(String key) {
return Boolean.valueOf(_get(key));
}
public boolean putBoolean(String key, boolean value) {
String prev = _put(key, value ? "true" : "false");
return Boolean.valueOf(prev);
}
public File getFile(String key) {
if (!_has(key))
return null;
String path = _get(key).trim();
if (path.length() == 0)
return null;
return new File(path);
}
public File getFile(String key, String subFolder) {
File file = getFile(key);
if (file == null)
return null;
return new File(file, subFolder);
}
public String toString(String indent, boolean sorted) {
String res = indent + "{\n";
Set<String> keys = sorted ? new TreeSet<String>(_keys()) : _keys();
for (String k : keys)
res += indent + k + " = " + _get(k) + "\n";
res += indent + "}";
return res;
}
public String toString(String indent) {
return toString(indent, false);
}
@Override
public String toString() {
return toString("", false);
}
public int size() {
return _size();
}
}
package processing.app;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiFunction;
import processing.app.helpers.OSUtils;
public class KeyValueStores {
private KeyValueStores() { /* CANNOT CREATE */ }
private static final boolean isLinux = OSUtils.isLinux();
private static final boolean isWindows = OSUtils.isWindows();
private static final boolean isMacOS = OSUtils.isMacOS();
/*private*/ static final BiFunction<String, String, String> FILTER_ACCEPT_ALL = (key, value) -> key;
/*private*/ static final BiFunction<String, String, String> FILTER_ACTIVE_PLATFORM_KEYS = (key, value) -> {
key = conditionalSuffix(key, ".linux", isLinux);
key = conditionalSuffix(key, ".windows", isWindows);
key = conditionalSuffix(key, ".macosx", isMacOS);
// if (key != null)
return key;
};
public static KeyValueStore fromFile(File file) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
return fromStream(fileInputStream, null);
}
}
public static KeyValueStore fromStoreWithFilter(KeyValueStore store, BiFunction<String, String, String> keyMapper) {
Map<String, String> values = new LinkedHashMap<>();
for(String key : store._keys()) {
String value = store.get(key);
String mapped = keyMapper.apply(key, value);
if(mapped != null) {
values.put(mapped, value);
}
}
return new KeyValueStoreImpl(values);
}
public static KeyValueStore platformSpecificFromStream(InputStream input) throws IOException {
return fromStream(input, FILTER_ACTIVE_PLATFORM_KEYS);
}
public static KeyValueStore fromStream(InputStream input) throws IOException {
return new KeyValueStoreImpl( loadValues(input, FILTER_ACCEPT_ALL) );
}
public static KeyValueStore fromStream(InputStream input, BiFunction<String, String, String> mapper) throws IOException {
return new KeyValueStoreImpl( loadValues(input, mapper) );
}
public static KeyValueStore overriding(KeyValueStore store, Map<String, String> values) {
// KeyValueStore def = new KeyValueStoreImpl(values);
return new KeyValueDelegatingStore(values, store);
}
/**
* Return a filter that will map a key name to a substring starting with a given {@code prefix}.
* The filter will return null if the key does not start with the prefix, and the remainder of
* the original key name without the shared prefrix in case of a positive match.
*
* <p>Given
* <pre>
* Map (
* alpha = Alpha
* alpha.somekey = v1
* alpha.some.keys = v2
* alpha.other.keys = v3
* beta = Beta
* beta.some.keys = v3
* )
* </pre>
*
* a call to keyPrefixMapper("alpha") will generate the following result:
*
* <pre>
* Map (
* somekey = v1
* some.keys = v2
* other.keys = v3
* )
* </pre>
*
*/
public static final BiFunction<String, String, String> keyPrefixMapper(String prefix) {
String filter = prefix.endsWith(".") ? prefix : prefix + ".";
int length = filter.length();
return new BiFunction<String, String, String>() {
@Override
public String apply(String key, String value) {
if(key.startsWith(filter)) {
return key.substring(length);
}
return null; // ignore
}
};
}
/**
* Return a filter that will perform the same matching as performed by {@link #keyPrefixMapper(String)},
* as well a verify that the length of the remainder of the key matched the value of the {@code length}
* parameter (length to be understood as 'number of parts when breaking the mapped key name at each .'
* For e.g. passing 1 will filter out mapped name where the resulting name contains one or more '.'.
*
* <p>Given
* <pre>
* Map (
* alpha = Alpha
* alpha.somekey = v1
* alpha.some.keys = v2
* alpha.other.keys = v3
* beta = Beta
* beta.some.keys = v3
* )
* </pre>
*
* a call to keyPrefixMapper("alpha", 1) will generate the following result:
*
* <pre>
* Map (
* somekey = v1
* )
* </pre>
*
*/
public static final BiFunction<String, String, String> keyPrefixMapper(String prefix, int length) {
String filter = prefix.endsWith(".") ? prefix : prefix + ".";
int keylength = filter.length();
return new BiFunction<String, String, String>() {
@Override
public String apply(String key, String value) {
if(key.startsWith(filter)) {
String mapped = key.substring(keylength);
if (mapped.split("\\.").length == length) {
return mapped;
}
}
return null; // ignore
}
};
}
// ----------------------------------------------------------------------------
/*public*/ static final Map<String,String> loadValues(InputStream input, BiFunction<String, String, String> mapper) throws IOException {
try (BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
String[] lines = lines(br);
Map<String,String> values = new LinkedHashMap<>();
for (String line : lines) {
if (line.length() == 0 || line.charAt(0) == '#')
continue;
int equals = line.indexOf('=');
if (equals != -1) {
String key = line.substring(0, equals).trim();
String value = line.substring(equals + 1).trim();
key = mapper.apply(key, value);
if (key != null) {
values.put(key, value);
}
}
}
return values;
}
}
private static String[] lines(BufferedReader br) throws IOException {
String lines[] = new String[100];
int lineCount = 0;
String line = null;
while ((line = br.readLine()) != null) {
if (lineCount == lines.length) {
String temp[] = new String[lineCount << 1]; // double
System.arraycopy(lines, 0, temp, 0, lineCount);
lines = temp;
}
lines[lineCount++] = line;
}
if (lineCount != lines.length) {
// resize array to appropriate amount for these lines
String output[] = new String[lineCount];
System.arraycopy(lines, 0, output, 0, lineCount);
return output;
}
return lines;
}
protected static final String conditionalSuffix(String key, String suffix, boolean filter) {
if (key == null)
return null;
// Key does not end with the given suffix? Process as normal
if (!key.endsWith(suffix))
return key;
if (!filter)
return null;
// Strip the suffix from the key
return key.substring(0, key.length() - suffix.length());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment