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 */
9package com.vladium.util;
10
11import java.io.PrintStream;
12import java.io.PrintWriter;
13import java.util.ArrayList;
14import java.util.Enumeration;
15import java.util.HashMap;
16import java.util.Iterator;
17import java.util.List;
18import java.util.Properties;
19import java.util.Set;
20import java.util.TreeSet;
21
22// ----------------------------------------------------------------------------
23/**
24 * @author Vlad Roubtsov, (C) 2003
25 */
26public
27interface 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// ----------------------------------------------------------------------------