/* * [MultiProperties.java] * * Summary: This class is similar to java.util.Properties. The properties file has a similar key=value format. * * Copyright: (c) 2003-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 10.2 2009-04-03 tidy up code to check presence of necessary files to make it more WORA. * However, for MultiProperties, spaces in the values are not coded as "\ ", just as plain " ". * Values can be a tuple separated by * commas. The inherited Hashtable get returns a String[]. There is no support for extended chars * such as \n. There is * no support for \ as a continuation character. Comment lines begin with #. Blank lines are ignored. * The file must end * with a line separator. All values are trimmed of leading and trailing spaces. All Strings are * interned, so they * behave just like hard-coded String static finals. It is basically just a Hashtable with a load * method and a few * conveniences added to the get method. * 10.3 2011-02-13 convert from Hashtable base to HashMap. */ package com.mindprod.replicatorcommon; import com.mindprod.common18.EIO; import com.mindprod.common18.Misc; import com.mindprod.common18.ST; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.regex.Pattern; import static java.lang.System.*; /** * This class is similar to java.util.Properties. The properties file has a similar key=value format. * * @author Roedy Green, Canadian Mind Products * @version 10.3 2011-02-13 convert from Hashtable base to HashMap * @since 2003-08-03 */ public final class MultiProperties extends HashMap { /** * true if you want the debugging harness, this is NOT the DEBUGGING property. */ private static final boolean DEBUGGING = false; /** * A EMPTY_PROPERTIES empty array of Strings. No point is allocating a fresh one every elapsedTime it is needed. */ private static final String[] EMPTY_PROPERTIES = new String[ 0 ]; /** * Pattern to split into words separated by commas. Two commas in a row in the String to be matched gives an empty * field */ private static final Pattern SPLIT_ON_COMMA = Pattern.compile( "\\s*,\\s*" ); // Pattern to split line into key and value at the = private static final Pattern SPLIT_ON_EQUAL = Pattern.compile( "=" ); /** * Constructs a new, empty HashMap with the specified initial capacity and the specified load factor. See * http://mindprod.com/jgloss/HashMap.html for a full description of what the initialCapacity and loadFactors * mean. * * @param initialCapacity the initial capacity of the HashMap. * @param loadFactor the load factor of the HashMap. * * @throws IllegalArgumentException if the initial capacity is less than zero, or if the load factor is * nonpositive. */ public MultiProperties( int initialCapacity, float loadFactor ) { super( initialCapacity, loadFactor ); } /** * Complain about malformed data. * * @param line Line of key=value that has a problem. */ private static void complain( String line ) { throw new IllegalArgumentException( "MultiProperties: malformed key=value : " + line ); } /** * TEST harness * * @param args not used */ public static void main( String[] args ) { if ( DEBUGGING ) { MultiProperties m = new MultiProperties( 100, .75f ); try { m.load( new FileInputStream( "replicator.properties" ) ); } catch ( IOException e ) { ReplicatorCommon.fatal( "Unable to read replicator.properties file." + "\n" + e.getMessage() ); } for ( String key : m.keySet() ) { String[] values = m.getMultiple( key ); out.println( key + " =" ); for ( String value : values ) { out.println( value ); } } // end for } // end if DEBUGGING } // end main /** * Get value associated with key. * * @param key Key, case-sensitive. * @param defaultValue Value for the default if the key is not defined. key=nothing returns "", not the default * value. * * @return String for a single value, or the first of a set of multiple values, or "". */ public String get( String key, String defaultValue ) { Object value = get( key ); if ( value == null ) { return defaultValue.intern(); } else { String[] array = ( ( String[] ) value ); if ( array.length != 0 ) { return array[ 0 ]; } else { return "";// not defaultValue! } } } /** * Get boolean value associated with key. Valid values for key are true, false, yes, no, case-insensitive. * * @param key Key, case-sensitive. * @param defaultValue Value for the default if the key is not defined. * * @return boolean value of the key, or defaultValue if not defined or if key= */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion" } ) public boolean getBoolean( String key, boolean defaultValue ) { Object value = get( key ); if ( value == null ) { return defaultValue; } else { String[] array = ( ( String[] ) value ); if ( array.length != 0 && !ST.isEmpty( array[ 0 ] ) ) { return Misc.parseBoolean( array[ 0 ], false ); } else { return defaultValue; } } } /** * Get single integer value associated with key. * * @param key Key, case-sensitive. * @param defaultValue Value for the default if the key is not defined. * * @return integer value of the key, or defaultValue if not defined or if key= * @throws NumberFormatException if the value is not a valid integer. */ public int getInt( String key, int defaultValue ) throws NumberFormatException { Object value = get( key ); if ( value == null ) { return defaultValue; } else { String[] array = ( ( String[] ) value ); if ( array.length != 0 ) { return Integer.parseInt( array[ 0 ] ); } else { return defaultValue; } } } /** * Get values associated with key. * * @param key Key, case-sensitive. * * @return array of associated Strings, possibly dimension 0. If key is undefined returns empty array, not null. */ public String[] getMultiple( String key ) { String[] value = get( key ); if ( value == null ) { return EMPTY_PROPERTIES; } else { return value; } } /** * Load the properties HashMap from a text file of key=value pairs. * * @param in where to load the textual key=value pairs from. * * @throws IOException if can't load properties table. */ public void load( InputStream in ) throws IOException { final BufferedReader br = EIO.getBufferedReader( in, 4 * 1024, EIO.UTF8 ); while ( true ) { String line = br.readLine(); if ( line == null ) { break; } if ( line.startsWith( "#" ) ) { /* ignore comments */ continue; } line = line.trim(); if ( line.length() == 0 ) { /* ignore blank lines */ continue; } // split line into key and value String[] keyValue = SPLIT_ON_EQUAL.split( line ); switch ( keyValue.length ) { case 1: { // key=nothing String key = keyValue[ 0 ].trim().intern(); if ( key.length() == 0 ) { complain( line ); } this.put( key, EMPTY_PROPERTIES ); } break; case 2: { // key=value String key = keyValue[ 0 ].trim().intern(); if ( key.length() == 0 ) { complain( line ); } // Split value into subfields String[] values = SPLIT_ON_COMMA.split( keyValue[ 1 ] .trim() ); // trim the multiple values for ( int i = 0; i < values.length; i++ ) { values[ i ] = values[ i ].trim().intern(); } // save in the underlying HashMap. this.put( key, values ); } break; default: case 0: complain( line ); } } // end while br.close(); } // end load } // end MultiProperties