All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.sfac.setting.SettingsImpl Maven / Gradle / Ivy

Go to download

This project is the model side of the Swing Framework and Components (SFaC). If your doing a clean separation between model (or business) and view (or GUI or rendering) parts of your application, (like in the MVC pattern), then the only classes of SFaC your model can access are in this project. On the other hand, the classes in sfac-core project are GUI-specific and should not be known by your model.

The newest version!
/*-------------------------------------------------------------------------
 Copyright 2009 Olivier Berlanger

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 -------------------------------------------------------------------------*/
package net.sf.sfac.setting;


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import net.sf.sfac.file.FilePathUtils;
import net.sf.sfac.file.FileUtils;
import net.sf.sfac.file.InvalidPathException;
import net.sf.sfac.utils.Comparison;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Default implementation of the Settings interface. 
* This class provides a 'Settings' service storing the settings in a java Property file. *

* The property are typed. The first time a property is accessed, it's type is set (the type is determined by the method called to * access the property like getRectangleProperty). Subsequent access to this property must use the same type. This is * done to improve efficiency because the propery can be stored with the proper type in the settings (wich avoids constant type * conversions).
* This class uses TypeHelper objects to manage each type of propery. Each helper knows how to convert a data type from * and to string (as only string can be saved in properties). The helper class instance is also used to identify a data type. *

*

* For file properties, the base path used is the directory where the property file lies. This allow to move (or copy) the * application easily without having to update all the file propeties with ne paths. *

