/*
* Adapted from http://www.davidc.net/programming/java/java-preferences-using-file-backing-store
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package org.rhq.core.util.preferences;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
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.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Preferences implementation that stores to a single user-defined file. See FilePreferencesFactory.
*
* @author David Croft (<a href="http://www.davidc.net">www.davidc.net</a>)
* @author Jay Shaughnessy (adapted from David Croft for RHQ)
*/
public class FilePreferences extends AbstractPreferences {
private final Log log = LogFactory.getLog(FilePreferences.class);
private Map<String, String> root;
private Map<String, FilePreferences> children;
private boolean isRemoved = false;
public FilePreferences(AbstractPreferences parent, String name) {
super(parent, name);
if (log.isDebugEnabled()) {
log.debug("Instantiating node " + name);
}
root = new TreeMap<String, String>();
children = new TreeMap<String, FilePreferences>();
try {
sync();
} catch (BackingStoreException e) {
log.error("Unable to sync on creation of node " + name, e);
}
}
protected void putSpi(String key, String value) {
root.put(key, value);
try {
flush();
} catch (BackingStoreException e) {
log.error("Unable to flush after putting " + key, e);
}
}
protected String getSpi(String key) {
String val = root.get(key);
return val;
}
protected void removeSpi(String key) {
root.remove(key);
try {
flush();
} catch (BackingStoreException e) {
log.error("Unable to flush after removing " + key, e);
}
}
protected void removeNodeSpi() throws BackingStoreException {
isRemoved = true;
flush();
}
protected String[] keysSpi() throws BackingStoreException {
return root.keySet().toArray(new String[root.keySet().size()]);
}
protected String[] childrenNamesSpi() throws BackingStoreException {
return children.keySet().toArray(new String[children.keySet().size()]);
}
protected FilePreferences childSpi(String name) {
FilePreferences child = children.get(name);
if (null == child || child.isRemoved()) {
child = new FilePreferences(this, name);
children.put(name, child);
}
return child;
}
protected void syncSpi() throws BackingStoreException {
if (isRemoved()) {
return;
}
final File file = FilePreferencesFactory.getPreferencesFile();
if (!file.exists()) {
return;
}
synchronized (file) {
Properties p = new Properties();
try {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
p.load(fis);
} finally {
if (null != fis) {
fis.close();
}
}
StringBuilder sb = new StringBuilder();
getPath(sb);
String path = sb.toString();
final Enumeration<?> pnen = p.propertyNames();
while (pnen.hasMoreElements()) {
String propKey = (String) pnen.nextElement();
if (propKey.startsWith(path)) {
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);
}
}
}
private void getPath(StringBuilder sb) {
FilePreferences parent = null;
try {
parent = (FilePreferences) parent();
} catch (IllegalStateException e) {
// expected when Node has been removed
}
if (null == parent) {
return;
}
parent.getPath(sb);
sb.append(name()).append('/');
}
protected void flushSpi() throws BackingStoreException {
final File file = FilePreferencesFactory.getPreferencesFile();
synchronized (file) {
Properties p = new Properties();
try {
StringBuilder sb = new StringBuilder();
getPath(sb);
String path = sb.toString();
if (file.exists()) {
p.load(new FileInputStream(file));
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()) {
String propKey = (String) pnen.nextElement();
if (propKey.startsWith(path)) {
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 (!isRemoved) {
for (String s : root.keySet()) {
p.setProperty(path + s, root.get(s));
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
p.store(fos, "RHQ FilePreferences. Do not edit this file manually.");
} finally {
if (null != fos) {
fos.close();
}
}
} catch (IOException e) {
throw new BackingStoreException(e);
}
}
}
}