// 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 * @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) { } } }