package au.net.causal.projo.prefs.properties;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import au.net.causal.projo.prefs.AbstractPreferenceNode;
import au.net.causal.projo.prefs.PreferenceKeyMetadata;
import au.net.causal.projo.prefs.PreferenceNode;
import au.net.causal.projo.prefs.PreferencesException;
import au.net.causal.projo.prefs.UnsupportedDataTypeException;
import com.google.common.reflect.TypeToken;
/**
* A preference node implementation that uses a {@link Properties} object to store values.
* <p>
*
* Child nodes are supported by generating property key names with '.' delimited node names and the key name. So you will get a property name of
* 'com.example.myKey' for a key of 'myKey', a parent node of 'com' and a child node of 'example'.
*
* @author prunge
*/
public class PropertiesPreferenceNode extends AbstractPreferenceNode
{
private static final String NODE_SEPARATOR = ".";
private final Properties properties;
private final String keyPrefix;
//TODO what happens if a key name or node name contains the node separator character?
/**
* Creates a <code>PropertiesPreferenceNode</code> that wraps a {@link Properties} object.
*
* @param properties the properties to wrap.
*
* @throws NullPointerException if <code>properties</code> is null.
*/
public PropertiesPreferenceNode(Properties properties)
{
this(properties, "");
}
/**
* Creates a <code>PropertiesPreferenceNode</code> that wraps a {@link Properties} and all keys will have the specified prefix.
* <p>
*
* This is not typically used by end-users, as it mostly provides a way of creating child nodes.
*
* @param properties the properties to wrap.
* @param keyPrefix the key prefix.
*
* @throws NullPointerException if <code>properties</code> or <code>keyPrefix</code> is null.
*/
public PropertiesPreferenceNode(Properties properties, String keyPrefix)
{
super(Collections.<Class<? extends Annotation>>emptySet());
if (properties == null)
throw new NullPointerException("properties == null");
if (keyPrefix == null)
throw new NullPointerException("keyPrefix == null");
this.properties = properties;
this.keyPrefix = keyPrefix;
}
@Override
protected boolean isDataTypeSupportedImpl(PreferenceKeyMetadata<?> keyType) throws PreferencesException
{
return(isDataTypeSupported(keyType.getDataType()));
}
private boolean isDataTypeSupported(TypeToken<?> type) throws PreferencesException
{
//Only supports string values
return(TypeToken.of(String.class).equals(type));
}
@Override
protected <T> T getValueImpl(String key, PreferenceKeyMetadata<T> metadata) throws UnsupportedDataTypeException, PreferencesException
{
if (key == null)
throw new NullPointerException("key == null");
if (metadata == null)
throw new NullPointerException("metadata == null");
if (!isDataTypeSupported(metadata.getDataType()))
throw new UnsupportedDataTypeException(metadata.getDataType());
String fullKey = keyPrefix + key;
String value = properties.getProperty(fullKey);
//We know T is for String since that is the only supported data type
@SuppressWarnings("unchecked")
T safeValue = (T)value;
return(safeValue);
}
@Override
protected <T> void putValueImpl(String key, T value, PreferenceKeyMetadata<T> metadata) throws UnsupportedDataTypeException, PreferencesException
{
if (key == null)
throw new NullPointerException("key == null");
if (metadata == null)
throw new NullPointerException("metadata == null");
if (!isDataTypeSupported(metadata.getDataType()))
throw new UnsupportedDataTypeException(metadata.getDataType());
String fullKey = keyPrefix + key;
properties.setProperty(fullKey, (String)value);
}
@Override
protected <T> void removeValueImpl(String key, PreferenceKeyMetadata<T> metadata) throws UnsupportedDataTypeException, PreferencesException
{
if (key == null)
throw new NullPointerException("key == null");
if (metadata == null)
throw new NullPointerException("metadata == null");
if (!isDataTypeSupported(metadata.getDataType()))
throw new UnsupportedDataTypeException(metadata.getDataType());
String fullKey = keyPrefix + key;
properties.remove(fullKey);
}
@Override
public void removeAllValues() throws PreferencesException
{
//Look for all nodes with our prefix and no other separator characters
for (String propertyName : properties.stringPropertyNames())
{
if (propertyName.startsWith(keyPrefix))
{
String unprefixedPropertyName = propertyName.substring(keyPrefix.length());
if (!unprefixedPropertyName.contains(NODE_SEPARATOR))
properties.remove(propertyName);
}
}
}
@Override
public PreferenceNode getChildNode(String name) throws PreferencesException
{
String fullPrefix = keyPrefix + name + NODE_SEPARATOR;
return(new PropertiesPreferenceNode(properties, fullPrefix));
}
@Override
public void removeChildNode(String name) throws PreferencesException
{
getChildNode(name).removeAllValues();
}
@Override
public Set<String> getKeyNames() throws PreferencesException
{
Set<String> keyNames = new LinkedHashSet<>();
//Look for all nodes with our prefix and no other separator characters
for (String propertyName : properties.stringPropertyNames())
{
if (propertyName.startsWith(keyPrefix))
{
String unprefixedPropertyName = propertyName.substring(keyPrefix.length());
if (!unprefixedPropertyName.contains(NODE_SEPARATOR))
keyNames.add(unprefixedPropertyName);
}
}
return(keyNames);
}
@Override
public Set<String> getNodeNames() throws PreferencesException
{
Set<String> nodeNames = new LinkedHashSet<>();
//Look for all nodes with our prefix and no other separator characters
for (String propertyName : properties.stringPropertyNames())
{
if (propertyName.startsWith(keyPrefix))
{
String unprefixedPropertyName = propertyName.substring(keyPrefix.length());
String[] splitPropertyName = unprefixedPropertyName.split(Pattern.quote(NODE_SEPARATOR), 2);
if (splitPropertyName.length > 1) //Don't include keys, just child nodes
nodeNames.add(splitPropertyName[0]);
}
}
return(nodeNames);
}
@Override
public void flush() throws PreferencesException
{
//Nothing to do
}
@Override
public void close() throws PreferencesException
{
flush();
}
}