cdc.util.prefs.FilePreferences Maven / Gradle / Ivy
package cdc.util.prefs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Preferences implementation that stores data to a user defined file.
*
* This is an adaptation of public domain code developed by David C., that
* can be found here.
*/
public class FilePreferences extends AbstractPreferences {
private static final Logger LOGGER = LogManager.getLogger(FilePreferences.class);
private final Map root = new TreeMap<>();
private final Map children = new TreeMap<>();
private boolean isRemoved = false;
public FilePreferences(AbstractPreferences parent,
String name) {
super(parent,
name);
LOGGER.trace("({}, {})", parent == null ? "null" : parent.absolutePath(), name);
try {
sync();
} catch (final BackingStoreException e) {
LOGGER.error("Unable to sync on creation of node " + name, e);
}
}
private void buildPath(StringBuilder builder) {
final FilePreferences parent = (FilePreferences) parent();
if (parent != null) {
parent.buildPath(builder);
builder.append(name()).append('.');
}
}
private String getPath() {
final StringBuilder builder = new StringBuilder();
buildPath(builder);
return builder.toString();
}
@Override
protected void putSpi(String key,
String value) {
LOGGER.trace("putSpi({}, {})", key, value);
root.put(key, value);
try {
flush();
} catch (final BackingStoreException e) {
LOGGER.error("Unable to flush after putting " + key, e);
}
}
@Override
protected String getSpi(String key) {
LOGGER.trace("getSpi({})", key);
return root.get(key);
}
@Override
protected void removeSpi(String key) {
LOGGER.trace("removeSpi({})", key);
root.remove(key);
try {
flush();
} catch (final BackingStoreException e) {
LOGGER.error("Unable to flush after removing " + key, e);
}
}
@Override
protected void removeNodeSpi() throws BackingStoreException {
LOGGER.trace("removeNodeSpi()");
isRemoved = true;
flush();
}
@Override
protected String[] keysSpi() {
return root.keySet().toArray(new String[root.keySet().size()]);
}
@Override
protected String[] childrenNamesSpi() {
return children.keySet().toArray(new String[children.keySet().size()]);
}
@Override
protected FilePreferences childSpi(String name) {
LOGGER.trace("childSpi({})", name);
FilePreferences child = children.get(name);
if (child == null || child.isRemoved()) {
child = new FilePreferences(this, name);
children.put(name, child);
}
return child;
}
@Override
protected void syncSpi() throws BackingStoreException {
LOGGER.trace("syncSpi()");
if (isRemoved()) {
return;
}
final File file = FilePreferencesFactory.getPreferencesFile();
if (!file.exists()) {
return;
}
synchronized (file) {
LOGGER.trace("load({})", file);
final Properties properties = new Properties();
try (final FileInputStream fis = new FileInputStream(file)) {
properties.load(fis);
final String path = getPath();
for (final String key : properties.stringPropertyNames()) {
if (key.startsWith(path)) {
final String subKey = key.substring(path.length());
// Only load immediate descendants
if (subKey.indexOf('.') == -1) {
root.put(subKey, properties.getProperty(key));
}
}
}
} catch (final IOException e) {
LOGGER.catching(e);
throw new BackingStoreException(e);
}
}
}
@Override
protected void flushSpi() throws BackingStoreException {
LOGGER.trace("flushSpi()");
final File file = FilePreferencesFactory.getPreferencesFile();
synchronized (file) {
final Properties properties = new Properties();
try {
final String path = getPath();
if (file.exists()) {
try (final FileInputStream fis = new FileInputStream(file)) {
properties.load(fis);
}
final List toRemove = new ArrayList<>();
// Make a list of all direct children of this node to be
// removed
for (final String key : properties.stringPropertyNames()) {
if (key.startsWith(path)) {
final String subKey = key.substring(path.length());
// Only do immediate descendants
if (subKey.indexOf('.') == -1) {
toRemove.add(key);
}
}
}
// Remove them now that the enumeration is done with
for (final String key : toRemove) {
properties.remove(key);
}
}
// If this node hasn't been removed, add back in any values
if (!isRemoved) {
for (final Map.Entry entry : root.entrySet()) {
properties.setProperty(path + entry.getKey(), entry.getValue());
}
}
LOGGER.trace("save({})", file);
try (final FileOutputStream fos = new FileOutputStream(file)) {
properties.store(fos, "FilePreferences");
}
} catch (final IOException e) {
LOGGER.catching(e);
throw new BackingStoreException(e);
}
}
}
}