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 }