Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
package com.marginallyclever.util;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
/**
* Created on 6/7/15.
*
* @author Peter Colapietro
* See <a href="http://www.davidc.net/programming/java/java-preferences-using-file-backing-store">Java Preferences using a file as the backing store</a>
* See <a href="http://stackoverflow.com/a/25548386">SO answer to: How to synchronize file access in a Java servlet?</a>
* @since v7.1.4
*/
public class MarginallyCleverPreferences extends AbstractPreferences implements Ancestryable {
private static final Logger logger = LoggerFactory.getLogger(MarginallyCleverPreferences.class);
/**
*
*/
private final Map<String, String> root;
/**
*
*/
private final Map<String, Preferences> children;
/**
*
*/
private boolean thisIsRemoved;
/**
*
*/
private final Object mutex = new Object();
/**
* Creates a preference node with the specified parent and the specified
* name relative to its parent.
*
* @param parent the parent of this preference node, or null if this
* is the root.
* @param name the name of this preference node, relative to its parent,
* or ≶tt>""≶/tt> if this is the root.
* @throws IllegalArgumentException if ≶tt>name≶/tt> contains a slash
* (≶tt>'/'≶/tt>), or ≶tt>parent≶/tt> is ≶tt>null≶/tt> and
* name isn't ≶tt>""≶/tt>.
*/
public MarginallyCleverPreferences(AbstractPreferences parent, String name) {
super(parent, name);
logger.debug("Instantiating node {}", name);
root = new TreeMap<String, String>();
children = new TreeMap<String, Preferences>();
try {
sync();
} catch (BackingStoreException e) {
logger.error("Unable to sync on creation of node {}.", name, e);
}
}
@Override
protected void putSpi(@NotNull String key, String value) {
root.put(key, value);
try {
flush();
} catch (BackingStoreException e) {
logger.error("Unable to flush after putting {}.", key, e);
}
}
@Override
protected String getSpi(@NotNull String key) {
return root.get(key);
}
@Override
protected void removeSpi(@NotNull String key) {
root.remove(key);
try {
flush();
} catch (BackingStoreException e) {
logger.error("Unable to flush after removing {}.", key, e);
}
}
@Override
protected void removeNodeSpi() throws BackingStoreException {
flush();
thisIsRemoved = true;
}
@NotNull
@Override
protected String[] keysSpi() {
final Set<String> keySet = root.keySet();
return keySet.toArray(new String[keySet.size()]);
}
@NotNull
@Override
protected String[] childrenNamesSpi() {
final Set<String> childrenNames = children.keySet();
return childrenNames.toArray(new String[childrenNames.size()]);
}
/**
* http://stackoverflow.com/a/24249709
*
* @param name
* @return preferences
*/
@NotNull
@Override
protected AbstractPreferences childSpi(@NotNull String name) {
AbstractPreferences childPreferenceNode = (AbstractPreferences) children.get(name);
boolean isChildRemoved = false;
if (childPreferenceNode != null) {
try {
isChildRemoved = getIsRemoved(childPreferenceNode);
} catch (ReflectiveOperationException e) {
logger.error( e.getMessage() );
}
}
if (childPreferenceNode == null || isChildRemoved) {
final AbstractPreferences castedPreferences = new MarginallyCleverPreferences(this, name);
childPreferenceNode = castedPreferences;
children.put(name, childPreferenceNode);
}
return childPreferenceNode;
}
/**
* FIXME - Pure hack to get around erasure.
*
* @param abstractPreference
* @return
* @throws ReflectiveOperationException
*/
private boolean getIsRemoved(AbstractPreferences abstractPreference) throws ReflectiveOperationException {
logger.debug( abstractPreference.toString() );
final Method declaredMethod = AbstractPreferences.class.getDeclaredMethod("isRemoved");
declaredMethod.setAccessible(true);
Object isRemoved = declaredMethod.invoke(abstractPreference, new Object[]{null});
return (Boolean) isRemoved;
}
@Override
protected void syncSpi() throws BackingStoreException {
if (isRemoved()) {
return;
}
final File propertiesPreferencesFile = MarginallyCleverPreferencesFileFactory.getPropertiesPreferencesFile();
if (!propertiesPreferencesFile.exists()) {
return;
}
synchronized (mutex) {
final Properties p = new Properties();
try {
try (final InputStream inStream = new FileInputStream(propertiesPreferencesFile)) {
p.load(inStream);
}
final StringBuilder sb = new StringBuilder();
getPath(sb);
final String path = sb.toString();
final Enumeration<?> propertyNames = p.propertyNames();
while (propertyNames.hasMoreElements()) {
final String propKey = (String) propertyNames.nextElement();
if (propKey.startsWith(path)) {
final String subKey = propKey.substring(path.length());
// Only load immediate descendants
if (subKey.indexOf('.') == -1) {
root.put(subKey, p.getProperty(propKey));
}
}
}
} catch (IOException e) {
throw new BackingStoreException(e);
}
}
}
@Override
protected void flushSpi() throws BackingStoreException {
final File xmlPreferencesFile = MarginallyCleverPreferencesFileFactory.getXmlPreferencesFile();
final File file = MarginallyCleverPreferencesFileFactory.getPropertiesPreferencesFile();
synchronized (mutex) {
try {
final Properties p = new Properties();
final StringBuilder sb = new StringBuilder();
getPath(sb);
final String path = sb.toString();
if (file.exists()) {
try (final InputStream fileInputStream = new FileInputStream(file)) {
p.load(fileInputStream);
}
final List<String> toRemove = new ArrayList<String>();
// Make a list of all direct children of this node to be removed
final Enumeration<?> pnen = p.propertyNames();
while (pnen.hasMoreElements()) {
final String propKey = (String) pnen.nextElement();
if (propKey.startsWith(path)) {
final String subKey = propKey.substring(path.length());
// Only do immediate descendants
if (subKey.indexOf('.') == -1) {
toRemove.add(propKey);
}
}
}
// Remove them now that the enumeration is done with
for (String propKey : toRemove) {
p.remove(propKey);
}
}
// If this node hasn't been removed, add back in any values
if (!thisIsRemoved) {
for (String s : root.keySet()) {
p.setProperty(path + s, root.get(s));
}
storePreferencesInFile(file, p);
storeNodeInFile(xmlPreferencesFile);
}
} catch (IOException e) {
throw new BackingStoreException(e);
}
}
}
private void storeNodeInFile(File file) throws IOException, BackingStoreException {
final Preferences parent = recursiveGetParent(this.parent() != null ? this.parent() : this);
try (final OutputStream fileOutputStream = new FileOutputStream(file)) {
parent.exportNode(fileOutputStream);
}
}
private Preferences recursiveGetParent(Preferences node) {
Preferences parent = node.parent();
if (parent == null) {
node = parent;
} else {
recursiveGetParent(parent);
}
return node;
}
/**
* @param file
* @param p
* @throws IOException
*/
private void storePreferencesInFile(File file, Properties p) throws IOException, BackingStoreException {
final String marginallyCleverPreferencesFileComments = "MarginallyCleverPreferences";
try (final OutputStream fileOutputStream = new FileOutputStream(file)) {
p.store(fileOutputStream, marginallyCleverPreferencesFileComments);
}
}
/**
* @param sb String builder
*/
private void getPath(StringBuilder sb) {
MarginallyCleverPreferences parent = null;
try {
parent = (MarginallyCleverPreferences) parent();
} catch (ClassCastException e) {
//logger.info("NOOP");
}
if (parent == null) {
return;
}
parent.getPath(sb);
sb.append(name()).append('.');
}
/**
* @return children
*/
@Override
public Map<String, Preferences> getChildren() {
return new TreeMap<String, Preferences>(children);
}
/**
* @return root
*/
@Override
public Map<String, String> getRoot() {
return new TreeMap<String, String>(root);
}
}
3f1df20e1This file is pinned to the selected release. Download the original to open it in your design tool.