1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2011, 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 import java.io.BufferedWriter; 30 import java.util.*; 31 import java.io.IOException; 32 import java.io.Reader; 33 import java.io.Writer; 34 35 /** 36 * A space-efficient string to string map. 37 * This class is similar to java.util.Properties. 38 * For this class, space is more important than speed. Use this class when 39 * you care much more about wasted space than wasting time doing reference 40 * juggling in memory. Arrays in this class must correspond to this format: 41 * <br> 42 * <Pre> 43 * {"key1", "value1", "key2", "value2", ...} 44 * </Pre> 45 */ 46 47 // This code was derived from that in com.sun.javatest.util.Properties. 48 49 public class PropertyArray { 50 /** 51 * A class used to report problems that may occur when using PropertyArray. 52 */ 53 public static class PropertyArrayError extends Error { 54 /** 55 * Create a PropertyArrayError object. 56 */ 57 public PropertyArrayError() { 58 super(); 59 } 60 61 /** 62 * Create a PropertyArrayError object. 63 * @param msg a detail message for the error 64 */ 65 public PropertyArrayError(String msg) { 66 super(msg); 67 } 68 } 69 70 /** 71 * Create a mutable object. 72 */ 73 public PropertyArray() { 74 locked = false; 75 } 76 77 /** 78 * Create a mutable object. 79 * @param initSize the initial capacity of the array 80 */ 81 public PropertyArray(int initSize) { 82 // This method probably should be some kind of optimization, but if 83 // we pre-create the array, how will be know the size? Another data 84 // member I suppose... 85 locked = false; 86 } 87 88 /** 89 * Create a immutable object, from data read from on a stream in 90 * the format of a standard Java properties file. 91 * @param in the stream from which to read the properties 92 * @throws IOException if a problem occurred while reading the data 93 */ 94 public PropertyArray(Reader in) throws IOException { 95 dataA = load(in); 96 locked = true; 97 } 98 99 /** 100 * Create a immutable PropertyArray object from a standard Properties object. 101 * @param props the object from which to initialize the array 102 */ 103 public PropertyArray(Map<String, String> props) { 104 dataA = getArray(props); 105 106 locked = true; 107 } 108 109 /** 110 * Create a immutable PropertyArray object from data in a compact array of 111 * names and values. 112 * @param data an array containing pairs of entries: even-numbered entries 113 * identify the names of properties, odd-numbered entries give the value 114 * for the preceding property name. 115 */ 116 public PropertyArray(String[] data) { 117 locked = true; 118 dataA = new String[data.length]; 119 120 // shallow copy the array 121 for (int i = 0; i < data.length; i++) { 122 dataA[i] = data[i]; 123 } 124 } 125 126 // --------------- STATIC METHODS ---------------- 127 128 /** 129 * Get a compact array containing the names and values of entries 130 * from a standard Properties object. 131 * @param props the Properties object from which to get the data 132 * @return an array containing the names of the properties in even-numbered 133 * entries, and the corresponding values in the adjacent odd-numbered entries 134 */ 135 public static String[] getArray(Map<String, String> props) { 136 Vector<String> data = new Vector<>(props.size(),2); 137 for (Map.Entry<String, String> entry : props.entrySet()) { 138 insert(data, entry.getKey(), entry.getValue()); 139 } 140 141 String[] arr = new String[data.size()]; 142 data.copyInto(arr); 143 144 return arr; 145 } 146 147 /** 148 * Add a mapping to an array, returning a new array. 149 * @param data The array to which to the new array is to be added. May be null. 150 * @param key the name of the new value to be added 151 * @param value the new value to be added 152 * @return an array with the new element added 153 * @exception PropertyArrayError May be thrown if a null key or value is 154 * supplied. 155 */ 156 public static String[] put(String[] data, String key, String value) { 157 Vector<String> vec; 158 String[] arr; 159 String old = null; 160 161 if (key == null || value == null) { 162 // i'd like to make null values legal but... 163 throw new PropertyArrayError( 164 "A key or value was null. Null keys and values are illegal"); 165 } 166 167 if (data == null) { 168 data = new String[0]; 169 } 170 171 // key == null is verified 172 vec = copyOutOf(data); 173 old = insert(vec, key, value); 174 arr = new String[vec.size()]; 175 vec.copyInto(arr); 176 177 return arr; 178 } 179 180 /** 181 * Get a named value from the array of properties. 182 * If the given data array is null or zero length, null is returned. 183 * If the key paramter is null, null will be returned, no error will 184 * occur. 185 * @param data an array containing sequential name value pairs 186 * @param key the name of the property to be returned 187 * @return the value of the named entry, or null if not found 188 */ 189 public static String get(String[] data, String key) { 190 if (data == null || data.length == 0 || key == null) { 191 return null; 192 } 193 194 int lower = 0; 195 int upper = data.length - 2; 196 int mid; 197 198 if (upper < 0) 199 return null; 200 201 String last = data[upper]; 202 int cmp = key.compareTo(last); 203 if (cmp > 0) 204 return null; 205 206 while (lower <= upper) { 207 // in next line, take care to ensure that mid is always even 208 mid = lower + ((upper - lower) / 4) * 2; 209 String e = data[mid]; 210 cmp = key.compareTo(e); 211 if (cmp < 0) 212 upper = mid - 2; 213 else if (cmp > 0) 214 lower = mid + 2; 215 else 216 return data[mid+1]; 217 } 218 219 // did not find an exact match 220 return null; 221 } 222 223 /** 224 * Remove an entry from an array of properties. 225 * @param data an array of sequential name value properties 226 * @param key the name of the entry to be removed 227 * @return an array that does not contain the named property 228 */ 229 public static String[] remove(String[] data, String key) { 230 Vector<String> vec = copyOutOf(data); 231 int lower = 0; 232 int upper = vec.size() - 2; 233 int mid = 0; 234 String old = null; 235 236 if (upper < 0) { 237 // no data yet 238 return data; 239 } 240 241 // goes at the end 242 String last = vec.elementAt(upper); 243 int cmp = key.compareTo(last); 244 if (cmp > 0) { 245 return data; 246 } 247 248 while (lower <= upper) { 249 // in next line, take care to ensure that mid is always even 250 mid = lower + ((upper - lower) / 4) * 2; 251 String e = (vec.elementAt(mid)); 252 cmp = key.compareTo(e); 253 if (cmp < 0) { 254 upper = mid - 2; 255 } 256 else if (cmp > 0) { 257 lower = mid + 2; 258 } 259 else { 260 // strings equal, zap key and value 261 vec.removeElementAt(mid); 262 old = vec.elementAt(mid); 263 vec.removeElementAt(mid); 264 break; 265 } 266 } 267 268 String[] outData = new String[vec.size()]; 269 vec.copyInto(outData); 270 return outData; 271 } 272 273 /** 274 * Get a standard Map object from an array of properties. 275 * @param data an array of sequential name value properties 276 * @return a Map object containing data from the array 277 */ 278 public static Map<String, String> getProperties(String[] data) { 279 Map<String, String> props = new HashMap<>(); 280 281 if (data != null && data.length > 0) { 282 for (int i = 0; i < data.length; i+=2) { 283 props.put(data[i], data[i+1]); 284 } // for 285 } // else 286 return props; 287 } 288 289 /** 290 * Write an array of properties to a stream. 291 * The data is written using the format for standard Java property files. 292 * @param data an array of sequential name value properties 293 * @param out a stream to which to write the data 294 * @throws IOException if a problem occurred while writing to the stream 295 * @see #load(Reader) 296 */ 297 public static void save(String[] data, Writer out) throws IOException { 298 299 // This could be used if we switch to JDK 1.6, where Properties support I/O 300 // through Reader and Writer 301 // 302 // Properties props = getProperties(data); 303 // StringWriter sw = new StringWriter(); 304 // props.store(sw, null); 305 // 306 // StringBuffer sb = sw.getBuffer(); 307 // while (sb.length() > 0 && sb.charAt(0) == '#') { 308 // int end = sb.indexOf(lineSeparator); 309 // sb = sb.delete(0, end); 310 // if (sb.length() > 0) { 311 // char ch = sb.charAt(0); 312 // while ((ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ')) { 313 // sb = sb.deleteCharAt(0); 314 // if (sb.length() > 0) { 315 // ch = sb.charAt(0); 316 // } 317 // else { 318 // break; 319 // } 320 // } 321 // } 322 // } 323 // 324 // out.write(sb.toString()); 325 326 327 if (data == null || data.length == 0) { 328 return; 329 } 330 331 // From JDK 1.6 java.util.Properties 332 BufferedWriter bout = (out instanceof BufferedWriter)?(BufferedWriter)out 333 : new BufferedWriter(out); 334 for (int i = 0; i < data.length; i+=2) { 335 String key = data[i]; 336 String val = data[i+1]; 337 key = Properties.saveConvert(key, true, false); 338 /* No need to escape embedded and trailing spaces for value, hence 339 * pass false to flag. 340 */ 341 val = Properties.saveConvert(val, false, false); 342 bout.write(key + "=" + val); 343 bout.newLine(); 344 } 345 bout.flush(); 346 347 } 348 349 /** 350 * Read an array of properties from an input stream. 351 * The data will be read according to the standard format for Java 352 * property files. 353 * @param in the stream from which to read the data 354 * @return an array of sequential name value properties 355 * @throws IOException if an error occurred while reading the data 356 * @see #save(String[], Writer) 357 */ 358 public static String[] load(Reader in) throws IOException { 359 360 Vector<String> v = Properties.load0(in, true); 361 Vector<String> sorted = new Vector<>(v.size()); 362 for (int i = 0; i < v.size(); i+=2) { 363 insert(sorted, v.elementAt(i), v.elementAt(i + 1)); 364 } 365 366 String[] data = new String[sorted.size()]; 367 sorted.copyInto(data); 368 return data; 369 } 370 371 /** 372 * Enumerate the properties in an array. 373 * @param props an array of sequential name value properties 374 * @return an enumeration of the properties in the array 375 */ 376 public static Enumeration<String> enumerate(final String[] props) { 377 return new Enumeration<String>() { 378 int pos = 0; 379 380 public boolean hasMoreElements() { 381 return (props != null && pos < props.length); 382 } 383 384 public String nextElement() { 385 if (props == null || pos >= props.length) { 386 return null; 387 } 388 else { 389 String current = props[pos]; 390 pos += 2; 391 return current; 392 } 393 } 394 }; 395 } 396 397 // --------------- INSTANCE METHODS ---------------- 398 399 /** 400 * Get the data in this PropertyArray as a standard Properties object. 401 * @return a Properties object containing the same data as this PropertyArray 402 */ 403 public Map<String, String> getProperties() { 404 return getProperties(dataA); 405 } 406 407 /** 408 * Check if the property array is mutable. 409 * @return true if data can be stored in this array, and false otherwise 410 */ 411 public boolean isMutable() { 412 return !locked; 413 } 414 415 /** 416 * Get the number of properties stored in the property array. 417 * @return the number of properties stored in the property array 418 */ 419 public int size() { 420 if (dataA == null) { 421 return 0; 422 } 423 else { 424 return dataA.length / 2; 425 } 426 } 427 428 /** 429 * Get the value of a named property. 430 * @param key the name of the desired property 431 * @return the value of the property, or null if it was not found 432 */ 433 public String get(String key) { 434 return get(dataA, key); 435 } 436 437 /** 438 * Get a copy of the data in this PropertyArray. 439 * @return a copy of the data, or null if there is no data. 440 */ 441 public String[] getArray() { 442 if (dataA == null || dataA.length == 0) { 443 return null; 444 } 445 446 return shallowCopy(dataA); 447 } 448 449 /** 450 * Put a property into the PropertyArray. 451 * @param key the name of the property to be added 452 * @param value the value of the property to be added 453 * @return the previous value (if any) of this property 454 * @throws PropertyArrayError if a null key or value is supplied. 455 */ 456 public String put(String key, String value) { 457 String old = null; 458 459 if (locked == true) { 460 throw new IllegalStateException("PropertyArray is immutable."); 461 } 462 463 if (key == null || value == null) { 464 // i'd like to make null values legal but... 465 throw new PropertyArrayError( 466 "A key or value was null. Null keys and values are illegal"); 467 } 468 469 Vector<String> vec = copyOutOf(dataA); 470 old = insert(vec, key, value); 471 dataA = new String[vec.size()]; 472 vec.copyInto(dataA); 473 474 return old; 475 } 476 477 /** 478 * Remove a property. 479 * @param key the name of the property to be removed 480 */ 481 public void remove(String key) { 482 dataA = remove(dataA, key); 483 } 484 485 /** 486 * Save the properties to a stream. The data is written using the format 487 * for a standard Java properties file. 488 * @param out the stream to which to write the data 489 * @throws IOException if an error occurred while writing the data 490 */ 491 public void save(Writer out) throws IOException { 492 save(dataA, out); 493 } 494 495 // --------------- PRIVATE ---------------- 496 497 /** 498 * Put a new value in, overwriting existing values. 499 * 500 * @param vec Vector to add data to. Must not be null! 501 */ 502 private static String insert(Vector<String> vec, String key, String value) { 503 int lower = 0; 504 int upper = vec.size() - 2; 505 int mid = 0; 506 String old = null; 507 508 if (upper < 0) { 509 // no data yet 510 vec.addElement(key); 511 vec.addElement(value); 512 return old; 513 } 514 515 // goes at the end 516 String last = vec.elementAt(upper); 517 int cmp = key.compareTo(last); 518 if (cmp > 0) { 519 vec.addElement(key); 520 vec.addElement(value); 521 return null; 522 } 523 524 while (lower <= upper) { 525 // in next line, take care to ensure that mid is always even 526 mid = lower + ((upper - lower) / 4) * 2; 527 String e = vec.elementAt(mid); 528 cmp = key.compareTo(e); 529 if (cmp < 0) { 530 upper = mid - 2; 531 } 532 else if (cmp > 0) { 533 lower = mid + 2; 534 } 535 else { 536 // strings equal 537 vec.removeElementAt(mid); 538 old = vec.elementAt(mid); 539 vec.removeElementAt(mid); 540 break; 541 } 542 } 543 544 // did not find an exact match (we did not expect to) 545 // adjust the insert point 546 if (cmp > 0) 547 mid += 2; 548 549 vec.insertElementAt(key, mid); 550 vec.insertElementAt(value, mid+1); 551 552 return old; 553 } 554 555 private static Vector<String> copyOutOf(String[] data) { 556 Vector<String> vec = null; 557 558 if (data == null) { 559 vec = new Vector<>(0,2); 560 } 561 else { 562 vec = new Vector<>(data.length,2); 563 564 for (int i = 0; i < data.length; i++) { 565 vec.addElement(data[i]); 566 } // for 567 } 568 569 return vec; 570 } 571 572 private static String[] shallowCopy(String[] arrIn) { 573 if (arrIn == null) { 574 return null; 575 } 576 577 String[] arrOut = new String[arrIn.length]; 578 579 // shallow copy the array 580 for (int i = 0; i < arrIn.length; i++) { 581 arrOut[i] = arrIn[i]; 582 } 583 584 return arrOut; 585 } 586 587 /** 588 * Convert a nibble to a hex character 589 * @param nibble the nibble to convert. 590 */ 591 private static char toHex(int nibble) { 592 return hexDigit[(nibble & 0xF)]; 593 } 594 595 /** A table of hex digits */ 596 private static char[] hexDigit = { 597 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' 598 }; 599 600 private static final String lineSeparator = System.getProperty("line.separator"); 601 602 private String[] dataA; 603 private boolean locked; 604 605 606 } 607