/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Sep 2, 2005 */ package com.bigdata.util; import java.io.PrintStream; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.TreeMap; /** * This utility class provides helper methods designed to fuse two * configurations in which at least one of the configuration is * represented as a {@link Properties} object, which may have a system * of inherited defaults. When a {@link Map} and a {@link Properties} * object or two {@link Properties} objects must be combined in a * "fused" configuration, it may be necessary to "flatten" a {@link * Properties} object such that all inherited properties are placed * within a simple {@link Map}. This reduces the problem to fusing * two maps, which may be done using {@link Map#putAll( Map other * )}.<p> * * Under some circumstances, it may be desirable to report a conflict * which would otherwise be silently ignored by {@link Map#putAll( Map * other )}. A helper method has been provided to report such * conflicts rather than letting one {@link Map} override another.<p> */ public class PropertyUtil { /** * Return a flatten copy of the specified {@link Properties}. The returned * object does not share any structure with the source object, but it does * share key and value references. Since keys and values are {@link String}s * for a {@link Properties} instance this SHOULD NOT be a problem. The * behavior is equivalent to: * * <pre> * Properties tmp = new Properties(); * tmp.putAll(flatten(props)); * </pre> * * except that it handles <code>null</code> values. * * @param props * The properties to be flattened and copied. * * @return A flat copy. */ static public Properties flatCopy( final Properties props ) { final Properties tmp = new Properties(); // tmp.putAll( flatten( props ) ); final Map<String, Object> map = flatten(props); for(Map.Entry<String,Object> e : map.entrySet()) { final String name = e.getKey(); final Object val = e.getValue(); // if (val == null || !(val instanceof String)) { // System.err.println("name=" + name + ", val=" + val); // } if (val != null) { // Note: Hashtable does not allow nulls. tmp.put(name, val); } } return tmp; } /** * Return a Map that provides a flattened view of a Properties * object.<p> * * For each level of the Properties object, visit all keys and * then resolve each key against the top-level Properties object * placing the result into the output Map. The order of * visitation of the Properties levels does not matter since the * value of the key is always defined against the top-level * Properties object which handles any defaults correctly.<p> * <pre> a) Example overwrites any shared keys in p1 with the definitions for those keys in p2. Properties p1 = ...; Properties p2 = ...; p1.putAll( flatten( p2 ) ); b) Examples in which a Map and a Properties object are fused. Map m1 = ...; Properties p1 = ....; // Override m1 with p1. m1.putAll( flatten( p1 ) ); vs. // Override p1 with m1. p1.putAll( m1 ); </pre> */ public static Map flatten( Properties properties ) { if( properties == null ) { throw new IllegalArgumentException(); } Map out = new TreeMap(); Enumeration e = properties.propertyNames(); while( e.hasMoreElements() ) { String property = (String) e.nextElement(); String propertyValue = properties.getProperty ( property ); out.put( property, propertyValue ); } return out; } /** * Lists all entries defined either directly by a {@link Properties} * object or at any level within its defaults hierarchy. */ static public void list( String msg, Properties properties, PrintStream ps ) { ps.println( msg+"-- listing properties --" ); Enumeration e = properties.propertyNames(); while( e.hasMoreElements() ) { String property = (String) e.nextElement(); String propertyValue = properties.getProperty ( property ); ps.println( property + "=" + propertyValue ); } } /** * Fuses two configurations and ignores any conflicts. * * @param defaults The default configuration. * * @param override Another configuration whose values will be * fused with the <i>defaults</i>. If this is a {@link * Properties} object then it is first flattened so that any * inherited property values will be fused. * * @return A {@link Properties} object. If <i>defaults</i> was a * {@link Properties} object, then this is <i>defaults</i> and any * values from <i>override</i> have been added to <i>defaults</i>. * Otherwise a new {@link Properties} object is created, the * entries from <i>defaults</i> are copied into that {@link * Properties} object, and any values from <i>override</i> are * copied onto that {@link Properties} object. */ static public Properties fuse( Map defaults, Map override ) { final boolean ignoreConflicts = true; return fuse( defaults, override, ignoreConflicts ); } /** * Fuses two configurations and optionally reports any conflicts. * * @param defaults The default configuration. * * @param override Another configuration whose values will be * fused with the <i>defaults</i>. If this is a {@link * Properties} object then it is first flattened so that any * inherited property values will be fused. * * @param ignoreConflicts When true an exception is not * reported if an entry from <i>override</i> would override an * entry in <i>defaults</i>. * * @return A {@link Properties} object. If <i>defaults</i> was a * {@link Properties} object, then this is <i>defaults</i> and any * values from <i>override</i> have been added to <i>defaults</i>. * Otherwise a new {@link Properties} object is created, the * entries from <i>defaults</i> are copied into that {@link * Properties} object, and any values from <i>override</i> are * copied onto that {@link Properties} object. */ static public Properties fuse( Map defaults, Map override, boolean ignoreConflicts ) { if( defaults == null ) { throw new IllegalArgumentException(); } if( override == null ) { throw new IllegalArgumentException(); } if( override instanceof Properties ) { override = flatten ( (Properties) override ); } if( ! ( defaults instanceof Properties ) ) { Properties tmp = new Properties(); tmp.putAll( defaults ); defaults = tmp; } list( "defaults : ", ((Properties)defaults), System.err ); // Visit all entries in the [override] Map (it is a Map since // we flattened it if it was a Properties object). Iterator itr = override.entrySet().iterator(); while( itr.hasNext() ) { Map.Entry entry = (Map.Entry) itr.next(); String property = (String) entry.getKey(); String overrideValue = (String) entry.getValue(); String existingValue = ((Properties)defaults).getProperty ( property ); System.err.println ( "property="+property+" : existingValue="+existingValue+", overrideValue="+overrideValue ); if( existingValue != null ) { if( existingValue.equals( overrideValue ) ) { // Property already has this value. continue; } else if( ignoreConflicts ) { // Override the existing value. defaults.put( property, overrideValue ); } else { // Throw exception rather than overriding the // existing value for that property. throw new RuntimeException ( "Would override property="+property+ ": existingValue="+existingValue+ ", overrideValue="+overrideValue ); } } else { // Since the property was not defined, nothing could // be overriden and we just copy the value from the // [override] source. defaults.put ( property, overrideValue ); } } // Return a Properties object. If [defaults] was a Properties // object, then this is [defaults]. Otherwise [defaults] has // been wrapped up as a {@link Properties} object and any // values from [overriden] were copied onto [defaults]. return (Properties) defaults; } // /** // * Helper class wraps an existing {@link Properties} object and // * exposes its {@link Properties#defaults} field. // */ // private static class MyProperties // extends Properties // { // public MyProperties( Properties properties ) // { // super( properties ); // } // public Properties getDefaults() // { // return defaults; // } // } public static Properties convert( Map configParams ) { Properties properties = null; if( configParams instanceof Properties ) { // Make sure that we inherit default properties if we were // passed a Properties object. properties = new Properties ( (Properties) configParams ); } else { // If we were NOT passed a Properties object, then we // allocate one and initialize it from the Map. properties = new Properties(); properties.putAll ( configParams ); } return properties; } }