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