| // Properties - Property list representation. |
| |
| /* Copyright (C) 1998, 1999 Cygnus Solutions |
| |
| This file is part of libgcj. |
| |
| This software is copyrighted work licensed under the terms of the |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for |
| details. */ |
| |
| 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 <tromey@cygnus.com> |
| * @date October 26, 1998. |
| */ |
| |
| /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3 |
| * Status: Complete to JDK 1.1. |
| */ |
| |
| public class Properties extends Hashtable |
| { |
| protected Properties defaults; |
| |
| public String getProperty (String propName) |
| { |
| return getProperty (propName, 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; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| // 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()); |
| } |
| } |
| |
| public Properties () |
| { |
| defaults = null; |
| } |
| |
| public Properties (Properties defs) |
| { |
| defaults = defs; |
| } |
| |
| 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(); |
| } |
| |
| public synchronized void save (OutputStream out, String comment) |
| { |
| // 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"); |
| |
| try |
| { |
| 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; |
| |
| 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); |
| } |
| |
| output.flush(); |
| } |
| catch (IOException ignore) |
| { |
| } |
| } |
| } |