blob: 09bb84e4a18abb5b63d3befd764ade09e933f6b1 [file] [log] [blame]
/* 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
// ----------------------------------------------------------------------------