// --- BEGIN COPYRIGHT BLOCK --- // 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., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // (C) 2007 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package com.netscape.cmscore.base; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; /** * The Properties class represents a persistent set of properties. * The Properties can be saved to a stream or loaded from a stream. * Each key and its corresponding value in the property list is a string. *

* A property list can contain another property list as its "defaults"; this * second property list is searched if the property key is not found in the * original property list. * * Because Properties inherits from Hashtable, the * put and putAll methods can be applied to a * Properties object. Their use is strongly discouraged as they * allow the caller to insert entries whose keys or values are not * Strings. The setProperty method should be used * instead. If the store or save method is called on a * "compromised" Properties object that contains a non- * String key or value, the call will fail. * */ public class SimpleProperties extends Hashtable { /** * */ private static final long serialVersionUID = -6129810287662322712L; /** * A property list that contains default values for any keys not found in * this property list. * * @serial */ protected SimpleProperties defaults; /** * Creates an empty property list with no default values. */ public SimpleProperties() { this(null); } /** * Creates an empty property list with the specified defaults. * * @param defaults the defaults. */ public SimpleProperties(SimpleProperties defaults) { this.defaults = defaults; } /** * Calls the hashtable method put. Provided for parallelism * with the getProperties method. Enforces use of strings for property keys * and values. * * @since JDK1.2 */ public synchronized Object setProperty(String key, String value) { return put(key, value); } private static final String keyValueSeparators = "=: \t\r\n\f"; private static final String strictKeyValueSeparators = "=:"; private static final String specialSaveChars = "=: \t\r\n\f#!"; private static final String whiteSpaceChars = " \t\r\n\f"; /** * Reads a property list (key and element pairs) from the input stream. *

* Every property occupies one line of the input stream. Each line is * terminated by a line terminator (\n or \r or * \r\n). Lines from the input stream are processed until end * of file is reached on the input stream. *

* A line that contains only whitespace or whose first non-whitespace * character is an ASCII # or ! is ignored (thus, * # or ! indicate comment lines). *

* Every line other than a blank line or a comment line describes one * property to be added to the table (except that if a line ends with \, * then the following line, if it exists, is treated as a continuation line, * as described below). The key consists of all the characters in the line * starting with the first non-whitespace character and up to, but not * including, the first ASCII =, :, or whitespace * character. All of the key termination characters may be included in the * key by preceding them with a \. Any whitespace after the key is skipped; * if the first non-whitespace character after the key is = or * :, then it is ignored and any whitespace characters after it * are also skipped. All remaining characters on the line become part of the * associated element string. Within the element string, the ASCII escape * sequences \t, \n, \r, * \\, \", \', \ * (a backslash and a space), and \\uxxxx are * recognized and converted to single characters. Moreover, if the last * character on the line is \, then the next line is treated as * a continuation of the current line; the \ and line * terminator are simply discarded, and any leading whitespace characters on * the continuation line are also discarded and are not part of the element * string. *

* As an example, each of the following four lines specifies the key * "Truth" and the associated element value * "Beauty": *

* *

     * Truth = Beauty
     * Truth:Beauty
     * Truth			:Beauty
     * 
* * As another example, the following three lines specify a single property: *

* *

     * fruits				apple, banana, pear, \
     *                                  cantaloupe, watermelon, \
     *                                  kiwi, mango
     * 
* * The key is "fruits" and the associated element is: *

* *

     * "apple, banana, pear, cantaloupe, watermelon,kiwi, mango"
     * 
* * Note that a space appears before each \ so that a space will * appear after each comma in the final result; the \, line * terminator, and leading whitespace on the continuation line are merely * discarded and are not replaced by one or more other characters. *

* As a third example, the line: *

* *

     * cheeses
     * 
* * specifies that the key is "cheeses" and the associated * element is the empty string. *

* * @param in the input stream. * @exception IOException if an error occurred when reading from the input * stream. */ public synchronized void load(InputStream inStream) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(inStream, "8859_1")); while (true) { // Get next line String line = in.readLine(); if (line == null) return; if (line.length() > 0) { // Continue lines that end in slashes if they are not comments char firstChar = line.charAt(0); if ((firstChar != '#') && (firstChar != '!')) { while (continueLine(line)) { String nextLine = in.readLine(); if (nextLine == null) nextLine = ""; String loppedLine = line.substring(0, line.length() - 1); // Advance beyond whitespace on new line int startIndex = 0; for (startIndex = 0; startIndex < nextLine.length(); startIndex++) if (whiteSpaceChars.indexOf(nextLine.charAt(startIndex)) == -1) break; nextLine = nextLine.substring(startIndex, nextLine.length()); line = new String(loppedLine + nextLine); } // Find start of key int len = line.length(); int keyStart; for (keyStart = 0; keyStart < len; keyStart++) { if (whiteSpaceChars.indexOf(line.charAt(keyStart)) == -1) break; } // Find separation between key and value int separatorIndex; for (separatorIndex = keyStart; separatorIndex < len; separatorIndex++) { char currentChar = line.charAt(separatorIndex); if (currentChar == '\\') separatorIndex++; else if (keyValueSeparators.indexOf(currentChar) != -1) break; } // Skip over whitespace after key if any int valueIndex; for (valueIndex = separatorIndex; valueIndex < len; valueIndex++) if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1) break; // Skip over one non whitespace key value separators if any if (valueIndex < len) if (strictKeyValueSeparators.indexOf(line.charAt(valueIndex)) != -1) valueIndex++; // Skip over white space after other separators if any while (valueIndex < len) { if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1) break; valueIndex++; } String key = line.substring(keyStart, separatorIndex); String value = (separatorIndex < len) ? line.substring(valueIndex, len) : ""; // Convert then store key and value // NETSCAPE: no need to convert escape characters // key = loadConvert(key); // value = loadConvert(value); put(key, value); } } } } /* * Returns true if the given line is a line that must be appended to the * next line */ private boolean continueLine(String line) { int slashCount = 0; int index = line.length() - 1; while ((index >= 0) && (line.charAt(index--) == '\\')) slashCount++; return (slashCount % 2 == 1); } /** * Calls the store(OutputStream out, String header) method and * suppresses IOExceptions that were thrown. * * @deprecated This method does not throw an IOException if an I/O error * occurs while saving the property list. As of JDK 1.2, the * preferred way to save a properties list is via the * store(OutputStream out, * String header) method. * * @param out an output stream. * @param header a description of the property list. * @exception ClassCastException if this Properties object * contains any keys or values that are not * Strings. */ public synchronized void save(OutputStream out, String header) { try { store(out, header); } catch (IOException e) { } } /** * Writes this property list (key and element pairs) in this * Properties table to the output stream in a format suitable * for loading into a Properties table using the * load method. *

* Properties from the defaults table of this Properties table * (if any) are not written out by this method. *

* If the header argument is not null, then an ASCII # * character, the header string, and a line separator are first written to * the output stream. Thus, the header can serve as an * identifying comment. *

* Next, a comment line is always written, consisting of an ASCII * # character, the current date and time (as if produced by * the toString method of Date for the current * time), and a line separator as generated by the Writer. *

* Then every entry in this Properties table is written out, * one per line. For each entry the key string is written, then an ASCII * =, then the associated element string. Each character of the * element string is examined to see whether it should be rendered as an * escape sequence. The ASCII characters \, tab, newline, and * carriage return are written as \\, \t, * \n, and \r, respectively. Characters less than * \u0020 and characters greater than \u007E are * written as \\uxxxx for the appropriate hexadecimal * value xxxx. Space characters, but not embedded or trailing space * characters, are written with a preceding \. The key and * value characters #, !, =, and * : are written with a preceding slash to ensure that they are * properly loaded. *

* After the entries have been written, the output stream is flushed. The * output stream remains open after this method returns. * * @param out an output stream. * @param header a description of the property list. * @exception ClassCastException if this Properties object * contains any keys or values that are not * Strings. */ public synchronized void store(OutputStream out, String header) throws IOException { BufferedWriter awriter; awriter = new BufferedWriter(new OutputStreamWriter(out, "8859_1")); if (header != null) writeln(awriter, "#" + header); writeln(awriter, "#" + new Date().toString()); for (Enumeration e = keys(); e.hasMoreElements();) { String key = e.nextElement(); String val = get(key); // key = saveConvert(key); // val = saveConvert(val); writeln(awriter, key + "=" + val); } awriter.flush(); } private static void writeln(BufferedWriter bw, String s) throws IOException { bw.write(s); bw.newLine(); } /** * Searches for the property with the specified key in this property list. * If the key is not found in this property list, the default property list, * and its defaults, recursively, are then checked. The method returns * null if the property is not found. * * @param key the property key. * @return the value in this property list with the specified key value. * @see java.util.Properties#defaults */ public String getProperty(String key) { String oval = super.get(key); String sval = (oval instanceof String) ? oval : null; return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; } /** * Searches for the property with the specified key in this property list. * If the key is not found in this property list, the default property list, * and its defaults, recursively, are then checked. The method returns the * default value argument if the property is not found. * * @param key the hashtable key. * @param defaultValue a default value. * * @return the value in this property list with the specified key value. * @see java.util.Properties#defaults */ public String getProperty(String key, String defaultValue) { String val = getProperty(key); return (val == null) ? defaultValue : val; } /** * Returns an enumeration of all the keys in this property list, including * the keys in the default property list. * * @return an enumeration of all the keys in this property list, including * the keys in the default property list. * @see java.util.Enumeration * @see java.util.Properties#defaults */ public Enumeration propertyNames() { Hashtable h = new Hashtable(); enumerate(h); return h.keys(); } /** * Prints this property list out to the specified output stream. This method * is useful for debugging. * * @param out an output stream. */ public void list(PrintStream out) { out.println("-- listing properties --"); Hashtable h = new Hashtable(); enumerate(h); for (Enumeration e = h.keys(); e.hasMoreElements();) { String key = e.nextElement(); String val = h.get(key); if (val.length() > 40) { val = val.substring(0, 37) + "..."; } out.println(key + "=" + val); } } /** * Prints this property list out to the specified output stream. This method * is useful for debugging. * * @param out an output stream. * @since JDK1.1 */ /* * Rather than use an anonymous inner class to share common code, this * method is duplicated in order to ensure that a non-1.1 compiler can * compile this file. */ public void list(PrintWriter out) { out.println("-- listing properties --"); Hashtable h = new Hashtable(); enumerate(h); for (Enumeration e = h.keys(); e.hasMoreElements();) { String key = e.nextElement(); String val = h.get(key); if (val.length() > 40) { val = val.substring(0, 37) + "..."; } out.println(key + "=" + val); } } /** * Enumerates all key/value pairs in the specified hastable. * * @param h the hashtable */ private synchronized void enumerate(Hashtable h) { if (defaults != null) { defaults.enumerate(h); } for (Enumeration e = keys(); e.hasMoreElements();) { String key = e.nextElement(); h.put(key, get(key)); } } /** A table of hex digits */ private static final char[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; }