| /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under |
| * the terms of the Common Public License v1.0 which accompanies this distribution, |
| * and is available at http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * $Id: IProperties.java,v 1.1.1.1.2.1 2004/07/08 10:52:10 vlad_r Exp $ |
| */ |
| package com.vladium.util; |
| |
| import java.io.PrintStream; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| // ---------------------------------------------------------------------------- |
| /** |
| * @author Vlad Roubtsov, (C) 2003 |
| */ |
| public |
| interface IProperties |
| { |
| // public: ................................................................ |
| |
| /** |
| * An IMapper is a stateless hook for mapping a arbitrary property key |
| * to another (useful, for example, for property aliasing and defaulting). |
| * Each IMapper must be completely stateless and could be shared between |
| * multiple IProperties instances (and invoked from multiple concurrent threads). |
| */ |
| interface IMapper |
| { |
| String getMappedKey (final String key); |
| |
| } // end of nested interface |
| |
| |
| String getProperty (String key); |
| String getProperty (String key, String dflt); |
| boolean isOverridden (String key); |
| |
| IProperties copy (); |
| Iterator /* String */ properties (); |
| Properties toProperties (); |
| /** |
| * @param prefix [may not be null] |
| */ |
| String [] toAppArgsForm (final String prefix); |
| boolean isEmpty (); |
| void list (PrintStream out); |
| void list (PrintWriter out); |
| |
| String setProperty (String key, String value); |
| |
| |
| abstract class Factory |
| { |
| /** |
| * Creates an empty IProperties set with an optional property mapper. |
| * |
| * @param mapper [may be null] |
| * @return an empty property set [never null] |
| */ |
| public static IProperties create (final IMapper mapper) |
| { |
| return new PropertiesImpl (null, mapper); |
| } |
| |
| /** |
| * Converts a java.util.Properties instance to an IProperties instance, |
| * with an optional property mapper. Note that 'properties' content is |
| * shallowly cloned, so the result can be mutated independently from |
| * the input. |
| * |
| * @param properties [may not be null] |
| * @param mapper [may be null] |
| * @return a property set based on 'properties' [never null] |
| */ |
| public static IProperties wrap (final Properties properties, final IMapper mapper) |
| { |
| final HashMap map = new HashMap (); |
| |
| // always use propertyNames() for traversing java.util.Properties: |
| |
| for (Enumeration names = properties.propertyNames (); names.hasMoreElements (); ) |
| { |
| final String n = (String) names.nextElement (); |
| final String v = properties.getProperty (n); |
| |
| map.put (n, v); |
| } |
| |
| return new PropertiesImpl (map, mapper); // note: map is a defensive clone |
| } |
| |
| /** |
| * Combines two property sets by creating a property set that chains 'overrides' |
| * to 'base' for property delegation. Note that 'overrides' are cloned |
| * defensively and so the result can be mutated independently of both inputs. |
| * |
| * @param overrides [may be null] |
| * @param base [may be null] |
| * @return [never null; an empty property set with a null mapper is created |
| * if both inputs are null] |
| */ |
| public static IProperties combine (final IProperties overrides, final IProperties base) |
| { |
| final IProperties result = overrides != null |
| ? overrides.copy () |
| : create (null); |
| |
| // [assertion: result != null] |
| |
| ((PropertiesImpl) result).getLastProperties ().setDelegate ((PropertiesImpl) base); |
| |
| return result; |
| } |
| |
| /* |
| * Not MT-safe |
| */ |
| private static final class PropertiesImpl implements IProperties, Cloneable |
| { |
| // ACCESSORS (IProperties): |
| |
| public String getProperty (final String key) |
| { |
| return getProperty (key, null); |
| } |
| |
| public String getProperty (final String key, final String dflt) |
| { |
| String value = (String) m_valueMap.get (key); |
| |
| // first, try to delegate horizontally: |
| if ((value == null) && (m_mapper != null)) |
| { |
| final String mappedKey = m_mapper.getMappedKey (key); |
| |
| if (mappedKey != null) |
| value = (String) m_valueMap.get (mappedKey); |
| } |
| |
| // next, try to delegate vertically: |
| if ((value == null) && (m_delegate != null)) |
| { |
| value = m_delegate.getProperty (key, null); |
| } |
| |
| return value != null ? value : dflt; |
| } |
| |
| public boolean isOverridden (final String key) |
| { |
| return m_valueMap.containsKey (key); |
| } |
| |
| public IProperties copy () |
| { |
| final PropertiesImpl _clone; |
| try |
| { |
| _clone = (PropertiesImpl) super.clone (); |
| } |
| catch (CloneNotSupportedException cnse) |
| { |
| throw new Error (cnse.toString ()); // never happens |
| } |
| |
| _clone.m_valueMap = (HashMap) m_valueMap.clone (); |
| _clone.m_unmappedKeySet = null; |
| |
| // note: m_mapper field is not cloned by design (mappers are stateless/shareable) |
| |
| PropertiesImpl scan = _clone; |
| for (PropertiesImpl delegate = m_delegate; delegate != null; delegate = delegate.m_delegate) |
| { |
| final PropertiesImpl _delegateClone; |
| try |
| { |
| _delegateClone = (PropertiesImpl) delegate.clone (); |
| } |
| catch (CloneNotSupportedException cnse) |
| { |
| throw new Error (cnse.toString ()); // never happens |
| } |
| |
| // note that the value map needs to be cloned not only for the |
| // outermost IProperties set but for the inner ones as well |
| // (to prevent independent mutation in them from showing through) |
| |
| _delegateClone.m_valueMap = (HashMap) delegate.m_valueMap.clone (); |
| _delegateClone.m_unmappedKeySet = null; // ensure that the last delegate will have this field reset |
| |
| scan.setDelegate (_delegateClone); |
| scan = _delegateClone; |
| } |
| |
| return _clone; |
| } |
| |
| public Iterator /* String */ properties () |
| { |
| return unmappedKeySet ().iterator (); |
| } |
| |
| public Properties toProperties () |
| { |
| final Properties result = new Properties (); |
| |
| for (Iterator i = properties (); i.hasNext (); ) |
| { |
| final String n = (String) i.next (); |
| final String v = getProperty (n); |
| |
| result.setProperty (n, v); |
| } |
| |
| return result; |
| } |
| |
| public boolean isEmpty () |
| { |
| return m_valueMap.isEmpty () && ((m_delegate == null) || ((m_delegate != null) && m_delegate.isEmpty ())); |
| } |
| |
| public String [] toAppArgsForm (final String prefix) |
| { |
| if (isEmpty ()) |
| return IConstants.EMPTY_STRING_ARRAY; |
| else |
| { |
| if (prefix == null) |
| throw new IllegalArgumentException ("null input: prefix"); |
| |
| final List _result = new ArrayList (); |
| for (Iterator names = properties (); names.hasNext (); ) |
| { |
| final String name = (String) names.next (); |
| final String value = getProperty (name, ""); |
| |
| _result.add (prefix.concat (name).concat ("=").concat (value)); |
| } |
| |
| final String [] result = new String [_result.size ()]; |
| _result.toArray (result); |
| |
| return result; |
| } |
| } |
| |
| public void list (final PrintStream out) |
| { |
| if (out != null) |
| { |
| for (Iterator i = properties (); i.hasNext (); ) |
| { |
| final String n = (String) i.next (); |
| final String v = getProperty (n); |
| |
| out.println (n + ":\t[" + v + "]"); |
| } |
| } |
| } |
| |
| public void list (final PrintWriter out) |
| { |
| if (out != null) |
| { |
| for (Iterator i = properties (); i.hasNext (); ) |
| { |
| final String n = (String) i.next (); |
| final String v = getProperty (n); |
| |
| out.println (n + ":\t[" + v + "]"); |
| } |
| } |
| } |
| |
| // MUTATORS: |
| |
| public String setProperty (final String key, final String value) |
| { |
| if (value == null) |
| throw new IllegalArgumentException ("null input: value"); |
| |
| m_unmappedKeySet = null; |
| |
| return (String) m_valueMap.put (key, value); |
| } |
| |
| |
| PropertiesImpl (final HashMap values, final IMapper mapper) |
| { |
| m_mapper = mapper; |
| m_valueMap = values != null ? values : new HashMap (); |
| |
| m_delegate = null; |
| } |
| |
| |
| Set unmappedKeySet () |
| { |
| Set result = m_unmappedKeySet; |
| if (result == null) |
| { |
| result = new TreeSet (); |
| result.addAll (m_valueMap.keySet ()); |
| if (m_delegate != null) |
| result.addAll (m_delegate.unmappedKeySet ()); |
| |
| m_unmappedKeySet = result; |
| return result; |
| } |
| |
| return result; |
| } |
| |
| // ACCESSORS: |
| |
| PropertiesImpl getLastProperties () |
| { |
| PropertiesImpl result = this; |
| |
| for (PropertiesImpl delegate = m_delegate; delegate != null; delegate = delegate.m_delegate) |
| { |
| // this does not detect all possible cycles: |
| if (delegate == this) |
| throw new IllegalStateException ("cyclic delegation detected"); |
| |
| result = delegate; |
| } |
| |
| return result; |
| } |
| |
| // MUTATORS: |
| |
| void setDelegate (final PropertiesImpl delegate) |
| { |
| m_delegate = delegate; |
| |
| m_unmappedKeySet = null; |
| } |
| |
| |
| private final IMapper m_mapper; |
| private /*final*/ HashMap m_valueMap; // never null |
| |
| private PropertiesImpl m_delegate; |
| private transient Set m_unmappedKeySet; |
| |
| } // end of nested class |
| |
| } // end of nested class |
| |
| // protected: ............................................................. |
| |
| // package: ............................................................... |
| |
| // private: ............................................................... |
| |
| } // end of class |
| // ---------------------------------------------------------------------------- |