From 43f8e3900215533a7dd823c7a0791934a8ed08fa Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Sat, 7 Oct 2000 18:13:11 +0000 Subject: [PATCH] * java/util/Properties.java: Merged with Classpath version. From-SVN: r36775 --- libjava/ChangeLog | 4 + libjava/java/util/Properties.java | 827 +++++++++++++++++++++----------------- 2 files changed, 465 insertions(+), 366 deletions(-) diff --git a/libjava/ChangeLog b/libjava/ChangeLog index b93bb7d..9bb268d 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,7 @@ +2000-10-07 Tom Tromey + + * java/util/Properties.java: Merged with Classpath version. + 2000-10-05 Tom Tromey * java/lang/reflect/natField.cc (BooleanClass): Don't define. diff --git a/libjava/java/util/Properties.java b/libjava/java/util/Properties.java index 6360b99..3f51d47 100644 --- a/libjava/java/util/Properties.java +++ b/libjava/java/util/Properties.java @@ -1,393 +1,488 @@ -// Properties - Property list representation. +/* java.util.Properties + Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. -/* Copyright (C) 1998, 1999, 2000 Free Software Foundation +This file is part of GNU Classpath. - This file is part of libgcj. +GNU Classpath 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; either version 2, or (at your option) +any later version. + +GNU Classpath 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. -This software is copyrighted work licensed under the terms of the -Libgcj License. Please consult the file "LIBGCJ_LICENSE" for -details. */ +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. -package java.util; - -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.io.PushbackReader; - -/** - * @author Tom Tromey - * @date October 26, 1998. - */ - -/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3 - * Status: Complete to JDK 1.2. - */ +As a special exception, if you link this library with other files to +produce an executable, this library does not by itself cause the +resulting executable to be covered by the GNU General Public License. +This exception does not however invalidate any other reasons why the +executable file might be covered by the GNU General Public License. */ -public class Properties extends Hashtable -{ - protected Properties defaults; - private static final long serialVersionUID = 4112578634029874840L; +package java.util; +import java.io.*; - public String getProperty (String propName) - { - return getProperty (propName, null); +/** + * An example of a properties file for the german language is given + * here. This extends the example given in ListResourceBundle. + * Create a file MyResource_de.properties with the following contents + * and put it in the CLASSPATH. (The character + * \u00e4 is the german ä) + * + *
+ * s1=3
+ * s2=MeineDisk
+ * s3=3. M\u00e4rz 96
+ * s4=Die Diskette ''{1}'' enth\u00e4lt {0} in {2}.
+ * s5=0
+ * s6=keine Dateien
+ * s7=1
+ * s8=eine Datei
+ * s9=2
+ * s10={0,number} Dateien
+ * s11=Das Formatieren schlug fehl mit folgender Exception: {0}
+ * s12=FEHLER
+ * s13=Ergebnis
+ * s14=Dialog
+ * s15=Auswahlkriterium
+ * s16=1,3
+ * 
+ * + * Although this is a sub class of a hash table, you should never + * insert anything other than strings to this property, or several + * methods, that need string keys and values, will fail. To ensure + * this, you should use the get/setProperty method instead + * of get/put. + * + * @see PropertyResourceBundle + * @author Jochen Hoenicke */ +public class Properties extends Hashtable { + /** + * The property list that contains default values for any keys not + * in this property list. + */ + protected Properties defaults; + + private static final long serialVersionUID = 4112578634029874840L; + + /** + * Creates a new empty property list. + */ + public Properties() { + this.defaults = null; } - - public String getProperty (String propName, String defVal) - { - String r = (String) get (propName); - if (r == null) - { - if (defaults != null) - r = defaults.getProperty(propName, defVal); - else - r = defVal; - } - return r; + + /** + * Create a new empty property list with the specified default values. + * @param defaults a Properties object containing the default values. + */ + public Properties(Properties defaults) { + this.defaults = defaults; } - public Object setProperty (String key, String value) - { - return put (key, value); - } - - public void list (PrintStream out) - { - Enumeration e = propertyNames (); - while (e.hasMoreElements()) - { - String key = (String) e.nextElement(); - String value = getProperty(key); - if (value != null) - { - if (value.length() > 40) - { - // JDK compatibility. - value = value.substring(0, 37) + "..."; - } - out.print(key); - out.print("="); - out.println(value); - } - } + /** + * Reads a property list from an input stream. The stream should + * have the following format:
+ * + * An empty line or a line starting with # or + * ! is ignored. An backslash (\) at the + * end of the line makes the line continueing on the next line + * (but make sure there is no whitespace after the backslash). + * Otherwise, each line describes a key/value pair.
+ * + * The chars up to the first whitespace, = or : are the key. You + * can include this caracters in the key, if you precede them with + * a backslash (\). The key is followed by optional + * whitespaces, optionally one = or :, + * and optionally some more whitespaces. The rest of the line is + * the resource belonging to the key.
+ * + * Escape sequences \t, \n, \r, \\, \", \', \!, \#, \ (a + * space), and unicode characters with the + * \uxxxx notation are detected, and + * converted to the corresponding single character.
+ * + *
+     * # This is a comment
+     * key     = value
+     * k\:5      \ a string starting with space and ending with newline\n
+     * # This is a multiline specification; note that the value contains
+     * # no white space.
+     * weekdays: Sunday,Monday,Tuesday,Wednesday,\
+     *           Thursday,Friday,Saturday
+     * # The safest way to include a space at the end of a value:
+     * label   = Name:\u0020
+     * 
+ * + * @param in the input stream + * @exception IOException if an error occured when reading + * from the input. */ + public void load(InputStream inStream) throws IOException { + BufferedReader reader = + new BufferedReader(new InputStreamReader(inStream)); + String line; + while ((line = reader.readLine()) != null) { + char c = 0; + int pos = 0; + while (pos < line.length() + && Character.isWhitespace(c = line.charAt(pos))) + pos++; + + // If line is empty or begins with a comment character, + // skip this line. + if (pos == line.length() || c == '#' || c == '!') + continue; + + // The characaters up to the next Whitespace, ':', or '=' + // describe the key. But look for escape sequences. + StringBuffer key = new StringBuffer(); + while (pos < line.length() + && !Character.isWhitespace(c = line.charAt(pos++)) + && c != '=' && c != ':') { + if (c == '\\') { + if (pos == line.length()) { + // The line continues on the next line. + line = reader.readLine(); + pos = 0; + while (pos < line.length() + && Character.isWhitespace(c = line.charAt(pos))) + pos++; + } else { + c = line.charAt(pos++); + switch (c) { + case 'n': + key.append('\n'); + break; + case 't': + key.append('\t'); + break; + case 'r': + key.append('\r'); + break; + case 'u': + if (pos+4 <= line.length()) { + char uni = (char) Integer.parseInt + (line.substring(pos, pos+4), 16); + key.append(uni); + } // else throw exception? + break; + default: + key.append(c); + break; + } + } + } else + key.append(c); + } + + boolean isDelim = (c == ':' || c == '='); + while (pos < line.length() + && Character.isWhitespace(c = line.charAt(pos))) + pos++; + + if (!isDelim && (c == ':' || c == '=')) { + pos++; + while (pos < line.length() + && Character.isWhitespace(c = line.charAt(pos))) + pos++; + } + + StringBuffer element = new StringBuffer(line.length()-pos); + while (pos < line.length()) { + c = line.charAt(pos++); + if (c == '\\') { + if (pos == line.length()) { + // The line continues on the next line. + line = reader.readLine(); + pos = 0; + while (pos < line.length() + && Character.isWhitespace(c = line.charAt(pos))) + pos++; + element.ensureCapacity(line.length()-pos+element.length()); + } else { + c = line.charAt(pos++); + switch (c) { + case 'n': + element.append('\n'); + break; + case 't': + element.append('\t'); + break; + case 'r': + element.append('\r'); + break; + case 'u': + if (pos+4 <= line.length()) { + char uni = (char) Integer.parseInt + (line.substring(pos, pos+4), 16); + element.append(uni); + } // else throw exception? + break; + default: + element.append(c); + break; + } + } + } else + element.append(c); + } + put(key.toString(), element.toString()); + } } - public void list (PrintWriter writer) - { - Enumeration e = propertyNames (); - while (e.hasMoreElements()) - { - String key = (String) e.nextElement(); - String value = getProperty(key); - if (value != null) - { - if (value.length() > 40) - { - // JDK compatibility. - value = value.substring(0, 37) + "..."; - } - writer.print(key); - writer.print("="); - writer.println(value); - } - } + /** + * Calls store(OutputStream out, String header) and + * ignores the IOException that may be thrown. + * @deprecated use store instead. + * @exception ClassCastException if this property contains any key or + * value that isn't a string. + */ + public void save(OutputStream out, String header) { + try { + store(out,header); + } catch (IOException ex) { + } } - - private final boolean skip_ws (PushbackReader reader) throws IOException - { - while (true) - { - int c = reader.read(); - if (c == -1) - return false; - // FIXME: we use our own definition of whitespace. - // Character.isWhitespace includes newlines, which we don't - // want. Character.isSpaceChar doesn't include \t. - if (c != ' ' && c != '\t') - { - reader.unread(c); - return true; - } - } + + /** + * Writes the key/value pairs to the given output stream.
+ * + * If header is not null, this method writes a comment containing + * the header as first line to the stream. The next line (or first + * line if header is null) contains a comment with the current date. + * Afterwards the key/value pairs are written to the stream in the + * following format.
+ * + * Each line has the form key = value. Newlines, + * Returns and tabs are written as \n,\t,\r resp. + * The characters \, !, #, = and : are + * preceeded by a backslash. Spaces are preceded with a backslash, + * if and only if they are at the beginning of the key. Characters + * that are not in the ascii range 33 to 127 are written in the + * \uxxxx Form. + * + * @param out the output stream + * @param header the header written in the first line, may be null. + * @exception ClassCastException if this property contains any key or + * value that isn't a string. + */ + public void store(OutputStream out, String header) throws IOException { + PrintWriter writer = new PrintWriter(out); + if (header != null) + writer.println("#"+header); + writer.println("#"+new Date().toString()); + list(writer); + writer.flush(); } - - // Note: this method needs to be rewritten for JDK 1.2. - // We rather arbitrarily decide that an EOF in the middle of a line - // means that the whole line should be ignored. The spec doesn't - // specifically address this, but this interpretation seems valid. - public synchronized void load (InputStream in) throws IOException - { - PushbackReader reader = new PushbackReader (new InputStreamReader (in)); - - StringBuffer key = new StringBuffer (); - StringBuffer value = new StringBuffer (); - - nextLine: - while (true) - { - key.setLength(0); - value.setLength(0); - - // Skip leading whitespace. - if (! skip_ws (reader)) - return; - - // Read key until key terminator. - boolean first_char = true; - int c; - while (true) - { - c = reader.read(); - if (c == -1) - return; - if (c == '\\') - { - first_char = false; - c = reader.read(); - if (c == -1) - return; - } - - // If we found a comment, just read to end of line and - // then keep going. - if (first_char == true && (c == '#' || c == '!')) - { - while (c != -1 && c != '\r' && c != '\n') - c = reader.read(); - if (c == -1) - return; - continue nextLine; - } - - if (c == '\r' || c == '\n') - { - if (first_char) - continue nextLine; - reader.unread(c); - break; - } - // FIXME: again, our own definition of whitespace. - if (c == ' ' || c == '\t' || c == ':' || c == '=') - break; - - first_char = false; - key.append((char) c); - } - - // Found end of key. Skip whitespace. If the terminator - // was whitespace, also skip a single instance of a "real" - // terminator, and then more whitespace. - if (! skip_ws (reader)) - return; - if (c != ':' && c != '=') - { - c = reader.read(); - if (c == -1) - return; - if (c == ':' || c == '=') - { - // Skip more whitespace. - if (! skip_ws (reader)) - return; - } - else - reader.unread(c); - } - - // Now read the value. - while (true) - { - c = reader.read(); - if (c == -1) - return; - if (c == '\r' || c == '\n') - break; - if (c == '\\') - { - c = reader.read(); - switch (c) - { - case -1: - return; - case 't': - c = '\t'; - break; - case 'r': - c = '\r'; - break; - case 'n': - c = '\n'; - break; - case 'u': - c = 0; - for (int i = 0; i < 4; ++i) - { - int x = reader.read(); - if (x == -1) - return; - int d = Character.digit((char) x, 16); - // We follow JDK here: invalid characters - // are treated as terminators. - if (d == -1) - { - value.append((char) c); - c = x; - break; - } - c <<= 4; - c |= d; - } - break; - default: - // Nothing. - } - } - value.append((char) c); - } - - put (key.toString(), value.toString()); - } + + /** + * Adds the given key/value pair to this properties. This calls + * the hashtable method put. + * @param key the key for this property + * @param value the value for this property + * @return The old value for the given key. + * @since JDK1.2 */ + public Object setProperty(String key, String value) { + return put(key,value); } - public Properties () - { - defaults = null; + /** + * Gets the property with the specified key in this property list. + * If the key is not found, the default property list is searched. + * If the property is not found in default or the default of + * default, null is returned. + * @param key The key for this property. + * @param defaulValue A default value + * @return The value for the given key, or null if not found. + * @exception ClassCastException if this property contains any key or + * value that isn't a string. + */ + public String getProperty(String key) { + return getProperty(key, null); } - public Properties (Properties defs) - { - defaults = defs; + /** + * Gets the property with the specified key in this property list. If + * the key is not found, the default property list is searched. If the + * property is not found in default or the default of default, the + * specified defaultValue is returned. + * @param key The key for this property. + * @param defaulValue A default value + * @return The value for the given key. + * @exception ClassCastException if this property contains any key or + * value that isn't a string. + */ + public String getProperty(String key, String defaultValue) { + Properties prop = this; + // Eliminate tail recursion. + do { + String value = (String) prop.get(key); + if (value != null) + return value; + prop = prop.defaults; + } while (prop != null); + return defaultValue; } - private final void addHashEntries (Hashtable base) - { - if (defaults != null) - defaults.addHashEntries(base); - Enumeration keys = keys (); - while (keys.hasMoreElements()) - base.put(keys.nextElement(), base); + private final void addHashEntries (Hashtable base) { + if (defaults != null) + defaults.addHashEntries(base); + Enumeration keys = keys (); + while (keys.hasMoreElements()) + base.put(keys.nextElement(), base); } - public Enumeration propertyNames () - { - // We make a new Hashtable that holds all the keys. Then we - // return an enumeration for this hash. We do this because we - // don't want modifications to be reflected in the enumeration - // (per JCL), and because there doesn't seem to be a - // particularly better way to ensure that duplicates are - // ignored. - Hashtable t = new Hashtable (); - addHashEntries (t); - return t.keys(); + /** + * Returns an enumeration of all keys in this property list, including + * the keys in the default property list. + */ + public Enumeration propertyNames () { + // We make a new Hashtable that holds all the keys. Then we + // return an enumeration for this hash. We do this because we + // don't want modifications to be reflected in the enumeration + // (per JCL), and because there doesn't seem to be a + // particularly better way to ensure that duplicates are + // ignored. + Hashtable t = new Hashtable (); + addHashEntries (t); + return t.keys(); } - public synchronized void save (OutputStream out, String comment) - { - try - { - store (out, comment); - } - catch (IOException _) - { - } - } - - public synchronized void store (OutputStream out, String comment) - throws IOException - { - // Use a buffer because writing a single string through - // OutputStreamWriter is fairly expensive. - BufferedWriter output - = new BufferedWriter (new OutputStreamWriter (out)); - String newline = System.getProperty("line.separator"); - - if (comment != null) - { - // We just lose if COMMENT contains a newline. This is - // what JDK 1.1 does. - output.write("#"); - output.write(comment); - output.write(newline); - } - output.write("# "); - output.write(new Date().toString()); - output.write(newline); - - Enumeration keys = keys (); - while (keys.hasMoreElements()) - { - String key = (String) keys.nextElement(); - String value = (String) get (key); - - // FIXME: JCL says that the key can contain many Unicode - // characters. But it also doesn't say we should encode - // it in any way. - // FIXME: if key contains ':', '=', or whitespace, must - // quote it here. Note that JDK 1.1 does not do this. - output.write(key); - output.write("="); - - boolean leading = true; - for (int i = 0; i < value.length(); ++i) - { - boolean new_lead = false; - char c = value.charAt(i); - switch (c) - { - case '\n': - output.write("\\n"); - break; - case '\r': - output.write("\\r"); - break; - case '\t': - output.write("\\t"); - break; - case '\\': - output.write("\\\\"); - break; - - case '#': - case '!': - case '=': - case ':': - output.write("\\"); - output.write(c); - break; - - case ' ': - new_lead = leading; - if (leading) - output.write("\\"); - output.write(c); - break; + /** + * Formats a key/value pair for output in a properties file. + * See store for a description of the format. + * @param key the key. + * @param value the value. + * @see #store + */ + private String formatForOutput(String key, String value) { + // This is a simple approximation of the expected line size. + StringBuffer result = new StringBuffer(key.length()+value.length()+16); + boolean head = true; + for (int i=0; i< key.length(); i++) { + char c = key.charAt(i); + switch (c) { + case '\n': + result.append("\\n"); + break; + case '\r': + result.append("\\r"); + break; + case '\t': + result.append("\\t"); + break; + case '\\': + result.append("\\\\"); + break; + case '!': + result.append("\\!"); + break; + case '#': + result.append("\\#"); + break; + case '=': + result.append("\\="); + break; + case ':': + result.append("\\:"); + break; + case ' ': + result.append("\\ "); + break; + default: + if (c < 32 || c > '~') { + String hex = Integer.toHexString(c); + result.append("\\u0000".substring(0, 6-hex.length())); + result.append(hex); + } else + result.append(c); + } + if (c != 32) + head = false; + } + result.append('='); + head=true; + for (int i=0; i< value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '\n': + result.append("\\n"); + break; + case '\r': + result.append("\\r"); + break; + case '\t': + result.append("\\t"); + break; + case '\\': + result.append("\\\\"); + break; + case '!': + result.append("\\!"); + break; + case '#': + result.append("\\#"); + break; + case ' ': + result.append(head ? "\\ ": " "); + break; + default: + if (c < 32 || c > '~') { + String hex = Integer.toHexString(c); + result.append("\\u0000".substring(0, 6-hex.length())); + result.append(hex); + } else + result.append(c); + } + if (c != 32) + head = false; + } + return result.toString(); + } - default: - if (c < '\u0020' || c > '\u007e') - { - output.write("\\u"); - output.write(Character.forDigit(c >>> 12, 16)); - output.write(Character.forDigit((c >>> 8) & 0xff, - 16)); - output.write(Character.forDigit((c >>> 4) & 0xff, - 16)); - output.write(Character.forDigit(c & 0xff, 16)); - } - else - output.write(c); - } - leading = new_lead; - } - output.write(newline); - } + /** + * Writes the key/value pairs to the given print stream. They are + * written in the way, described in the method store. + * @param out the stream, where the key/value pairs are written to. + * @exception ClassCastException if this property contains any key or + * value that isn't a string. + * @see #store + */ + public void list(PrintStream out) { + Enumeration keys = keys(); + Enumeration elts = elements(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + String elt = (String) elts.nextElement(); + String output = formatForOutput(key,elt); + out.println(output); + } + } - output.flush(); - } + /** + * Writes the key/value pairs to the given print writer. They are + * written in the way, described in the method store. + * @param out the writer, where the key/value pairs are written to. + * @exception ClassCastException if this property contains any key or + * value that isn't a string. + * @see #store + * @see #list(java.io.PrintStream) + * @since JDK1.1 + */ + public void list(PrintWriter out) { + Enumeration keys = keys(); + Enumeration elts = elements(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + String elt = (String) elts.nextElement(); + String output = formatForOutput(key,elt); + out.println(output); + } + } } -- 2.7.4