package org.limewire.setting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
import org.limewire.setting.evt.SettingEvent;
import org.limewire.setting.evt.SettingListener;
import org.limewire.setting.evt.SettingEvent.EventType;
/**
* Provides a key-value property as an abstract class. The value is typed
* in subclasses to avoid casting and ensure your settings are type-safe.
* The value has a unique key.
* <p>
* When you add a new <code>AbstractSetting</code> subclass, add a public synchronized
* method to {@link SettingsFactory} to create an instance of the setting.
* For example, subclass {@link IntSetting}, <code>SettingsFactory</code> has
* {@link SettingsFactory#createIntSetting(String, int)} and
* {@link SettingsFactory#createRemoteIntSetting(String, int)}.
* <p>
* <code>AbstractSetting</code> includes an abstract method to load a <code>String</code>
* into the key-value property. You are responsible to convert the
* <code>String</code> to the appropriate type in a subclass.
* <p>
* For example, if your subclass is for an integer setting you can
* have a integer field i.e. <code>myIntValue</code>, in the class. Then you
* would set <code>myIntValue</code> with the integer converted
* <code>String</code> argument, for example:
<pre>
protected void loadValue(String sValue) {
try {
value = Integer.parseInt(sValue.trim());
} catch(NumberFormatException nfe) {
revertToDefault();
}
}
</pre>
*<p>
* This class, includes fields for the <code>AbstractSetting</code>'s visibility
* (public vs. private) and persistence (always save vs don't save).
* <p>
* Visibility and persistence are just fields for a property; what the field
* means to your application is up to you. For example, you could give the
* setting a "don't save" value and when it's time to store the setting to a
* database, you check the setting and take appropriate actions.
* <p>
* See {@link SettingsFactory} for an example of creating an
* <code>IntSetting</code> object which is a
* subclass of <code>AbstractSetting</code> . Additionally the
* example shows how to load and save the setting to disk.
*/
public abstract class AbstractSetting<T> implements Setting<T> {
/**
* Protected default <tt>Properties</tt> instance for subclasses.
*/
protected final Properties DEFAULT_PROPS;
/**
* Protected <tt>Properties</tt> instance containing properties for any
* subclasses.
*/
protected final Properties PROPS;
/**
* The constant key for this property, specified upon construction.
*/
protected final String KEY;
/**
* Constant for the default value for this <tt>Setting</tt>.
*/
protected final String DEFAULT_VALUE;
/**
* Value for whether or not this setting should always save.
*/
private boolean alwaysSave = false;
/**
* Setting for whether or not this setting is private and should
* not be reported in bug reports.
*/
private boolean isPrivate = false;
/**
* List of {@link SettingListener}.
*/
private Collection<SettingListener> listeners = null;
/**
* Constructs a new setting with the specified key and default
* value. Private access ensures that only this class can construct
* new <tt>Setting</tt>s.
*
* @param key the key for the setting
* @param defaultValue the defaultValue for the setting
* @throws <tt>IllegalArgumentException</tt> if the key for this
* setting is already contained in the map of default settings
*/
protected AbstractSetting(Properties defaultProps, Properties props, String key,
String defaultValue) {
DEFAULT_PROPS = defaultProps;
PROPS = props;
KEY = key;
DEFAULT_VALUE = defaultValue;
if(DEFAULT_PROPS.containsKey(key))
throw new IllegalArgumentException("duplicate setting key: " + key);
DEFAULT_PROPS.put(KEY, defaultValue);
loadValue(defaultValue);
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#addSettingListener(org.limewire.setting.evt.SettingListener)
*/
public void addSettingListener(SettingListener l) {
if (l == null) {
throw new NullPointerException("SettingListener is null");
}
synchronized (this) {
if (listeners == null) {
listeners = new ArrayList<SettingListener>();
}
listeners.add(l);
}
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#removeSettingListener(org.limewire.setting.evt.SettingListener)
*/
public void removeSettingListener(SettingListener l) {
if (l == null) {
throw new NullPointerException("SettingListener is null");
}
synchronized (this) {
if (listeners != null) {
listeners.remove(l);
if (listeners.isEmpty()) {
listeners = null;
}
}
}
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#getSettingListeners()
*/
public SettingListener[] getSettingListeners() {
synchronized (this) {
if (listeners == null) {
return null;
}
return listeners.toArray(new SettingListener[listeners.size()]);
}
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#reload()
*/
public void reload() {
String value = PROPS.getProperty(KEY);
if (value == null) {
value = DEFAULT_VALUE;
}
// Ensure that PROPS is always backed with the default value.
// This is necessary for saving the PROPS file, as the backing
// default map isn't serialized with the properties.
// So any defaults that want to be "always saved" need to
// be explicitly inserted.
if(isDefault()) {
PROPS.setProperty(KEY, DEFAULT_VALUE);
}
loadValue(value);
fireSettingEvent(EventType.RELOAD);
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#revertToDefault()
*/
public boolean revertToDefault() {
if (!isDefault()) {
setValueInternal(DEFAULT_VALUE);
fireSettingEvent(EventType.REVERT_TO_DEFAULT);
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#shouldAlwaysSave()
*/
public boolean shouldAlwaysSave() {
return alwaysSave;
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#setAlwaysSave(boolean)
*/
public AbstractSetting<T> setAlwaysSave(boolean alwaysSave) {
if (this.alwaysSave != alwaysSave) {
this.alwaysSave = alwaysSave;
fireSettingEvent(EventType.ALWAYS_SAVE_CHANGED);
}
return this;
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#setPrivate(boolean)
*/
public Setting<T> setPrivate(boolean isPrivate) {
if (this.isPrivate != isPrivate) {
this.isPrivate = isPrivate;
fireSettingEvent(EventType.PRIVACY_CHANGED);
}
return this;
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#isPrivate()
*/
public boolean isPrivate() {
return isPrivate;
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#isDefault()
*/
public boolean isDefault() {
String value = PROPS.getProperty(KEY);
if (value == null)
return true;
return value.equals(DEFAULT_PROPS.getProperty(KEY));
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#getKey()
*/
public String getKey() {
return KEY;
}
/* (non-Javadoc)
* @see org.limewire.setting.Setting#getValueAsString()
*/
public String getValueAsString() {
String prop = PROPS.getProperty(KEY);
return prop == null ? DEFAULT_VALUE : prop;
}
/**
* Set new property value.
* <p>
* NOTE: This is protected so that only this package
* can update all kinds of settings using a String value.
* StringSetting updates the access to public.
* @param value new property value
*/
protected void setValueInternal(String value) {
String old = PROPS.getProperty(KEY);
if (old == null || !old.equals(value)) {
PROPS.setProperty(KEY, value);
loadValue(value);
fireSettingEvent(EventType.VALUE_CHANGED);
}
}
/**
* Load value from property string value.
* @param sValue property string value
*/
abstract protected void loadValue(String sValue);
@Override
public String toString() {
return KEY + "=" + getValueAsString();
}
/**
* Fires a SettingEvent.
*/
protected void fireSettingEvent(EventType type) {
fireSettingEvent(new SettingEvent(type, this));
}
/**
* Fires a SettingEvent.
*/
protected void fireSettingEvent(final SettingEvent evt) {
if (evt == null) {
throw new NullPointerException("SettingEvent is null");
}
final SettingListener[] listeners = getSettingListeners();
if (listeners != null) {
Runnable command = new Runnable() {
public void run() {
for (SettingListener l : listeners) {
l.settingChanged(evt);
}
}
};
SettingsGroupManager.instance().execute(command);
}
}
}