1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2006, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.interview;
  28 
  29 import java.io.IOException;
  30 import java.io.PrintWriter;
  31 import java.io.Reader;
  32 import java.io.Writer;
  33 import java.util.Date;
  34 import java.util.Enumeration;
  35 import java.util.Hashtable;
  36 
  37 /**
  38  * The <code>Properties</code> class represents a persistent set of
  39  * properties. The <code>Properties</code> can be saved to a stream
  40  * or loaded from a stream. Each key and its corresponding value in
  41  * the property list is a string.
  42  * <p>
  43  * A property list can contain another property list as its
  44  * "defaults"; this second property list is searched if
  45  * the property key is not found in the original property list.
  46  *
  47  * This class is similar to java.util.Properties, but has upgraded
  48  * capabilities.
  49  */
  50 
  51 public class Properties2 extends Hashtable<String, Object> {
  52     /**
  53      * A property list that contains default values for any keys not
  54      * found in this property list.
  55      */
  56     protected Properties2 defaults;
  57 
  58     /**
  59      * Creates an empty property list with no default values.
  60      */
  61     public Properties2() {
  62         this(null);
  63     }
  64 
  65     /**
  66      * Creates an empty property list with the specified defaults.
  67      * @param   defaults   the defaults.
  68      */
  69     public Properties2(Properties2 defaults) {
  70         this.defaults = defaults;
  71     }
  72 
  73     public void load(java.util.Properties source) {
  74         Enumeration e = source.propertyNames();
  75         while(e.hasMoreElements()) {
  76             Object next = e.nextElement();
  77             put( ((String)next), source.get(next) );
  78         }   // while
  79     }
  80 
  81 
  82     /**
  83      * Reads a property list from an input stream.
  84      *
  85      * @param      in   the input stream.
  86      * @exception  IOException  if an error occurred when reading from the
  87      *               input stream.
  88      */
  89     public synchronized void load(Reader in) throws IOException {
  90         int ch = in.read();
  91         while (true) {
  92             switch (ch) {
  93               case -1:
  94                 return;
  95 
  96               case '#':
  97               case '!':
  98                 do {
  99                     ch = in.read();
 100                 } while ((ch >= 0) && (ch != '\n') && (ch != '\r'));
 101                 continue;
 102 
 103               case '\n':
 104               case '\r':
 105               case ' ':
 106               case '\t':
 107                 ch = in.read();
 108                 continue;
 109             }
 110 
 111             // Read the key
 112             StringBuffer key = new StringBuffer();
 113             while ((ch >= 0) && (ch != '=') && (ch != ':') &&
 114                    (ch != ' ') && (ch != '\t') && (ch != '\n') && (ch != '\r')) {
 115                 key.append((char)ch);
 116                 ch = in.read();
 117             }
 118             while ((ch == ' ') || (ch == '\t')) {
 119                 ch = in.read();
 120             }
 121             if ((ch == '=') || (ch == ':')) {
 122                 ch = in.read();
 123             }
 124             while ((ch == ' ') || (ch == '\t')) {
 125                 ch = in.read();
 126             }
 127 
 128             // Read the value
 129             StringBuffer val = new StringBuffer();
 130             while ((ch >= 0) && (ch != '\n') && (ch != '\r')) {
 131                 int next = 0;
 132                 if (ch == '\\') {
 133                     switch (ch = in.read()) {
 134                       case '\r':
 135                         if (((ch = in.read()) == '\n') ||
 136                             (ch == ' ') || (ch == '\t')) {
 137                           // fall thru to '\n' case
 138                         } else continue;
 139                       case '\n':
 140                         while (((ch = in.read()) == ' ') || (ch == '\t'));
 141                         continue;
 142                       case 't': ch = '\t'; next = in.read(); break;
 143                       case 'n': ch = '\n'; next = in.read(); break;
 144                       case 'r': ch = '\r'; next = in.read(); break;
 145                       case 'u': {
 146                         while ((ch = in.read()) == 'u');
 147                         int d = 0;
 148                       loop:
 149                         for (int i = 0 ; i < 4 ; i++) {
 150                             next = in.read();
 151                             switch (ch) {
 152                               case '0': case '1': case '2': case '3': case '4':
 153                               case '5': case '6': case '7': case '8': case '9':
 154                                 d = (d << 4) + ch - '0';
 155                                 break;
 156                               case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
 157                                 d = (d << 4) + 10 + ch - 'a';
 158                                 break;
 159                               case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
 160                                 d = (d << 4) + 10 + ch - 'A';
 161                                 break;
 162                               default:
 163                                 break loop;
 164                             }
 165                             ch = next;
 166                         }
 167                         ch = d;
 168                         break;
 169                       }
 170                       default: next = in.read(); break;
 171                     }
 172                 } else {
 173                     next = in.read();
 174                 }
 175                 val.append((char)ch);
 176                 ch = next;
 177             }
 178 
 179             //System.out.println(key + " = '" + val + "'");
 180             put(key.toString(), val.toString());
 181         }
 182     }
 183 
 184     /**
 185      * Stores this property list to the specified output stream. The
 186      * string header is printed as a comment at the beginning of the stream.
 187      *
 188      * @param   out      an output stream.
 189      * @param   header   a description of the property list.
 190      */
 191     public synchronized void save(Writer out, String header) {
 192         save(out, header, false);
 193     }
 194 
 195     private void save(Writer out, String header, boolean localize) {
 196         PrintWriter prnt = (out instanceof PrintWriter ? (PrintWriter)out :
 197                             new PrintWriter(out, false));
 198 
 199         if (header != null) {
 200             prnt.write('#');
 201             prnt.println(header);
 202         }
 203         prnt.write('#');
 204         prnt.println(new Date());
 205 
 206         for (Enumeration e = keys() ; e.hasMoreElements() ;) {
 207             String key = (String)e.nextElement();
 208             prnt.print(key);
 209             prnt.write('=');
 210 
 211             String val = (String)get(key);
 212             int len = val.length();
 213             boolean empty = false;
 214 
 215             for (int i = 0 ; i < len ; i++) {
 216                 int ch = val.charAt(i);
 217 
 218                 switch (ch) {
 219                   case '\\': prnt.write('\\'); prnt.write('\\'); break;
 220                   case '\t': prnt.write('\\'); prnt.write('t'); break;
 221                   case '\n': prnt.write('\\'); prnt.write('n'); break;
 222                   case '\r': prnt.write('\\'); prnt.write('r'); break;
 223 
 224                   default:
 225                     if ((ch < ' ') || (ch >= 127) || (empty && (ch == ' '))) {
 226                         if ((ch > 255) && localize) {
 227                             prnt.write(ch);
 228                         } else {
 229                             prnt.write('\\');
 230                             prnt.write('u');
 231                             prnt.write(toHex((ch >> 12) & 0xF));
 232                             prnt.write(toHex((ch >>  8) & 0xF));
 233                             prnt.write(toHex((ch >>  4) & 0xF));
 234                             prnt.write(toHex((ch >>  0) & 0xF));
 235                         }
 236                     } else {
 237                         prnt.write(ch);
 238                     }
 239                 }
 240                 empty = false;
 241             }
 242             prnt.write('\n');
 243         }
 244     }
 245 
 246     /**
 247      * Searches for the property with the specified key in this property list.
 248      * If the key is not found in this property list, the default property list,
 249      * and its defaults, recursively, are then checked. The method returns
 250      * <code>null</code> if the property is not found.
 251      *
 252      * @param   key   the property key.
 253      * @return  the value in this property list with the specified key value.
 254      * @see     com.sun.interview.Properties2#defaults
 255      */
 256     public String getProperty(String key) {
 257         Object oval = super.get(key);
 258         String sval = (oval instanceof String) ? (String)oval : null;
 259         return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
 260     }
 261 
 262     /**
 263      * Searches for the property with the specified key in this property list.
 264      * If the key is not found in this property list, the default property list,
 265      * and its defaults, recursively, are then checked. The method returns the
 266      * default value argument if the property is not found.
 267      *
 268      * @param   key            the hashtable key.
 269      * @param   defaultValue   a default value.
 270      *
 271      * @return  the value in this property list with the specified key value.
 272      * @see     com.sun.interview.Properties2#defaults
 273      */
 274     public String getProperty(String key, String defaultValue) {
 275         String val = getProperty(key);
 276         return (val == null) ? defaultValue : val;
 277     }
 278 
 279     /**
 280      * Returns an enumeration of all the keys in this property list, including
 281      * the keys in the default property list.
 282      *
 283      * @return  an enumeration of all the keys in this property list, including
 284      *          the keys in the default property list.
 285      * @see     java.util.Enumeration
 286      * @see     com.sun.interview.Properties2#defaults
 287      */
 288     public Enumeration propertyNames() {
 289         Hashtable<String, Object> h = new Hashtable<>();
 290         enumerate(h);
 291         return h.keys();
 292     }
 293 
 294 
 295     /**
 296      * Prints this property list out to the specified output stream.
 297      * This method is useful for debugging.
 298      *
 299      * @param   out   an output stream.
 300      */
 301     /*
 302      * Rather than use an anonymous inner class to share common code, this
 303      * method is duplicated in order to ensure that a non-1.1 compiler can
 304      * compile this file.
 305      */
 306     public void list(PrintWriter out) {
 307         out.println("-- listing properties --");
 308         Hashtable<String, Object> h = new Hashtable<>();
 309         enumerate(h);
 310         for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
 311             String key = (String)e.nextElement();
 312             String val = (String)h.get(key);
 313             if (val.length() > 40) {
 314                 val = val.substring(0, 37) + "...";
 315             }
 316             out.println(key + "=" + val);
 317         }
 318     }
 319 
 320     /**
 321      * Enumerates all key/value pairs in the specified hastable.
 322      * @param h the hashtable
 323      */
 324     private synchronized void enumerate(Hashtable<String, Object> h) {
 325         if (defaults != null) {
 326             defaults.enumerate(h);
 327         }
 328         for (Enumeration e = keys() ; e.hasMoreElements() ;) {
 329             String key = (String)e.nextElement();
 330             h.put(key, get(key));
 331         }
 332     }
 333 
 334     /**
 335      * Convert a nibble to a hex character
 336      * @param   nibble  the nibble to convert.
 337      */
 338     private static char toHex(int nibble) {
 339         return hexDigit[(nibble & 0xF)];
 340     }
 341 
 342     /** A table of hex digits */
 343     private static char[] hexDigit = {
 344         '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
 345     };
 346 }