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 { getMappedKey(final String key)39 String getMappedKey (final String key); 40 41 } // end of nested interface 42 43 getProperty(String key)44 String getProperty (String key); getProperty(String key, String dflt)45 String getProperty (String key, String dflt); isOverridden(String key)46 boolean isOverridden (String key); 47 copy()48 IProperties copy (); properties()49 Iterator /* String */ properties (); toProperties()50 Properties toProperties (); 51 /** 52 * @param prefix [may not be null] 53 */ toAppArgsForm(final String prefix)54 String [] toAppArgsForm (final String prefix); isEmpty()55 boolean isEmpty (); list(PrintStream out)56 void list (PrintStream out); list(PrintWriter out)57 void list (PrintWriter out); 58 setProperty(String key, String value)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 */ create(final IMapper mapper)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 */ wrap(final Properties properties, final IMapper mapper)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 */ combine(final IProperties overrides, final IProperties base)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 getProperty(final String key)132 public String getProperty (final String key) 133 { 134 return getProperty (key, null); 135 } 136 getProperty(final String key, final String dflt)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 isOverridden(final String key)159 public boolean isOverridden (final String key) 160 { 161 return m_valueMap.containsKey (key); 162 } 163 copy()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 properties()208 public Iterator /* String */ properties () 209 { 210 return unmappedKeySet ().iterator (); 211 } 212 toProperties()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 isEmpty()228 public boolean isEmpty () 229 { 230 return m_valueMap.isEmpty () && ((m_delegate == null) || ((m_delegate != null) && m_delegate.isEmpty ())); 231 } 232 toAppArgsForm(final String prefix)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 list(final PrintStream out)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 list(final PrintWriter out)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 setProperty(final String key, final String value)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 PropertiesImpl(final HashMap values, final IMapper mapper)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 unmappedKeySet()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 getLastProperties()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 setDelegate(final PropertiesImpl delegate)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 // ----------------------------------------------------------------------------