Index: lutinutil/src/java/org/codelutin/util/MyProperties.java diff -u /dev/null lutinutil/src/java/org/codelutin/util/MyProperties.java:1.1 --- /dev/null Sun Dec 9 20:01:47 2007 +++ lutinutil/src/java/org/codelutin/util/MyProperties.java Sun Dec 9 20:01:42 2007 @@ -0,0 +1,593 @@ +/* +* ##% Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Code Lutin, +* Benjamin Poussin, Tony Chemit, and others +* +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* ##% */ +package org.codelutin.util; + +import org.apache.commons.beanutils.ConvertUtils; +import static org.codelutin.i18n.I18n._; +import org.codelutin.util.MyPropertiesUtil.MyPropertyDef; +import org.codelutin.util.MyPropertiesUtil.MyPropertyKey; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * This class encapsulate a {@link Properties} object with more + * friendly methods to obtain or set some valued values automaticly. + *

+ * The class is parametrized by the class of an enum which contains keys to use. + *

+ * And more the typed class must also be a {@link MyPropertyKey} class. + *

+ * Use Factory methods {@link #newInstance(Class)} and variants one. + *

+ * Date: 8 déc. 2007 + * + * @author tony + * @see org.codelutin.util.MyPropertiesUtil.MyPropertyKey + */ +public class MyProperties> { + + public static > MyProperties newInstance(Class keyType) { + MyProperties myProperties; + myProperties = new MyProperties(keyType); + return myProperties; + } + + public static > MyProperties newInstance(Class keyType, Properties data) { + MyProperties myProperties = newInstance(keyType); + myProperties.load(data); + return myProperties; + } + + public static > MyProperties newInstance(Class keyType, Reader reader) throws IOException { + MyProperties myProperties = newInstance(keyType); + myProperties.load(reader); + return myProperties; + } + + public static > MyProperties newInstance(Class keyType, InputStream inStream) throws IOException { + MyProperties myProperties = newInstance(keyType); + myProperties.load(inStream); + return myProperties; + } + + public static > MyProperties newInstanceFromXml(Class keyType, InputStream inStream) throws IOException { + MyProperties myProperties = newInstance(keyType); + myProperties.loadFromXML(inStream); + return myProperties; + } + + /** the internal Properties object used for io operations */ + protected Properties tmp; + + /** the internal map of data indexed by their key */ + protected SortedMap data; + + /** the internal map of rejected data indexed by their key found */ + protected SortedMap unsafeData; + + /** the type of key to use, this class must also implements {@link MyPropertyDef} */ + protected final Class keyType; + + /** the set of all authorized keys */ + protected EnumSet universe; + + /** the current set of registred keys */ + protected EnumSet keys; + + /** key manager */ + protected MyPropertyKeyManager manager; + + public Class getKeyType() { + return keyType; + } + + public EnumSet getUniverse() { + return universe; + } + + public EnumSet getKeys() { + return keys; + } + + public EnumSet getMissingKeys() { + return EnumSet.complementOf(keys); + } + + public boolean isEmpty() { + return size() == 0; + } + + public boolean isFull() { + return !isEmpty() && getMissingKeys().isEmpty(); + } + + public boolean isSafe() { + return unsafeData == null || unsafeData.isEmpty(); + } + + public int size() { + return data == null ? 0 : data.size(); + } + + /** + * Obtain the value for a given key, with no default value + * if not found. + * + * @param key the key to use + * @return the value for the given key, or null if not found + */ + public Object getProperty(T key) { + return getProperty(key, null); + } + + /** + * Obtain the value for a given key,or defaultValue + * if not found. + * + * @param key the key to use + * @param defaultValue default value to return if not found + * @return the value for the given key, or null if not found + */ + public Object getProperty(T key, Object defaultValue) { + checkAuthorizedKey(key); + Class klass = getManager().getType((MyPropertyDef) key); + if (!containsKey(key)) { + return defaultValue; + } + Object result; + result = getData().get(key); + return result; + } + + /** + * Set safely a value for a given key. + * + * @param key the key to use + * @param value the value to set + * @see #setProperty(Enum, Object, boolean) + */ + public void setProperty(T key, Object value) { + setProperty(key, value, true); + } + + /** + * @param key the given key + * @return true if key is legal and used + * ,false otherwise. + */ + public boolean containsKey(T key) { + return universe.contains(key) && keys.contains(key); + } + + /** + * @param key the given unsafe key, this is the original key of unsafe + * property (if a load was done), this is not connected with MyPropertyKey. + * @return the value of the given unsafe value, or null if there is not a + * such unsafe property. + */ + public Object getUnsafeData(Object key) { + if (isSafe()) { + return null; + } + Object result; + result = getUnsafeData().get(key); + return result; + } + + /** + * Remove a unsafe property using a given unsage key, if there is a such + * unsafe property. + * + * @param key the key to use + */ + public void removeUnsafeData(Object key) { + if (isSafe()) { + return; + } + getUnsafeData().remove(key); + } + + /** + * clear the instance (data, tmp,unsafeData and keys). + *

+ * After the method invocation, the {@link #isEmpty()}'s method's return is + * always true. + */ + public void clear() { + if (data != null) { + data.clear(); + } + if (tmp != null) { + tmp.clear(); + } + if (unsafeData != null) { + unsafeData.clear(); + } + keys.clear(); + } + + /** + * Load some properties from a given {@link Properties} object. + * + * @param data the data to inject + * @return the current instance + */ + public MyProperties load(Properties data) { + if (data == null || data.isEmpty()) { + return this; + } + getTmp().putAll(data); + updateUniverse(); + return this; + } + + /** + * Load some properties from a given Properties files via a {@link Reader} object. + * + * @param reader the data to inject + * @return the current instance + * @throws java.io.IOException if problem while grabbing data. + */ + public MyProperties load(Reader reader) throws IOException { + if (reader == null) { + return this; + } + getTmp().load(reader); + updateUniverse(); + return this; + } + + /** + * Load some properties from a given properties files via a {@link InputStream} object. + * + * @param inputStream the data to inject + * @return the current instance + * @throws java.io.IOException if problem while grabbing data. + */ + public MyProperties load(InputStream inputStream) throws IOException { + if (inputStream == null) { + return this; + } + getTmp().load(inputStream); + updateUniverse(); + return this; + } + + /** + * Load some properties from a given xml properties file via a {@link InputStream} object. + * + * @param inputStream the data to inject + * @return the current instance + * @throws java.io.IOException if problem while grabbing data. + */ + public MyProperties loadFromXML(InputStream inputStream) throws IOException { + if (inputStream == null) { + return this; + } + getTmp().loadFromXML(inputStream); + updateUniverse(); + return this; + } + + /** + * Try to fill the cuurent instance missing properties using the given + * otherProperties object. + * + * @param otherProperties the other properties object to use + * @return the current instance + */ + public MyProperties fillMissingKeys(MyProperties otherProperties) { + for (T key : getMissingKeys()) { + Object value = otherProperties.getProperty(key); + setProperty(key, value); + } + return this; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(super.toString()).append("'); + if (!isEmpty()) { + for (T key : keys) { + MyPropertyKey entry = getManager().getEntry((MyPropertyDef) key, false); + s.append('\n').append(entry).append(" = ").append(getData().get(key)); + } + } + if (!isSafe()) { + s.append("\nunsafe:"); + for (Map.Entry entry : unsafeData.entrySet()) { + s.append('\n').append(entry.getKey()).append(" = ").append(entry.getValue()); + } + } + return s.toString(); + } + + /** @return the lazy manager instance */ + protected MyPropertyKeyManager getManager() { + if (manager == null) { + manager = MyPropertyKeyManager.getInstance(); + } + return manager; + } + + /** @return the lazy tmp instance */ + protected Properties getTmp() { + if (tmp == null) { + tmp = new Properties(); + } + return tmp; + } + + /** @return the lazy data instance */ + protected Map getData() { + if (data == null) { + data = new TreeMap(); + } + return data; + } + + /** @return the lazy unsafeData instance */ + protected Map getUnsafeData() { + if (unsafeData == null) { + unsafeData = new TreeMap(); + } + return unsafeData; + } + + /** + * @param def def to use + * @return the {@link MyPropertyKey#getKey()} value for the given entry + * associated to the given def + */ + protected String getKey(T def) { + return getManager().getKey((MyPropertyDef) def); + } + + /** + * @param def def to use + * @return the {@link MyPropertyKey#getType()} value for the given entry + * associated to the given def + */ + protected Class getType(T def) { + return getManager().getType((MyPropertyDef) def); + } + + /** + * Test if a def is authorized in the current{@link #universe}. + *

+ * If not throw an {@link IllegalArgumentException}. + * + * @param def the given def to test + */ + protected void checkAuthorizedKey(T def) { + if (def == null || !universe.contains(def)) { + throw new IllegalArgumentException(_("lutinutil.error.config.unauthorized.key", def, universe.toString())); + } + } + + /** + * Set the value for a given property using his def. + * + * @param def the given key to use + * @param value the given valuer to set + * @param safe the flag to say do it safely or not (if safe then it + * will throw exceptions if found errors). + */ + protected void setProperty(T def, Object value, boolean safe) { + checkAuthorizedKey(def); + if (value == null) { + removeProperty(def); + return; + } + MyPropertyDef def2 = (MyPropertyDef) def; + MyPropertyKey entry = getManager().getEntry(def2, true); + Class klass = entry.getType(); + + if (klass == String.class) { + addProperty(def, String.valueOf(value)); + return; + } + + if (value instanceof String) { + // make a conversion from String + Object typeValue = convert(entry.getType(), value); + if (typeValue == null) { + addUnsafeData(entry.getKey(), klass, value); + } else { + addProperty(def, typeValue); + } + return; + } + + if (value.getClass() == klass) { + // add a matching typed value + addProperty(def, value); + return; + } + + Object value2 = value; + if (klass.isEnum()) { + // try an Enum conversion from ordinal + value2 = ConvertUtils.lookup(klass).convert(klass, value); + if (value2 != null && value2.getClass() == klass) { + // add a matching typed value after conversion + addProperty(def, value2); + } + } + if (value2 == null) { + addUnsafeData(entry.getKey(), klass, value); + } + } + + /** + * Update unvierse after a load(XXX) method. + *

+ * The data to inject are stored in the {@link #tmp}. + */ + protected void updateUniverse() { + if (tmp == null || tmp.isEmpty()) { + return; + } + try { + // tmp contains new data to digest + for (Object o : tmp.keySet()) { + String kStr = o + ""; + T def = getEnumName(kStr); + Object value = tmp.get(o); + MyPropertyKey key = getManager().getEntry((MyPropertyDef) def, false); + if (def != null && key != null && universe.contains(def)) { + setProperty(def, value, false); + } else { + addUnsafeData(kStr, key == null ? null : key.getType(), value); + } + } + } finally { + tmp.clear(); + } + } + + /** + * Set a value for a given property using his def and update + * also the list of keys used. + * + * @param def the def to use + * @param value the value to set + */ + protected void addProperty(T def, Object value) { + getData().put(def, value); + if (!keys.contains(def)) { + keys.add(def); + } + } + + /** + * Remove a given property using his def and update + * also the list of keys used. + * + * @param def the def to use + */ + protected void removeProperty(T def) { + getData().remove(def); + if (keys.contains(def)) { + keys.remove(def); + } + } + + /** + * Set a unsafe property. + * + * @param key unsafe key + * @param klass the unsafe property value's type + * @param value the unsafe property value to set + */ + protected void addUnsafeData(Object key, Class klass, Object value) { + getUnsafeData().put(key, value); + } + + + /** + * Convert the given o object to the type klass + * + * @param klass the class of value to extract + * @param o the original value + * @return the converted value + */ + protected Object convert(Class klass, Object o) { + if (o == null) { + return null; + } + Object result; + if (klass == String.class) { + result = o; + } else { + if (o.getClass() == String.class) { + result = ConvertUtils.convert(String.valueOf(o), klass); + } else { + result = o; + } + } + if (result != null && !klass.isAssignableFrom(result.getClass())) { + result = null; + } + return result; + } + + /** + * @param kStr the key to used + * @return the MyPropertyKey instance for the + * {@link MyPropertyKey#key} for the given key, or null if not found. + */ + protected T getEnumName(String kStr) { + for (T t : universe) { + MyPropertyKey entry = getManager().getEntry((MyPropertyDef) t, false); + if (entry.getKey().equals(kStr)) { + return t; + } + } + return null; + } + + /** + * Instanciate an empty instance. + *

+ * Private constructor, use Factory newInstance(XXX) + * methods instead. + * + * @param keyType the type of key to use + */ + private MyProperties(Class keyType) { + if (!(MyPropertyDef.class.isAssignableFrom(keyType))) { + throw new IllegalArgumentException(_("lutinutil.error.config.unauthorized.type.key", keyType)); + } + this.keyType = keyType; + this.universe = EnumSet.allOf(this.keyType); + this.keys = EnumSet.noneOf(this.keyType); + this.manager = MyPropertyKeyManager.getInstance(); + } + + public MyPropertyKey getEntry(T def) { + return getManager().getEntry((MyPropertyDef) def, false); + } + + public java.util.List getUnsafeDataKeys() { + if (isSafe()) { + return Collections.emptyList(); + } + List result = new ArrayList(getUnsafeData().size()); + for (Object o : unsafeData.keySet()) { + result.add(o); + } + return result; + } +}