Home | History | Annotate | Download | only in util
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: IProperties.java,v 1.1.1.1.2.1 2004/07/08 10:52:10 vlad_r Exp $
      8  */
      9 package com.vladium.util;
     10 
     11 import java.io.PrintStream;
     12 import java.io.PrintWriter;
     13 import java.util.ArrayList;
     14 import java.util.Enumeration;
     15 import java.util.HashMap;
     16 import java.util.Iterator;
     17 import java.util.List;
     18 import java.util.Properties;
     19 import java.util.Set;
     20 import java.util.TreeSet;
     21 
     22 // ----------------------------------------------------------------------------
     23 /**
     24  * @author Vlad Roubtsov, (C) 2003
     25  */
     26 public
     27 interface IProperties
     28 {
     29     // public: ................................................................
     30 
     31     /**
     32      * An IMapper is a stateless hook for mapping a arbitrary property key
     33      * to another (useful, for example, for property aliasing and defaulting).
     34      * Each IMapper must be completely stateless and could be shared between
     35      * multiple IProperties instances (and invoked from multiple concurrent threads).
     36      */
     37     interface IMapper
     38     {
     39         String getMappedKey (final String key);
     40 
     41     } // end of nested interface
     42 
     43 
     44     String getProperty (String key);
     45     String getProperty (String key, String dflt);
     46     boolean isOverridden (String key);
     47 
     48     IProperties copy ();
     49     Iterator /* String */ properties ();
     50     Properties toProperties ();
     51     /**
     52      * @param prefix [may not be null]
     53      */
     54     String [] toAppArgsForm (final String prefix);
     55     boolean isEmpty ();
     56     void list (PrintStream out);
     57     void list (PrintWriter out);
     58 
     59     String setProperty (String key, String value);
     60 
     61 
     62     abstract class Factory
     63     {
     64         /**
     65          * Creates an empty IProperties set with an optional property mapper.
     66          *
     67          * @param mapper [may be null]
     68          * @return an empty property set [never null]
     69          */
     70         public static IProperties create (final IMapper mapper)
     71         {
     72             return new PropertiesImpl (null, mapper);
     73         }
     74 
     75         /**
     76          * Converts a java.util.Properties instance to an IProperties instance,
     77          * with an optional property mapper. Note that 'properties' content is
     78          * shallowly cloned, so the result can be mutated independently from
     79          * the input.
     80          *
     81          * @param properties [may not be null]
     82          * @param mapper [may be null]
     83          * @return a property set based on 'properties' [never null]
     84          */
     85         public static IProperties wrap (final Properties properties, final IMapper mapper)
     86         {
     87             final HashMap map = new HashMap ();
     88 
     89             // always use propertyNames() for traversing java.util.Properties:
     90 
     91             for (Enumeration names = properties.propertyNames (); names.hasMoreElements (); )
     92             {
     93                 final String n = (String) names.nextElement ();
     94                 final String v = properties.getProperty (n);
     95 
     96                 map.put (n, v);
     97             }
     98 
     99             return new PropertiesImpl (map, mapper); // note: map is a defensive clone
    100         }
    101 
    102         /**
    103          * Combines two property sets by creating a property set that chains 'overrides'
    104          * to 'base' for property delegation. Note that 'overrides' are cloned
    105          * defensively and so the result can be mutated independently of both inputs.
    106          *
    107          * @param overrides [may be null]
    108          * @param base [may be null]
    109          * @return [never null; an empty property set with a null mapper is created
    110          * if both inputs are null]
    111          */
    112         public static IProperties combine (final IProperties overrides, final IProperties base)
    113         {
    114             final IProperties result = overrides != null
    115                 ? overrides.copy ()
    116                 : create (null);
    117 
    118             // [assertion: result != null]
    119 
    120             ((PropertiesImpl) result).getLastProperties ().setDelegate ((PropertiesImpl) base);
    121 
    122             return result;
    123         }
    124 
    125         /*
    126          * Not MT-safe
    127          */
    128         private static final class PropertiesImpl implements IProperties, Cloneable
    129         {
    130             // ACCESSORS (IProperties):
    131 
    132             public String getProperty (final String key)
    133             {
    134                 return getProperty (key, null);
    135             }
    136 
    137             public String getProperty (final String key, final String dflt)
    138             {
    139                 String value = (String) m_valueMap.get (key);
    140 
    141                 // first, try to delegate horizontally:
    142                 if ((value == null) && (m_mapper != null))
    143                 {
    144                     final String mappedKey = m_mapper.getMappedKey (key);
    145 
    146                     if (mappedKey != null)
    147                         value = (String) m_valueMap.get (mappedKey);
    148                 }
    149 
    150                 // next, try to delegate vertically:
    151                 if ((value == null) && (m_delegate != null))
    152                 {
    153                     value = m_delegate.getProperty (key, null);
    154                 }
    155 
    156                 return value != null ? value : dflt;
    157             }
    158 
    159             public boolean isOverridden (final String key)
    160             {
    161                 return m_valueMap.containsKey (key);
    162             }
    163 
    164             public IProperties copy ()
    165             {
    166                 final PropertiesImpl _clone;
    167                 try
    168                 {
    169                     _clone = (PropertiesImpl) super.clone ();
    170                 }
    171                 catch (CloneNotSupportedException cnse)
    172                 {
    173                     throw new Error (cnse.toString ()); // never happens
    174                 }
    175 
    176                 _clone.m_valueMap = (HashMap) m_valueMap.clone ();
    177                 _clone.m_unmappedKeySet = null;
    178 
    179                 // note: m_mapper field is not cloned by design (mappers are stateless/shareable)
    180 
    181                 PropertiesImpl scan = _clone;
    182                 for (PropertiesImpl delegate = m_delegate; delegate != null; delegate = delegate.m_delegate)
    183                 {
    184                     final PropertiesImpl _delegateClone;
    185                     try
    186                     {
    187                         _delegateClone = (PropertiesImpl) delegate.clone ();
    188                     }
    189                     catch (CloneNotSupportedException cnse)
    190                     {
    191                         throw new Error (cnse.toString ()); // never happens
    192                     }
    193 
    194                     // note that the value map needs to be cloned not only for the
    195                     // outermost IProperties set but for the inner ones as well
    196                     // (to prevent independent mutation in them from showing through)
    197 
    198                     _delegateClone.m_valueMap = (HashMap) delegate.m_valueMap.clone ();
    199                     _delegateClone.m_unmappedKeySet = null; // ensure that the last delegate will have this field reset
    200 
    201                     scan.setDelegate (_delegateClone);
    202                     scan = _delegateClone;
    203                 }
    204 
    205                 return _clone;
    206             }
    207 
    208             public Iterator /* String */ properties ()
    209             {
    210                 return unmappedKeySet ().iterator ();
    211             }
    212 
    213             public Properties toProperties ()
    214             {
    215                 final Properties result = new Properties ();
    216 
    217                 for (Iterator i = properties (); i.hasNext (); )
    218                 {
    219                     final String n = (String) i.next ();
    220                     final String v = getProperty (n);
    221 
    222                     result.setProperty (n, v);
    223                 }
    224 
    225                 return result;
    226             }
    227 
    228             public boolean isEmpty ()
    229             {
    230                 return m_valueMap.isEmpty () && ((m_delegate == null) || ((m_delegate != null) && m_delegate.isEmpty ()));
    231             }
    232 
    233             public String [] toAppArgsForm (final String prefix)
    234             {
    235                 if (isEmpty ())
    236                     return IConstants.EMPTY_STRING_ARRAY;
    237                 else
    238                 {
    239                     if (prefix == null)
    240                         throw new IllegalArgumentException ("null input: prefix");
    241 
    242                     final List _result = new ArrayList ();
    243                     for (Iterator names = properties (); names.hasNext (); )
    244                     {
    245                         final String name = (String) names.next ();
    246                         final String value = getProperty (name, "");
    247 
    248                         _result.add (prefix.concat (name).concat ("=").concat (value));
    249                     }
    250 
    251                     final String [] result = new String [_result.size ()];
    252                     _result.toArray (result);
    253 
    254                     return result;
    255                 }
    256             }
    257 
    258             public void list (final PrintStream out)
    259             {
    260                 if (out != null)
    261                 {
    262                     for (Iterator i = properties (); i.hasNext (); )
    263                     {
    264                         final String n = (String) i.next ();
    265                         final String v = getProperty (n);
    266 
    267                         out.println (n + ":\t[" + v + "]");
    268                     }
    269                 }
    270             }
    271 
    272             public void list (final PrintWriter out)
    273             {
    274                 if (out != null)
    275                 {
    276                     for (Iterator i = properties (); i.hasNext (); )
    277                     {
    278                         final String n = (String) i.next ();
    279                         final String v = getProperty (n);
    280 
    281                         out.println (n + ":\t[" + v + "]");
    282                     }
    283                 }
    284             }
    285 
    286             // MUTATORS:
    287 
    288             public String setProperty (final String key, final String value)
    289             {
    290                 if (value == null)
    291                     throw new IllegalArgumentException ("null input: value");
    292 
    293                 m_unmappedKeySet = null;
    294 
    295                 return (String) m_valueMap.put (key, value);
    296             }
    297 
    298 
    299             PropertiesImpl (final HashMap values, final IMapper mapper)
    300             {
    301                 m_mapper = mapper;
    302                 m_valueMap = values != null ? values : new HashMap ();
    303 
    304                 m_delegate = null;
    305             }
    306 
    307 
    308             Set unmappedKeySet ()
    309             {
    310                 Set result = m_unmappedKeySet;
    311                 if (result == null)
    312                 {
    313                     result = new TreeSet ();
    314                     result.addAll (m_valueMap.keySet ());
    315                     if (m_delegate != null)
    316                         result.addAll (m_delegate.unmappedKeySet ());
    317 
    318                     m_unmappedKeySet = result;
    319                     return result;
    320                 }
    321 
    322                 return result;
    323             }
    324 
    325             // ACCESSORS:
    326 
    327             PropertiesImpl getLastProperties ()
    328             {
    329                 PropertiesImpl result = this;
    330 
    331                 for (PropertiesImpl delegate = m_delegate; delegate != null; delegate = delegate.m_delegate)
    332                 {
    333                     // this does not detect all possible cycles:
    334                     if (delegate == this)
    335                         throw new IllegalStateException ("cyclic delegation detected");
    336 
    337                     result = delegate;
    338                 }
    339 
    340                 return result;
    341             }
    342 
    343             // MUTATORS:
    344 
    345             void setDelegate (final PropertiesImpl delegate)
    346             {
    347                 m_delegate = delegate;
    348 
    349                 m_unmappedKeySet = null;
    350             }
    351 
    352 
    353             private final IMapper m_mapper;
    354             private /*final*/ HashMap m_valueMap; // never null
    355 
    356             private PropertiesImpl m_delegate;
    357             private transient Set m_unmappedKeySet;
    358 
    359         } // end of nested class
    360 
    361     } // end of nested class
    362 
    363     // protected: .............................................................
    364 
    365     // package: ...............................................................
    366 
    367     // private: ...............................................................
    368 
    369 } // end of class
    370 // ----------------------------------------------------------------------------