* * @author Olivier Berlanger */ public class SettingsImpl implements Settings { private static Log log = LogFactory.getLog(SettingsImpl.class); /** Helper for identifying and managing String properties. */ public static final TypeHelper STRING_HELPER = new StringHelper(); /** Helper for identifying and managing String properties. */ public static final TypeHelper PASSWORD_HELPER = new PasswordHelper(); /** Helper for identifying and managing String properties. */ public static final TypeHelper STRING_ARRAY_HELPER = new StringArrayHelper(); /** Helper for identifying and managing Integer properties. */ public static final TypeHelper INTEGER_HELPER = new IntegerHelper(); /** Helper for identifying and managing Boolean properties. */ public static final TypeHelper BOOLEAN_HELPER = new BooleanHelper(); /** Helper for identifying and managing Double properties. */ public static final TypeHelper DOUBLE_HELPER = new DoubleHelper(); /** Helper for identifying and managing int array properties. */ public static final TypeHelper INT_ARRAY_HELPER = new IntArrayHelper(); /** Helper for identifying and managing Rectangle properties. */ public static final TypeHelper RECTANGLE_HELPER = new RectangleHelper(); /** Helper for identifying and managing Dimension properties. */ public static final TypeHelper DIMENSION_HELPER = new DimensionHelper(); /** Helper for identifying and managing Dimension properties. */ public static final TypeHelper COLOR_HELPER = new ColorHelper(); /** Map holding the enumeration helpers (one per enum class). */ private static Map, EnumHelper> enumHelpers; /** * Helper for identifying and managing file properties (it's not static because it depends on base path of this class). */ private final TypeHelper FILE_HELPER = new FileHelper(); /** File where the properties are stored. */ private File propFile; /** Description of properties written as header in properties file. */ private String fileDescription; /** Properties managed by this class. */ private Properties props; /** Map holding cached typed values of the properties. */ private HashMap> cache; /** PropertyChangeSupport used to manage PropertyChangeListeners. */ private PropertyChangeSupport changeSupport; /** Path utilities class used to convert between absolute and relative paths. */ private FilePathUtils pathConverter; /** * Construct a new settings implementation bound to a given property file. * * @param propertyFile * File where the properties are stored. * @param descr * Description of properties written as header in properties file. */ public SettingsImpl(File propertyFile, String descr) { if (propertyFile == null) throw new IllegalArgumentException("Property file cannot be null"); propFile = propertyFile.getAbsoluteFile(); fileDescription = descr; props = new OrderedProperties(); cache = new HashMap>(); pathConverter = new FilePathUtils(propFile.getParentFile()); changeSupport = new PropertyChangeSupport(this); } public String getPropertyFilePath() { return propFile.getAbsolutePath(); } /** * Get the settings description (the one saved at the top of the properties file). * * @return the settings description. */ public String getDescription() { return fileDescription; } /** * Set the settings description (the one saved at the top of the properties file). * * @param newDescr * the new settings description. */ public void setDescription(String newDescr) { fileDescription = newDescr; } /** * Get the FilePathUtils used to manage the file/directory settings.
* The returned FilePathUtils is set to use the same base directory as this class. * * @return The FilePathUtils used to manage the file/directory settings. */ public FilePathUtils getFilePathUtils() { return pathConverter; } /** * Load all the settings from the property file (if the file exists). * * @exception IOException * If something prevents the settings to be loaded. */ public void load() throws IOException { props.clear(); cache.clear(); if (propFile.exists()) { FileInputStream is = new FileInputStream(propFile); props.load(is); is.close(); } } /** * Save all the settings to the property file (if the file don't exists, it is created). * * @exception IOException * If something prevents the settings to be saved. */ public void save() throws IOException { log.info("Saving settings in file: " + propFile); synchronizeCache(); // save the properties FileUtils.ensureParentDirectoryExists(propFile); OutputStream out = new FileOutputStream(propFile); props.store(out, fileDescription); out.close(); } /** * Put all values that are in the cache in the properties. */ private void synchronizeCache() { for (Iterator it = cache.keySet().iterator(); it.hasNext();) { String key = it.next(); TypedValueHolder holder = cache.get(key); String stringValue = holder.getStringValue(); if (Comparison.isDefined(stringValue)) { props.setProperty(key, stringValue); } else { props.remove(key); } } } /** * Set a new property file to store the settings. The given property file will be set as new reference for paths. * * @param fil * The new properties file. */ public void setPropertyFile(File fil) { // Synchronize the cache for (Iterator it = cache.keySet().iterator(); it.hasNext();) { String key = it.next(); TypedValueHolder holder = cache.get(key); props.setProperty(key, holder.getStringValue()); } // set new property file propFile = fil.getAbsoluteFile(); cache.clear(); File parentDir = propFile.getParentFile(); pathConverter = new FilePathUtils(parentDir); } /** * Save all the settings to a new property file. If the file don't exists, it is created, if the file exists it is overriden * without warning. The given property file will be set as new reference for paths. * * @param fil * The new properties file. * @exception IOException * If something prevents the settings to be saved. */ public void saveAs(File fil) throws IOException { setPropertyFile(fil); // save the properties FileUtils.ensureParentDirectoryExists(propFile); OutputStream out = new FileOutputStream(propFile); props.store(out, fileDescription); out.close(); } /** * Get the cached (typed) value of a property. If the cached value is not defined but the string property is defined, the cached * value is built from the string value using the given helper. Return null if no (string or cached) value is found. * * @param key * Key for the property value. * @param helper * Helper identifying the value type and knowing how to convert a string to the typed value. * @return The cached (typed) value of a property, null if no value is found. * @exception IllegalArgumentException * if the value has an other type than the one specified by the helper. */ @SuppressWarnings("unchecked") private T getCachedValue(String key, TypeHelper helper) { TypedValueHolder holder = (TypedValueHolder) cache.get(key); if (holder != null) return holder.getValue(helper); else { String propValue = props.getProperty(key, null); if (propValue != null) { if (helper != null) { holder = new TypedValueHolder(helper); try { T value = helper.stringToObject(propValue); holder.setValue(helper, value); cache.put(key, holder); return value; } catch (Exception e) { // error in stored property -> ignore it to avoid breaking everything // return null, so the default value will be used as it is the better fallback log.warn("Bad property " + key + "='" + propValue + "' for type " + helper, e); } } else { // Note: the helper can be null in case of a deleteProperty. return (T) propValue; } } } return null; } /** * Set the cached value of a property.
* Setting a null value removes the property (values and keys are removed).
* If the new value is different than the previous one, a property change event is fired. * * @param key * Key for the property value. * @param helper * Helper identifying the value type. * @param value * the new value of the property. * @exception IllegalArgumentException * if the value has an other type than the one specified by the helper. */ @SuppressWarnings("unchecked") private void setCachedValue(String key, TypeHelper helper, T value) { if (value == null) { removeProperty(key, helper); } else { Object oldValue = getCachedValue(key, helper); if (!value.equals(oldValue)) { if (log.isDebugEnabled()) log.debug("New value set for setting '" + key + "' -> " + value); TypedValueHolder holder = (TypedValueHolder) cache.get(key); if (holder != null) holder.setValue(helper, value); else { holder = new TypedValueHolder(helper); holder.setValue(helper, value); cache.put(key, holder); } if (changeSupport.hasListeners(key)) changeSupport.firePropertyChange(key, oldValue, value); } else { if (log.isDebugEnabled()) log.debug("Equals value set for setting '" + key + "' -> " + value); } } } /** * Check if there is a property value defined for the given key. * * @param key * Key for the value. * @return True iff there is a property value defined for the given key. */ public boolean containsProperty(String key) { return (cache.containsKey(key)) || (props.containsKey(key)); } public Iterator getKeys() { synchronizeCache(); return props.stringPropertyNames().iterator(); } public void removeProperty(String key) { removeProperty(key, null); } private void removeProperty(String key, TypeHelper helper) { Object oldValue = getCachedValue(key, helper); if (cache.containsKey(key)) cache.remove(key); if (props.containsKey(key)) props.remove(key); if ((oldValue != null) && changeSupport.hasListeners(key)) changeSupport.firePropertyChange(key, oldValue, null); } // -------------------------- access for typed properties ---------------------------------------------------- public String getPropertyAsInternalString(String key) { TypedValueHolder holder = cache.get(key); if (holder != null) return holder.getStringValue(); return props.getProperty(key, null); } public void setPropertyAsInternalString(String key, String value) { if (value == null) { removeProperty(key); } else { try { Object oldValue = getCachedValue(key, null); if (!value.equals(oldValue)) { if (log.isDebugEnabled()) log.debug("New internal value set for setting '" + key + "' -> " + value); TypedValueHolder holder = cache.get(key); if (holder != null) holder.setStringValue(value); else props.setProperty(key, value); if (changeSupport.hasListeners(key)) changeSupport.firePropertyChange(key, oldValue, value); } else { if (log.isDebugEnabled()) log.debug("Equals internal value set for setting '" + key + "' -> " + value); } } catch (Exception e) { throw new IllegalArgumentException("Invalid internal value '" + value + "' for key: " + key, e); } } } public T getProperty(String key, TypeHelper helper, T defaultValue) { if (helper == null) throw new IllegalArgumentException("Helper cannot be null"); T val = getCachedValue(key, helper); return (val == null) ? defaultValue : val; } public void setProperty(String key, TypeHelper helper, T value) { if (value == null) { removeProperty(key, helper); } else { if (helper == null) throw new IllegalArgumentException("Helper cannot be null"); setCachedValue(key, helper, value); } } public String getStringProperty(String key, String defaultValue) { String val = getCachedValue(key, STRING_HELPER); return (val == null) ? defaultValue : val; } public void setStringProperty(String key, String value) { setCachedValue(key, STRING_HELPER, value); } public String getPasswordProperty(String key, String defaultValue) { String val = getCachedValue(key, PASSWORD_HELPER); return (val == null) ? defaultValue : val; } public void setPasswordProperty(String key, String value) { setCachedValue(key, PASSWORD_HELPER, value); } public String[] getStringArrayProperty(String key, String[] defaultValue) { String[] val = getCachedValue(key, STRING_ARRAY_HELPER); return (val == null) ? defaultValue : val; } public void setStringArrayProperty(String key, String[] value) { setCachedValue(key, STRING_ARRAY_HELPER, value); } public int getIntProperty(String key, int defaultValue) { Integer val = getCachedValue(key, INTEGER_HELPER); return (val == null) ? defaultValue : val.intValue(); } public void setIntProperty(String key, int value) { setCachedValue(key, INTEGER_HELPER, Integer.valueOf(value)); } public Integer getIntegerProperty(String key, Integer defaultValue) { Integer val = getCachedValue(key, INTEGER_HELPER); return (val == null) ? defaultValue : val; } public void setIntegerProperty(String key, Integer value) { setCachedValue(key, INTEGER_HELPER, value); } public double getDoubleProperty(String key, double defaultValue) { Double val = getCachedValue(key, DOUBLE_HELPER); return (val == null) ? defaultValue : val.intValue(); } public void setDoubleProperty(String key, double value) { setCachedValue(key, DOUBLE_HELPER, new Double(value)); } public boolean getBooleanProperty(String key, boolean defaultValue) { Boolean val = getCachedValue(key, BOOLEAN_HELPER); return (val == null) ? defaultValue : val.booleanValue(); } public void setBooleanProperty(String key, boolean value) { setCachedValue(key, BOOLEAN_HELPER, value ? Boolean.TRUE : Boolean.FALSE); } public Boolean getBooleanProperty(String key, Boolean defaultValue) { Boolean val = getCachedValue(key, BOOLEAN_HELPER); return (val == null) ? defaultValue : val; } public void setBooleanProperty(String key, Boolean value) { setCachedValue(key, BOOLEAN_HELPER, value); } public int[] getIntArrayProperty(String key, int[] defaultValue) { int[] val = getCachedValue(key, INT_ARRAY_HELPER); if (log.isDebugEnabled()) log.debug("Setting value " + key + "=" + Arrays.toString(val)); return (val == null) ? defaultValue : val; } public void setIntArrayProperty(String key, int[] value) { setCachedValue(key, INT_ARRAY_HELPER, value); } public Rectangle getRectangleProperty(String key, Rectangle defaultValue) { Rectangle val = getCachedValue(key, RECTANGLE_HELPER); return (val == null) ? defaultValue : val; } public void setRectangleProperty(String key, Rectangle value) { setCachedValue(key, RECTANGLE_HELPER, value); } public Dimension getDimensionProperty(String key, Dimension defaultValue) { Dimension val = getCachedValue(key, DIMENSION_HELPER); return (val == null) ? defaultValue : val; } public void setDimensionProperty(String key, Dimension value) { setCachedValue(key, DIMENSION_HELPER, value); } public Color getColorProperty(String key, Color defaultValue) { Color val = getCachedValue(key, COLOR_HELPER); return (val == null) ? defaultValue : val; } public void setColorProperty(String key, Color value) { setCachedValue(key, COLOR_HELPER, value); } public > T getEnumProperty(String key, T defaultValue) { T val = getCachedValue(key, getEnumHelper(defaultValue)); return (val == null) ? defaultValue : val; } public > void setEnumProperty(String key, T value) { setCachedValue(key, (value == null) ? null : getEnumHelper(value), value); } /** * Note: I was not able to find correct generic declaration for the enumClass inside this method, so I used the raw type. */ @SuppressWarnings("unchecked") private > EnumHelper getEnumHelper(T enumValue) { if (enumValue == null) throw new IllegalArgumentException("Default values of 'enum' settings cannot be null"); Class enumClass = enumValue.getDeclaringClass(); if (enumHelpers == null) enumHelpers = new HashMap, EnumHelper>(); EnumHelper helper = (EnumHelper) enumHelpers.get(enumClass); if (helper == null) { helper = new EnumHelper(enumClass); enumHelpers.put(enumClass, helper); } return helper; } public String getFileProperty(String key, String defaultValue) { return getFileProperty(key, defaultValue, true); } public String getFileProperty(String key, String defaultValue, boolean absolute) { String path; if (absolute) path = getCachedValue(key, FILE_HELPER); else path = props.getProperty(key, null); // use default if null if (path == null) { try { if (absolute) { path = pathConverter.getAbsoluteFilePath(defaultValue); } else { path = pathConverter.getRelativeFilePath(defaultValue); } } catch (InvalidPathException ipe) { throw new IllegalArgumentException(ipe.getMessage()); } } return path; } public void setFileProperty(String key, String value) throws InvalidPathException { String absolutePath = pathConverter.getAbsoluteFilePath(value); String relativePath = pathConverter.getRelativeFilePath(value); setCachedValue(key, FILE_HELPER, absolutePath); if (value != null) props.setProperty(key, relativePath); } // -------------------------- interface for translating values ---------------------------------------------------- static class StringHelper extends TypeHelper { @Override public String stringToObject(String str) { return str; } @Override public String objectToString(String obj) { return obj; } } static class PasswordHelper extends TypeHelper { private static final long SEED = 5671709; @Override public String stringToObject(String str) { if (str == null) return null; if (Comparison.isEmpty(str)) return ""; StringBuilder sb = new StringBuilder(); int len = str.length(); if ((len % 4) != 0) throw new IllegalStateException("Bad hex-encoded password: " + str); int strLen = len / 4; long mask = SEED; for (int i = 0; i < strLen; i++) { mask = ((mask * 11) / 7) + i; String hexStr = str.substring(i * 4, i * 4 + 4); int transformed = Integer.parseInt(hexStr, 16); char ch = (char) (transformed ^ mask); sb.append(ch); } return sb.toString(); } @Override public String objectToString(String str) { if (str == null) return null; if (Comparison.isEmpty(str)) return ""; StringBuilder sb = new StringBuilder(); int strLen = str.length(); long mask = SEED; for (int i = 0; i < strLen; i++) { mask = ((mask * 11) / 7) + i; char transformed = (char) (str.charAt(i) ^ (char) mask); String hexString = Integer.toHexString(transformed); int hexLen = hexString.length(); if (hexLen < 4) { for (int j = 3 - hexLen; j >= 0; j--) { sb.append('0'); } } else if (hexLen != 4) { throw new InternalError("Bad password conversion!"); } sb.append(hexString); } return sb.toString(); } } static class StringArrayHelper extends TypeHelper { @Override public String[] stringToObject(String str) { if (str == null) return null; List strings = new ArrayList(); StringBuffer sb = new StringBuffer(); int len = str.length(); char ch; for (int i = 0; i < len; i++) { ch = str.charAt(i); if (ch == '|') { strings.add(sb.toString()); sb.setLength(0); } else if (ch == '~') { i++; if (i < len) { ch = str.charAt(i); if (ch == '@') { strings.add(null); sb.setLength(0); i++; } else { sb.append(ch); } } } else { sb.append(ch); } } strings.add(sb.toString()); return strings.toArray(new String[strings.size()]); } @Override public String objectToString(String[] strings) { if (strings == null) return null; StringBuffer sb = new StringBuffer(); char ch; int len = strings.length; for (int i = 0; i < len; i++) { if (i > 0) sb.append('|'); String str = strings[i]; if (str == null) { sb.append("~@"); } else { int strLen = str.length(); for (int j = 0; j < strLen; j++) { ch = str.charAt(j); if (ch == '|') { sb.append("~|"); } else if (ch == '~') { sb.append("~~"); } else { sb.append(ch); } } } } return sb.toString(); } } static class IntegerHelper extends TypeHelper { @Override public Integer stringToObject(String str) { return (str == null) ? null : new Integer(str); } } static class BooleanHelper extends TypeHelper { @Override public Boolean stringToObject(String str) { return (str == null) ? null : ("true".equals(str)) ? Boolean.TRUE : Boolean.FALSE; } } static class DoubleHelper extends TypeHelper { @Override public Double stringToObject(String str) { return (str == null) ? null : new Double(str); } } static class IntArrayHelper extends TypeHelper { @Override public int[] stringToObject(String str) { if (str == null) return null; StringTokenizer st = new StringTokenizer(str, ", "); int len = st.countTokens(); int[] result = new int[len]; for (int i = 0; i < len; i++) { result[i] = Integer.parseInt(st.nextToken()); } return result; } @Override public String objectToString(int[] value) { if (value == null) return null; StringBuffer sb = new StringBuffer(); int len = value.length; for (int i = 0; i < len; i++) { if (i > 0) sb.append(','); sb.append(value[i]); } return sb.toString(); } } static class RectangleHelper extends TypeHelper { @Override public Rectangle stringToObject(String str) throws Exception { if (str == null) return null; int[] arr = INT_ARRAY_HELPER.stringToObject(str); return new Rectangle(arr[0], arr[1], arr[2], arr[3]); } @Override public String objectToString(Rectangle obj) { if (obj == null) return null; Rectangle rect = obj; int[] value = new int[] { rect.x, rect.y, rect.width, rect.height }; return INT_ARRAY_HELPER.objectToString(value); } } static class DimensionHelper extends TypeHelper { @Override public Dimension stringToObject(String str) throws Exception { if (str == null) return null; int[] arr = INT_ARRAY_HELPER.stringToObject(str); return new Dimension(arr[0], arr[1]); } @Override public String objectToString(Dimension obj) { if (obj == null) return null; Dimension dim = obj; int[] value = new int[] { dim.width, dim.height }; return INT_ARRAY_HELPER.objectToString(value); } } static class ColorHelper extends TypeHelper { @Override public Color stringToObject(String str) throws Exception { if (str == null) return null; int rgb = Integer.parseInt(str, 16); return new Color(rgb); } @Override public String objectToString(Color obj) { if (obj == null) return null; Color col = obj; int rgb = col.getRGB(); return Integer.toHexString(rgb); } } static class EnumHelper> extends TypeHelper { private T[] enumValues; public EnumHelper(Class enumClass) { enumValues = enumClass.getEnumConstants(); } @Override public T stringToObject(String str) { if (str == null) return null; for (T e : enumValues) { if (str.equals(e.name())) return e; } return null; } @Override public String objectToString(T e) { if (e == null) return null; return e.name(); } } /** * Helper used to manage strings representing file path.
* It's a special case because the file path can be represented on two forms: absolute or relative. As we want that in the saved * property file, all paths are relative (to this property file location) so it can easily be moved and that the application * used absolute paths (so it has not to bother with base directory) we will convert from realative to absolute paths in this * helper. The 'string' is the relative path, the 'object' the absolute path. * * @author Olivier Berlanger */ class FileHelper extends TypeHelper { @Override public String stringToObject(String str) throws InvalidPathException { return getFilePathUtils().getAbsoluteFilePath(str); } @Override public String objectToString(String obj) { try { return getFilePathUtils().getRelativeFilePath(obj); } catch (InvalidPathException e) { throw new IllegalArgumentException("Invalid path " + obj, e); } } } // -------------------------- class for holding cached property values ---------------------------------------------------- /** * Holder object holding a typed value and the type helper identifying the data type. * * @author Olivier Berlanger */ static class TypedValueHolder { /** The type helper identifying the data type. */ private TypeHelper helper; /** Typed value of a property. */ private T value; /** * Construct a new type holder for a given data type. * * @param typHelper * The type helper identifying the data type. */ TypedValueHolder(TypeHelper typHelper) { helper = typHelper; } /** * Get the typed value of this holder. * * @param typHelper * The requested value type. (it will be compared to the type helper of this holder to ensure that there is no * type mismatch). * @return The typed value of this holder. * @exception IllegalArgumentException * if the requested type is not the current type of this holder. */ public T getValue(TypeHelper typHelper) { if ((typHelper != null) && (typHelper != helper)) throw new IllegalArgumentException("Wrong Setting type: <" + typHelper + "> type <" + helper + "> expected for value '" + value + "'"); return value; } /** * Set a new typed value in this holder. * * @param typHelper * The set value type. (it will be compared to the type helper of this holder to ensure that there is no type * mismatch). * @param val * The new typed value for this holder. * @exception IllegalArgumentException * if the set type is not the current type of this holder. */ public void setValue(TypeHelper typHelper, T val) { if (typHelper != helper) throw new IllegalArgumentException("Wrong Setting type: <" + typHelper + "> type <" + helper + "> expected for value '" + val + "'"); value = val; } /** * Set a new string value in this holder. The string value will be converted to the correct type using the helper. * * @param stringVal * a new string value for this holder. * @exception Exception * if the conversion failed in the helper. */ public void setStringValue(String stringVal) throws Exception { value = helper.stringToObject(stringVal); } /** * Get the value of this holder as a string. The typed value will be converted to string using the helper. * * @return The value of this holder as a string. */ public String getStringValue() { return helper.objectToString(value); } } // ------------------ PropertyChangeListener support------------------------------------------------- /** * Add a PropertyChangeListener for a specific setting. * * @param settingKey * The key of the setting to listen on. * @param listener * The PropertyChangeListener to be added */ public void addPropertyChangeListener(String settingKey, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(settingKey, listener); } /** * Remove a PropertyChangeListener for a specific setting. * * @param settingKey * The key of the setting that was listened on. * @param listener * The PropertyChangeListener to be removed */ public void removePropertyChangeListener(String settingKey, PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(settingKey, listener); } // -------------------------- Ordered properties ---------------------------------------------------- static class OrderedProperties extends Properties { private static final long serialVersionUID = 5669742785337439143L; /** * Note: this is impossible to get right because properties declare to contains Object keys but effectively contains only * String keys (and Objects are not Comparable). So, I'm forced to cast to raw types. */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public synchronized Enumeration keys() { Vector keys = new Vector((Set) keySet()); Collections.sort(keys); return (Enumeration) keys.elements(); } } }