diff src/com/go/trove/util/PropertyMap.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/go/trove/util/PropertyMap.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,695 @@
+/* ====================================================================
+ * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
+ * ====================================================================
+ * The Tea Software License, Version 1.1
+ *
+ * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Walt Disney Internet Group (http://opensource.go.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact opensource@dig.com.
+ *
+ * 5. Products derived from this software may not be called "Tea",
+ *    "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
+ *    "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
+ *    written permission of the Walt Disney Internet Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * For more information about Tea, please see http://opensource.go.com/.
+ */
+
+package com.go.trove.util;
+
+import java.util.*;
+import java.io.Serializable;
+
+/******************************************************************************
+ * A class that is similar to {@link java.util.Properties} but preserves
+ * original property order and supports a special {@link #subMap} view
+ * operation. PropertyMap also has more convenience methods for getting
+ * properties as certain types.
+ *
+ * @author Brian S O'Neill
+ * @version
+ * <!--$$Revision: 1.1 $--> 4 <!-- $$JustDate:--> 01/02/26 <!-- $-->
+ */
+public class PropertyMap extends AbstractMap {
+    public static final Class ELEMENT_TYPE = String.class;
+
+    private static String internStr(String str) {
+        return (String)Utils.intern(str);
+    }
+
+    private Map mMappings;
+    private String mSeparator;
+    private String mPrefix;
+
+    private transient Set mSubMapKeySet;
+    private transient Set mEntrySet;
+
+    /**
+     * Construct a PropetyMap using a dot (".") separator.
+     */
+    public PropertyMap() {
+        this(null, ".");
+    }
+
+    /**
+     * @param map Map of defaults.
+     */
+    public PropertyMap(Map map) {
+        this(map, ".");
+    }
+
+    /**
+     * @param separator Sub-key separator, i.e. ".".
+     */
+    public PropertyMap(String separator) {
+        this(null, separator);
+    }
+
+    /**
+     * @param map Optional map of defaults.
+     * @param separator Sub-key separator, i.e. ".".
+     *
+     * @see #putDefaults(Map)
+     */
+    public PropertyMap(Map map, String separator) {
+        UsageMap usageMap = new UsageMap();
+        usageMap.setReverseOrder(true);
+        mMappings = usageMap;
+        mSeparator = separator;
+        if (map != null) {
+            putAll(map);
+        }
+    }
+
+    private PropertyMap(String prefix, PropertyMap source) {
+        mMappings = source;
+        mSeparator = source.getSeparator();
+        mPrefix = prefix;
+    }
+
+    public String getSeparator() {
+        return mSeparator;
+    }
+
+    /**
+     * Returns a view of this map for keys that are the same as the given key,
+     * or start with it (and a separator). The names of the keys in the
+     * sub-map have their prefix truncated.
+     *
+     * <p>A sub-map of
+     *
+     * <pre>
+     * "x" = "a"
+     * "foo" = "b"
+     * "foo." = "c"
+     * "foo.bar" = "d"
+     * "foo.bar.splat" = "e"
+     * "foo..bar" = "f"
+     * ".foo" = "g"
+     * "" = "h"
+     * null = "i"
+     * </pre>
+     * 
+     * using a key of "foo" results in
+     *
+     * <pre>
+     * null = "b"
+     * "" = "c"
+     * "bar" = "d"
+     * "bar.splat" = "e"
+     * ".bar" = "f"
+     * </pre>
+     *
+     * using a key of "x" results in
+     *
+     * <pre>
+     * null = "a"
+     * </pre>
+     *
+     * using a key of "" results in
+     *
+     * <pre>
+     * "foo" = "g"
+     * null = "h"
+     * </pre>
+     *
+     * and using a key of null results in the original map.
+     */
+    public PropertyMap subMap(String key) {
+        if (key == null) {
+            return this;
+        }
+        else {
+            return new PropertyMap(key, this);
+        }
+    }
+
+    /**
+     * Returns the key names of each sub-map in this PropertyMap. The returned
+     * set is unmodifiable.
+     */
+    public Set subMapKeySet() {
+        if (mSubMapKeySet == null) {
+            mSubMapKeySet = new SubMapKeySet();
+        }
+        return mSubMapKeySet;
+    }
+
+    public int size() {
+        if (mPrefix == null) {
+            return mMappings.size();
+        }
+        else {
+            return super.size();
+        }
+    }
+
+    public boolean isEmpty() {
+        if (mPrefix == null) {
+            return mMappings.isEmpty();
+        }
+        else {
+            return super.isEmpty();
+        }
+    }
+    
+    public boolean containsKey(Object key) {
+        if (key == null) {
+            return containsKey((String)null);
+        }
+        else {
+            return containsKey(key.toString());
+        }
+    }
+    
+    public boolean containsKey(String key) {
+        if (mPrefix == null) {
+            return mMappings.containsKey(internStr(key));
+        }
+        else {
+            return mMappings.containsKey(mPrefix + mSeparator + key);
+        }
+    }
+    
+    public Object get(Object key) {
+        if (key == null) {
+            return get((String)null);
+        }
+        else {
+            return get(key.toString());
+        }
+    }
+
+    public Object get(String key) {
+        if (mPrefix == null) {
+            return mMappings.get(internStr(key));
+        }
+        else {
+            return mMappings.get(mPrefix + mSeparator + key);
+        }
+    }
+
+    /**
+     * Returns null if the given key isn't in this PropertyMap.
+     *
+     * @param key Key of property to read
+     */
+    public String getString(String key) {
+        Object value = get(key);
+        if (value == null) {
+            return null;
+        }
+        else {
+            return value.toString();
+        }
+    }
+    
+    /**
+     * Returns the default value if the given key isn't in this PropertyMap.
+     *
+     * @param key Key of property to read
+     * @param def Default value
+     */
+    public String getString(String key, String def) {
+        Object value = get(key);
+        if (value == null) {
+            return def;
+        }
+        else {
+            return value.toString();
+        }
+    }
+    
+    /**
+     * Returns 0 if the given key isn't in this PropertyMap.
+     *
+     * @param key Key of property to read
+     */
+    public int getInt(String key) throws NumberFormatException {
+        String value = getString(key);
+        if (value == null) {
+            return 0;
+        }
+        else {
+            return Integer.parseInt(value);
+        }
+    }
+
+    /**
+     * Returns the default value if the given key isn't in this PropertyMap or
+     * isn't a valid integer.
+     *
+     * @param key Key of property to read
+     * @param def Default value
+     */
+    public int getInt(String key, int def) {
+        String value = getString(key);
+        if (value == null) {
+            return def;
+        }
+        else {
+            try {
+                return Integer.parseInt(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            return def;
+        }
+    }
+
+    /**
+     * Returns null if the given key isn't in this PropertyMap or it isn't a
+     * valid integer.
+     *
+     * @param key Key of property to read
+     */
+    public Integer getInteger(String key) {
+        return getInteger(key, null);
+    }
+
+    /**
+     * Returns the default value if the given key isn't in this PropertyMap or
+     * it isn't a valid integer.
+     *
+     * @param key Key of property to read
+     * @param def Default value
+     */
+    public Integer getInteger(String key, Integer def) {
+        String value = getString(key);
+        if (value == null) {
+            return def;
+        }
+        else {
+            try {
+                return Integer.valueOf(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            return def;
+        }
+    }
+
+    /**
+     * Returns null if the given key isn't in this PropertyMap.
+     *
+     * @param key Key of property to read
+     */
+    public Number getNumber(String key) throws NumberFormatException {
+        String value = getString(key);
+        if (value == null) {
+            return null;
+        }
+        else {
+            try {
+                return Integer.valueOf(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            try {
+                return Long.valueOf(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            return Double.valueOf(value);
+        }
+    }
+
+    /**
+     * Returns the default value if the given key isn't in this PropertyMap or
+     * isn't a valid number.
+     *
+     * @param key Key of property to read
+     * @param def Default value
+     */
+    public Number getNumber(String key, Number def) {
+        String value = getString(key);
+        if (value == null) {
+            return def;
+        }
+        else {
+            try {
+                return Integer.valueOf(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            try {
+                return Long.valueOf(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            try {
+                return Double.valueOf(value);
+            }
+            catch (NumberFormatException e) {
+            }
+            return def;
+        }
+    }
+
+    /**
+     * Returns true only if value is "true", ignoring case.
+     *
+     * @param key Key of property to read
+     */
+    public boolean getBoolean(String key) {
+        return "true".equalsIgnoreCase(getString(key));
+    }
+
+    /**
+     * Returns the default value if the given key isn't in this PropertyMap or
+     * if the the value isn't equal to "true", ignoring case.
+     *
+     * @param key Key of property to read
+     * @param def Default value
+     */
+    public boolean getBoolean(String key, boolean def) {
+        String value = getString(key);
+        if (value == null) {
+            return def;
+        }
+        else {
+            return "true".equalsIgnoreCase(value);
+        }
+    }
+
+    public Set entrySet() {
+        if (mPrefix == null) {
+            return mMappings.entrySet();
+        }
+        else {
+            if (mEntrySet == null) {
+                mEntrySet = new EntrySet();
+            }
+            return mEntrySet;
+        }
+    }
+
+    public Set keySet() {
+        if (mPrefix == null) {
+            return mMappings.keySet();
+        }
+        else {
+            return super.keySet();
+        }
+    }
+
+    public Collection values() {
+        if (mPrefix == null) {
+            return mMappings.values();
+        }
+        else {
+            return super.values();
+        }
+    }
+
+    /**
+     * The key is always converted to a String.
+     */
+    public Object put(Object key, Object value) {
+        if (key == null) {
+            return put((String)null, value);
+        }
+        else {
+            return put(key.toString(), value);
+        }
+    }
+
+    public Object put(String key, Object value) {
+        if (mPrefix == null) {
+            return mMappings.put(internStr(key), value);
+        }
+        else {
+            return mMappings.put(mPrefix + mSeparator + key, value);
+        }
+    }
+
+    /**
+     * Copies the entries of the given map into this one only for keys that
+     * aren't contained in this map. Is equivalent to putAll if this map is
+     * empty.
+     */
+    public void putDefaults(Map map) {
+        Iterator it = map.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry entry = (Map.Entry)it.next();
+            Object key = entry.getKey();
+            if (!containsKey(key)) {
+                put(key, entry.getValue());
+            }
+        }
+    }
+    
+    public Object remove(Object key) {
+        if (key == null) {
+            return remove((String)null);
+        }
+        else {
+            return remove(key.toString());
+        }
+    }
+    
+    public Object remove(String key) {
+        if (mPrefix == null) {
+            return mMappings.remove(internStr(key));
+        }
+        else {
+            return mMappings.remove(mPrefix + mSeparator + key);
+        }
+    }
+    
+    public void clear() {
+        if (mPrefix == null) {
+            mMappings.clear();
+        }
+        else {
+            super.clear();
+        }
+    }
+
+    private class SubMapKeySet extends AbstractSet {
+        public int size() {
+            int size = 0;
+            Iterator it = iterator();
+            while (it.hasNext()) {
+                it.next();
+                size++;
+            }
+            return size;
+        }
+        
+        public Iterator iterator() {
+            return new Iterator() {
+                final Iterator mIterator = keySet().iterator();
+                Set mSeen = new HashSet();
+                String mNext;
+
+                public boolean hasNext() {
+                    if (mNext != null) {
+                        return true;
+                    }
+
+                    String sep = mSeparator;
+
+                    while (mIterator.hasNext()) {
+                        String key = (String)mIterator.next();
+                        if (key != null) {
+                            int index = key.indexOf(sep);
+                            if (index >= 0) {
+                                String subKey = key.substring(0, index);
+                                if (!mSeen.contains(subKey)) {
+                                    mSeen.add(subKey);
+                                    mNext = subKey;
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+
+                    return false;
+                }
+
+                public Object next() {
+                    if (!hasNext()) {
+                        throw new NoSuchElementException();
+                    }
+                    else {
+                        Object next = mNext;
+                        mNext = null;
+                        return next;
+                    }
+                }
+
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+
+    private class EntrySet extends AbstractSet {
+        public int size() {
+            int size = 0;
+            Iterator it = iterator();
+            while (it.hasNext()) {
+                it.next();
+                size++;
+            }
+            return size;
+        }
+        
+        public boolean remove(Object obj) {
+            if (obj instanceof Map.Entry) {
+                Object key = ((Map.Entry)obj).getKey();
+                Object value = ((Map.Entry)obj).getValue();
+
+                if (PropertyMap.this.containsKey(key)) {
+                    Object v = PropertyMap.this.get(key);
+                    if (v == null) {
+                        if (value == null) {
+                            PropertyMap.this.remove(key);
+                            return true;
+                        }
+                    }
+                    else if (v.equals(value)) {
+                        PropertyMap.this.remove(key);
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+        
+        public void clear() {
+            Iterator it = iterator();
+            while (it.hasNext()) {
+                it.next();
+                it.remove();
+            }
+        }
+
+        public Iterator iterator() {
+            return new Iterator() {
+                final Iterator mIterator = mMappings.entrySet().iterator();
+                Map.Entry mNext;
+
+                public boolean hasNext() {
+                    if (mNext != null) {
+                        return true;
+                    }
+
+                    String prefix = mPrefix;
+                    String sep = mSeparator;
+
+                    while (mIterator.hasNext()) {
+                        final Map.Entry entry = (Map.Entry)mIterator.next();
+                        String key = (String)entry.getKey();
+
+                        if (key != null && key.startsWith(prefix)) {
+                            final String subKey;
+                            if (key.length() == prefix.length()) {
+                                subKey = null;
+                            }
+                            else {
+                                if (key.startsWith(sep, prefix.length())) {
+                                    subKey = key.substring
+                                        (prefix.length() + sep.length());
+                                }
+                                else {
+                                    continue;
+                                }
+                            }
+
+                            mNext = new AbstractMapEntry() {
+                                public Object getKey() {
+                                    return subKey;
+                                }
+
+                                public Object getValue() {
+                                    return entry.getValue();
+                                }
+                                
+                                public Object setValue(Object value) {
+                                    return put(subKey, value);
+                                }
+                            };
+
+                            return true;
+                        }
+                    }
+
+                    return false;
+                }
+
+                public Object next() {
+                    if (!hasNext()) {
+                        throw new NoSuchElementException();
+                    }
+                    else {
+                        Object next = mNext;
+                        mNext = null;
+                        return next;
+                    }
+                }
+
+                public void remove() {
+                    mIterator.remove();
+                }
+            };
+        }
+    }
+}