1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2010, 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.javatest.util;
  28 
  29 // Modified from JDK java.util.Properties to use Reader/Writer as well as
  30 // InputStream/OutputStream
  31 
  32 import java.io.*;
  33 import java.util.*;
  34 
  35 /**
  36  * The <code>Properties</code> class represents a persistent set of
  37  * properties. The <code>Properties</code> can be saved to a stream
  38  * or loaded from a stream. Each key and its corresponding value in
  39  * the property list is a string.
  40  * <p>
  41  * A property list can contain another property list as its
  42  * "defaults"; this second property list is searched if
  43  * the property key is not found in the original property list.
  44  *
  45  * <p>
  46  * Modified from JDK java.util.Properties to use Reader/Writer as well as
  47  * InputStream/OutputStream, to fix locale/codepage problems.
  48  */
  49 public
  50 class Properties extends Hashtable<String, String> {
  51     /**
  52      * A property list that contains default values for any keys not
  53      * found in this property list.
  54      *
  55      * @since   JDK1.0
  56      */
  57     protected Properties defaults;
  58 
  59     /**
  60      * Creates an empty property list with no default values.
  61      *
  62      * @since   JDK1.0
  63      */
  64     public Properties() {
  65         this(null);
  66     }
  67 
  68     /**
  69      * Creates an empty property list with the specified defaults.
  70      *
  71      * @param   defaults   the defaults.
  72      * @since   JDK1.0
  73      */
  74     public Properties(Properties defaults) {
  75         this.defaults = defaults;
  76     }
  77 
  78     /**
  79      * Utility method that writes the given map of strings to the given output stream with provided comments
  80      * using {@code java.util.Properties.store(java.io.OutputStream, String)} method.
  81      */
  82     public static void store(Map<String, String> stringProps, OutputStream out, String comments) throws IOException {
  83         java.util.Properties properties = new java.util.Properties();
  84         properties.putAll(stringProps);
  85         properties.store(out, comments);
  86     }
  87 
  88     /**
  89      * Reads a property list from the given input stream
  90      * using {@code java.util.Properties.load(java.io.InputStream inStream)} method
  91      * and stores properties into a {@code Map<String, String>} that is returned.
  92      */
  93     public static Map<String, String> load(InputStream inputStream) throws IOException {
  94         return populate(new HashMap<String, String>(), inputStream);
  95     }
  96 
  97     /**
  98      * Reads a property list from the given input stream
  99      * using {@code java.util.Properties.load(java.io.InputStream inStream)} method
 100      * and stores string properties into a {@code SortedMap<String, String>} that is returned.
 101      */
 102     public static SortedMap<String, String> loadSorted(InputStream inputStream) throws IOException {
 103         return populate(new TreeMap<String, String>(), inputStream);
 104     }
 105 
 106     /**
 107      * Populated a map of strings using {@code java.util.Properties.load(java.io.InputStream)} method
 108      */
 109     private static <M extends Map<String, String>> M populate(M stringProps, InputStream inputStream) throws IOException {
 110         java.util.Properties p = new java.util.Properties();
 111         p.load(inputStream);
 112         return extractStringPropsTo(stringProps, p);
 113     }
 114 
 115     /**
 116      * Converts the given properties to {@code Map<String, String>} instance
 117      * picking only string properties from the given {@code java.util.Properties} instance.
 118      */
 119     public static Map<String, String> convertToStringProps(java.util.Properties properties) {
 120         return extractStringPropsTo(new HashMap<String, String>(), properties);
 121     }
 122 
 123     private static <M extends Map<String, String>> M extractStringPropsTo(M stringProps, java.util.Properties p) {
 124         for (String name : p.stringPropertyNames()) {
 125             stringProps.put(name, p.getProperty(name));
 126         }
 127         return stringProps;
 128     }
 129 
 130 //    /**
 131 //     * Reads a property list from an input stream.
 132 //     *
 133 //     * @param      in   the input stream.
 134 //     * @exception  IOException  if an error occurred when reading from the
 135 //     *               input stream.
 136 //     * @since   JDK1.0
 137 //     */
 138 //    public synchronized void load(InputStream in) throws IOException {
 139 //      in = Runtime.getRuntime().getLocalizedInputStream(in);
 140 //      load(new InputStreamReader(in));
 141 //    }
 142 
 143 
 144     /**
 145      * Reads a property list from an input stream.
 146      *
 147      * @param      in   the input stream.
 148      * @exception  IOException  if an error occurred when reading from the
 149      *               input stream.
 150      */
 151     public synchronized void load(Reader in) throws IOException {
 152         Vector<String> v = load0(in, false);
 153         for (int i = 0; i < v.size(); i+=2) {
 154             put(v.elementAt(i), v.elementAt(i + 1));
 155         }
 156     }
 157 
 158 
 159     static Vector<String> load0(Reader in, boolean breakOnEmptyLine) throws IOException {
 160 
 161 // This could be used if we switch to JDK 1.6, where Properties support I/O
 162 // through Reader and Writer
 163 //        StringBuffer buff = new StringBuffer();
 164 //        int lineCount = 0;
 165 //        int ch = '\n';
 166 //
 167 //        while (ch >= 0) {
 168 //            buff.append((char)ch);
 169 //            if (ch == '\n') {
 170 //                if (++lineCount > 1) {
 171 //                    break;
 172 //                }
 173 //            }
 174 //            else if (ch != '\r' && ch != '\t' && ch != ' ') {
 175 //                lineCount = 0;
 176 //            }
 177 //            ch = in.read();
 178 //        }
 179 //        Reader r = new StringReader(buff.toString());
 180 //        java.util.Properties props = new java.util.Properties();
 181 //        props.load(r);
 182 //
 183 //        return getArray(props);
 184 //
 185 //
 186 //
 187 
 188         Vector<String> v = new Vector<>();
 189         int ch = 0;
 190 
 191         //// eat any preceding whitespace
 192         //do {
 193         //    ch = in.read();
 194         //} while ((ch == '\n') || (ch == '\r') || (ch == '\t') || (ch == ' '));
 195 
 196         ch = '\n';
 197         // pretend to have read newline, so that if an initial
 198         // blank line is read, it is treated as the end of an empty props object
 199 
 200     parsing:
 201         while (true) {
 202             // parse until EOF or a blank line
 203             switch (ch) {
 204             case -1:
 205                 // EOF
 206                 break parsing;
 207 
 208             case '#':
 209             case '!':
 210                 // comment: skip to end of line
 211                 do {
 212                     ch = in.read();
 213                 } while ((ch >= 0) && (ch != '\n') && (ch != '\r'));
 214                 continue;
 215 
 216             case '\n':
 217             case '\t':
 218             case '\r':
 219             case ' ':
 220                 // skip whitespace but count newlines along the way;
 221                 // if there is more than one, we're done. This is intended
 222                 // to work on both \n and \r\n systems
 223                 int lines = 0;
 224                 do {
 225                     if (breakOnEmptyLine && ch == '\n' && (++lines > 1))
 226                         break parsing;
 227                     ch = in.read();
 228                 } while ((ch == '\n') || (ch == '\r') || (ch == '\t') || (ch == ' '));
 229                 continue;
 230 
 231             default:
 232 
 233 
 234 
 235 //               key=value
 236 //               start by reading the key; stop at newline (unless escaped in value)
 237                 StringBuffer key = new StringBuffer();
 238                 StringBuffer val = new StringBuffer();
 239 
 240                 boolean hasSep = false;
 241                 boolean precedingBackslash = false;
 242                 while (ch >=0 && (ch != '\n') && (ch != '\r')) {
 243                     //need check if escaped.
 244                     if ((ch == '=' ||  ch == ':') && !precedingBackslash) {
 245 //                        valueStart = keyLen + 1;
 246                         ch = in.read();
 247                         hasSep = true;
 248                         break;
 249                     } else if ((ch == ' ' || ch == '\t' ||  ch == '\f') && !precedingBackslash) {
 250 //                        valueStart = keyLen + 1;
 251                         ch = in.read();
 252                         break;
 253                     }
 254                     if (ch == '\\') {
 255                         precedingBackslash = !precedingBackslash;
 256                     } else {
 257                         precedingBackslash = false;
 258                     }
 259 //                    keyLen++;
 260                     key.append((char)ch);
 261                     ch = in.read();
 262                 }
 263 
 264                 // handle multi-char separator: prop  =    value
 265                 while (ch >=0 && (ch != '\n') && (ch != '\r')) {
 266 //                    c = lr.lineBuf[valueStart];
 267                     if (ch != ' ' && ch != '\t' &&  ch != '\f') {
 268                         if (!hasSep && (ch == '=' ||  ch == ':')) {
 269                             hasSep = true;
 270                         } else {
 271                             break;
 272                         }
 273                     }
 274 //                    valueStart++;
 275                     ch = in.read();
 276                 }
 277 //                while (ch >=0 && (ch != '\n') && (ch != '\r')) {
 278 //                    val.append((char)ch);
 279 //                    ch = in.read();
 280 //                }
 281 
 282             // Read the value
 283             while ((ch >= 0) && (ch != '\n') && (ch != '\r')) {
 284                 if (ch == '\\') {
 285                     switch (ch = in.read()) {
 286                       case '\r':
 287                         if (((ch = in.read()) == '\n') ||
 288                             (ch == ' ') || (ch == '\t')) {
 289                           // fall thru to '\n' case
 290                         } else continue;
 291                       case '\n':
 292                         while (((ch = in.read()) == ' ') || (ch == '\t'));
 293                         continue;
 294                       default:
 295                           val.append('\\');
 296                           break;
 297                     }
 298                 }
 299                 val.append((char)ch);
 300                 ch = in.read();
 301             }
 302 
 303                 char[] cKey = new char[key.length()];
 304                 char[] cVal = new char[val.length()];
 305                 key.getChars(0, key.length(), cKey, 0);
 306                 val.getChars(0, val.length(), cVal, 0);
 307 
 308                 v.add(loadConvert(cKey));
 309                 v.add(loadConvert(cVal));
 310 
 311             }
 312         }
 313 
 314         return v;
 315     }
 316 
 317 
 318 
 319 
 320     private static String loadConvert (char[] in/*, int off, int len, char[] convtBuf*/) {
 321 //        if (convtBuf.length < len) {
 322 //            int newLen = len * 2;
 323 //            if (newLen < 0) {
 324 //              newLen = Integer.MAX_VALUE;
 325 //          }
 326 //          convtBuf = new char[newLen];
 327 //        }
 328 //        char aChar;
 329 //        char[] out = convtBuf;
 330 //        int outLen = 0;
 331 //        int end = off + len;
 332         char aChar;
 333         int len = in.length;
 334         char[] out = new char[len];
 335         int outLen = 0;
 336         int off = 0;
 337 
 338         while (off < len) {
 339             aChar = in[off++];
 340             if (aChar == '\\') {
 341                 aChar = in[off++];
 342                 if(aChar == 'u') {
 343                     // Read the xxxx
 344                     int value=0;
 345                     for (int i=0; i<4; i++) {
 346                         aChar = in[off++];
 347                         switch (aChar) {
 348                           case '0': case '1': case '2': case '3': case '4':
 349                           case '5': case '6': case '7': case '8': case '9':
 350                              value = (value << 4) + aChar - '0';
 351                              break;
 352                           case 'a': case 'b': case 'c':
 353                           case 'd': case 'e': case 'f':
 354                              value = (value << 4) + 10 + aChar - 'a';
 355                              break;
 356                           case 'A': case 'B': case 'C':
 357                           case 'D': case 'E': case 'F':
 358                              value = (value << 4) + 10 + aChar - 'A';
 359                              break;
 360                           default:
 361                               throw new IllegalArgumentException(
 362                                            "Malformed \\uxxxx encoding.");
 363                         }
 364                      }
 365                     out[outLen++] = (char)value;
 366                 } else {
 367                     if (aChar == 't') aChar = '\t';
 368                     else if (aChar == 'r') aChar = '\r';
 369                     else if (aChar == 'n') aChar = '\n';
 370                     else if (aChar == 'f') aChar = '\f';
 371                     out[outLen++] = aChar;
 372                 }
 373             } else {
 374                 out[outLen++] = aChar;
 375             }
 376         }
 377         return new String (out, 0, outLen);
 378     }
 379 
 380 
 381 
 382 
 383 
 384 
 385 //    /**
 386 //     * Stores this property list to the specified output stream. The
 387 //     * string header is printed as a comment at the beginning of the stream.
 388 //     *
 389 //     * @param   out      an output stream.
 390 //     * @param   header   a description of the property list.
 391 //     * @since   JDK1.0
 392 //     */
 393 //    public synchronized void save(OutputStream out, String header) {
 394 //      OutputStream localOut = Runtime.getRuntime().getLocalizedOutputStream(out);
 395 //      boolean localize = localOut != out;
 396 //      save(new OutputStreamWriter(localOut), header, localize);
 397 //    }
 398 
 399     /**
 400      * Stores this property list to the specified output stream. The
 401      * string header is printed as a comment at the beginning of the stream.
 402      *
 403      * @param   out      an output stream.
 404      * @param   header   a description of the property list.
 405      * @since   JDK1.0
 406      */
 407     public synchronized void save(Writer out, String header) throws IOException {
 408         // From JDK 1.6 java.util.Properties
 409         BufferedWriter bout = (out instanceof BufferedWriter)?(BufferedWriter)out
 410                                                  : new BufferedWriter(out);
 411 
 412         PrintWriter prnt = new PrintWriter(bout);
 413         if (header != null) {
 414             prnt.write('#');
 415             prnt.println(header);
 416         }
 417         prnt.write('#');
 418         prnt.println(new Date());
 419 
 420         for (Enumeration<String> e = keys() ; e.hasMoreElements() ;) {
 421             String key = e.nextElement();
 422             String val = get(key);
 423             key = saveConvert(key, true, false);
 424             /* No need to escape embedded and trailing spaces for value, hence
 425              * pass false to flag.
 426              */
 427             val = saveConvert(val, false, false);
 428             bout.write(key + "=" + val);
 429             bout.newLine();
 430         }
 431         bout.flush();
 432     }
 433 
 434     // Copied from JDK 1.6
 435     static String saveConvert(String theString,
 436                                boolean escapeSpace,
 437                                boolean escapeUnicode) {
 438         int len = theString.length();
 439         int bufLen = len * 2;
 440         if (bufLen < 0) {
 441             bufLen = Integer.MAX_VALUE;
 442         }
 443         StringBuffer outBuffer = new StringBuffer(bufLen);
 444 
 445         for(int x=0; x<len; x++) {
 446             char aChar = theString.charAt(x);
 447             // Handle common case first, selecting largest block that
 448             // avoids the specials below
 449             if ((aChar > 61) && (aChar < 127)) {
 450                 if (aChar == '\\') {
 451                     outBuffer.append('\\'); outBuffer.append('\\');
 452                     continue;
 453                 }
 454                 outBuffer.append(aChar);
 455                 continue;
 456             }
 457             switch(aChar) {
 458                 case ' ':
 459                     if (x == 0 || escapeSpace)
 460                         outBuffer.append('\\');
 461                     outBuffer.append(' ');
 462                     break;
 463                 case '\t':outBuffer.append('\\'); outBuffer.append('t');
 464                           break;
 465                 case '\n':outBuffer.append('\\'); outBuffer.append('n');
 466                           break;
 467                 case '\r':outBuffer.append('\\'); outBuffer.append('r');
 468                           break;
 469                 case '\f':outBuffer.append('\\'); outBuffer.append('f');
 470                           break;
 471                 case '=': // Fall through
 472                 case ':': // Fall through
 473                 case '#': // Fall through
 474                 case '!':
 475                     outBuffer.append('\\'); outBuffer.append(aChar);
 476                     break;
 477                 default:
 478                     if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) {
 479                         outBuffer.append('\\');
 480                         outBuffer.append('u');
 481                         outBuffer.append(toHex((aChar >> 12) & 0xF));
 482                         outBuffer.append(toHex((aChar >>  8) & 0xF));
 483                         outBuffer.append(toHex((aChar >>  4) & 0xF));
 484                         outBuffer.append(toHex( aChar        & 0xF));
 485                     } else {
 486                         outBuffer.append(aChar);
 487                     }
 488             }
 489         }
 490         return outBuffer.toString();
 491     }
 492 
 493 
 494     /**
 495      * Searches for the property with the specified key in this property list.
 496      * If the key is not found in this property list, the default property list,
 497      * and its defaults, recursively, are then checked. The method returns
 498      * <code>null</code> if the property is not found.
 499      *
 500      * @param   key   the property key.
 501      * @return  the value in this property list with the specified key value.
 502      * @see     com.sun.javatest.util.Properties#defaults
 503      * @since   JDK1.0
 504      */
 505     public String getProperty(String key) {
 506         Object oval = super.get(key);
 507         String sval = (oval instanceof String) ? (String)oval : null;
 508         return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
 509     }
 510 
 511     /**
 512      * Searches for the property with the specified key in this property list.
 513      * If the key is not found in this property list, the default property list,
 514      * and its defaults, recursively, are then checked. The method returns the
 515      * default value argument if the property is not found.
 516      *
 517      * @param   key            the hashtable key.
 518      * @param   defaultValue   a default value.
 519      *
 520      * @return  the value in this property list with the specified key value.
 521      * @see     com.sun.javatest.util.Properties#defaults
 522      * @since   JDK1.0
 523      */
 524     public String getProperty(String key, String defaultValue) {
 525         String val = getProperty(key);
 526         return (val == null) ? defaultValue : val;
 527     }
 528 
 529     /**
 530      * Returns an enumeration of all the keys in this property list, including
 531      * the keys in the default property list.
 532      *
 533      * @return  an enumeration of all the keys in this property list, including
 534      *          the keys in the default property list.
 535      * @see     java.util.Enumeration
 536      * @see     com.sun.javatest.util.Properties#defaults
 537      * @since   JDK1.0
 538      */
 539     public Enumeration<String> propertyNames() {
 540         Hashtable<String, String> h = new Hashtable<>();
 541         enumerate(h);
 542         return h.keys();
 543     }
 544 
 545 //    /**
 546 //     * Prints this property list out to the specified output stream.
 547 //     * This method is useful for debugging.
 548 //     *
 549 //     * @param   out   an output stream.
 550 //     * @since   JDK1.0
 551 //     */
 552 //    public void list(PrintStream out) {
 553 //      list(new PrintWriter(out));
 554 //    }
 555 
 556     /**
 557      * Prints this property list out to the specified output stream.
 558      * This method is useful for debugging.
 559      *
 560      * @param   out   an output stream.
 561      * @since   JDK1.1
 562      */
 563     /*
 564      * Rather than use an anonymous inner class to share common code, this
 565      * method is duplicated in order to ensure that a non-1.1 compiler can
 566      * compile this file.
 567      */
 568     public void list(PrintWriter out) {
 569         out.println("-- listing properties --");
 570         Hashtable<String, String> h = new Hashtable<>();
 571         enumerate(h);
 572         for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
 573             String key = e.nextElement();
 574             String val = h.get(key);
 575             if (val.length() > 40) {
 576                 val = val.substring(0, 37) + "...";
 577             }
 578             out.println(key + "=" + val);
 579         }
 580     }
 581 
 582     /**
 583      * Enumerates all key/value pairs in the specified hastable.
 584      * @param h the hashtable
 585      */
 586     private synchronized void enumerate(Map<String, String> h) {
 587         if (defaults != null) {
 588             defaults.enumerate(h);
 589         }
 590         for (Enumeration<String> e = keys() ; e.hasMoreElements() ;) {
 591             String key = e.nextElement();
 592             h.put(key, get(key));
 593         }
 594     }
 595 
 596     /**
 597      * Convert a nibble to a hex character
 598      * @param   nibble  the nibble to convert.
 599      */
 600     private static char toHex(int nibble) {
 601         return hexDigit[(nibble & 0xF)];
 602     }
 603 
 604     /** A table of hex digits */
 605     private static char[] hexDigit = {
 606         '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
 607     };
 608 }