/*
* $Id$
*
* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javatest.util;
import java.io.BufferedWriter;
import java.util.*;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
/**
* A space-efficient string to string map.
* This class is similar to java.util.Properties.
* For this class, space is more important than speed. Use this class when
* you care much more about wasted space than wasting time doing reference
* juggling in memory. Arrays in this class must correspond to this format:
*
*
* {"key1", "value1", "key2", "value2", ...}
*
*/
// This code was derived from that in com.sun.javatest.util.Properties.
public class PropertyArray {
/**
* A class used to report problems that may occur when using PropertyArray.
*/
public static class PropertyArrayError extends Error {
/**
* Create a PropertyArrayError object.
*/
public PropertyArrayError() {
super();
}
/**
* Create a PropertyArrayError object.
* @param msg a detail message for the error
*/
public PropertyArrayError(String msg) {
super(msg);
}
}
/**
* Create a mutable object.
*/
public PropertyArray() {
locked = false;
}
/**
* Create a mutable object.
* @param initSize the initial capacity of the array
*/
public PropertyArray(int initSize) {
// This method probably should be some kind of optimization, but if
// we pre-create the array, how will be know the size? Another data
// member I suppose...
locked = false;
}
/**
* Create a immutable object, from data read from on a stream in
* the format of a standard Java properties file.
* @param in the stream from which to read the properties
* @throws IOException if a problem occurred while reading the data
*/
public PropertyArray(Reader in) throws IOException {
dataA = load(in);
locked = true;
}
/**
* Create a immutable PropertyArray object from a standard Properties object.
* @param props the object from which to initialize the array
*/
public PropertyArray(Map props) {
dataA = getArray(props);
locked = true;
}
/**
* Create a immutable PropertyArray object from data in a compact array of
* names and values.
* @param data an array containing pairs of entries: even-numbered entries
* identify the names of properties, odd-numbered entries give the value
* for the preceding property name.
*/
public PropertyArray(String[] data) {
locked = true;
dataA = new String[data.length];
// shallow copy the array
for (int i = 0; i < data.length; i++) {
dataA[i] = data[i];
}
}
// --------------- STATIC METHODS ----------------
/**
* Get a compact array containing the names and values of entries
* from a standard Properties object.
* @param props the Properties object from which to get the data
* @return an array containing the names of the properties in even-numbered
* entries, and the corresponding values in the adjacent odd-numbered entries
*/
public static String[] getArray(Map props) {
Vector data = new Vector<>(props.size(),2);
for (Map.Entry entry : props.entrySet()) {
insert(data, entry.getKey(), entry.getValue());
}
String[] arr = new String[data.size()];
data.copyInto(arr);
return arr;
}
/**
* Add a mapping to an array, returning a new array.
* @param data The array to which to the new array is to be added. May be null.
* @param key the name of the new value to be added
* @param value the new value to be added
* @return an array with the new element added
* @exception PropertyArrayError May be thrown if a null key or value is
* supplied.
*/
public static String[] put(String[] data, String key, String value) {
Vector vec;
String[] arr;
String old = null;
if (key == null || value == null) {
// i'd like to make null values legal but...
throw new PropertyArrayError(
"A key or value was null. Null keys and values are illegal");
}
if (data == null) {
data = new String[0];
}
// key == null is verified
vec = copyOutOf(data);
old = insert(vec, key, value);
arr = new String[vec.size()];
vec.copyInto(arr);
return arr;
}
/**
* Get a named value from the array of properties.
* If the given data array is null or zero length, null is returned.
* If the key paramter is null, null will be returned, no error will
* occur.
* @param data an array containing sequential name value pairs
* @param key the name of the property to be returned
* @return the value of the named entry, or null if not found
*/
public static String get(String[] data, String key) {
if (data == null || data.length == 0 || key == null) {
return null;
}
int lower = 0;
int upper = data.length - 2;
int mid;
if (upper < 0)
return null;
String last = data[upper];
int cmp = key.compareTo(last);
if (cmp > 0)
return null;
while (lower <= upper) {
// in next line, take care to ensure that mid is always even
mid = lower + ((upper - lower) / 4) * 2;
String e = data[mid];
cmp = key.compareTo(e);
if (cmp < 0)
upper = mid - 2;
else if (cmp > 0)
lower = mid + 2;
else
return data[mid+1];
}
// did not find an exact match
return null;
}
/**
* Remove an entry from an array of properties.
* @param data an array of sequential name value properties
* @param key the name of the entry to be removed
* @return an array that does not contain the named property
*/
public static String[] remove(String[] data, String key) {
Vector vec = copyOutOf(data);
int lower = 0;
int upper = vec.size() - 2;
int mid = 0;
String old = null;
if (upper < 0) {
// no data yet
return data;
}
// goes at the end
String last = vec.elementAt(upper);
int cmp = key.compareTo(last);
if (cmp > 0) {
return data;
}
while (lower <= upper) {
// in next line, take care to ensure that mid is always even
mid = lower + ((upper - lower) / 4) * 2;
String e = (vec.elementAt(mid));
cmp = key.compareTo(e);
if (cmp < 0) {
upper = mid - 2;
}
else if (cmp > 0) {
lower = mid + 2;
}
else {
// strings equal, zap key and value
vec.removeElementAt(mid);
old = vec.elementAt(mid);
vec.removeElementAt(mid);
break;
}
}
String[] outData = new String[vec.size()];
vec.copyInto(outData);
return outData;
}
/**
* Get a standard Map object from an array of properties.
* @param data an array of sequential name value properties
* @return a Map object containing data from the array
*/
public static Map getProperties(String[] data) {
Map props = new HashMap<>();
if (data != null && data.length > 0) {
for (int i = 0; i < data.length; i+=2) {
props.put(data[i], data[i+1]);
} // for
} // else
return props;
}
/**
* Write an array of properties to a stream.
* The data is written using the format for standard Java property files.
* @param data an array of sequential name value properties
* @param out a stream to which to write the data
* @throws IOException if a problem occurred while writing to the stream
* @see #load(Reader)
*/
public static void save(String[] data, Writer out) throws IOException {
// This could be used if we switch to JDK 1.6, where Properties support I/O
// through Reader and Writer
//
// Properties props = getProperties(data);
// StringWriter sw = new StringWriter();
// props.store(sw, null);
//
// StringBuffer sb = sw.getBuffer();
// while (sb.length() > 0 && sb.charAt(0) == '#') {
// int end = sb.indexOf(lineSeparator);
// sb = sb.delete(0, end);
// if (sb.length() > 0) {
// char ch = sb.charAt(0);
// while ((ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ')) {
// sb = sb.deleteCharAt(0);
// if (sb.length() > 0) {
// ch = sb.charAt(0);
// }
// else {
// break;
// }
// }
// }
// }
//
// out.write(sb.toString());
if (data == null || data.length == 0) {
return;
}
// From JDK 1.6 java.util.Properties
BufferedWriter bout = (out instanceof BufferedWriter)?(BufferedWriter)out
: new BufferedWriter(out);
for (int i = 0; i < data.length; i+=2) {
String key = data[i];
String val = data[i+1];
key = Properties.saveConvert(key, true, false);
/* No need to escape embedded and trailing spaces for value, hence
* pass false to flag.
*/
val = Properties.saveConvert(val, false, false);
bout.write(key + "=" + val);
bout.newLine();
}
bout.flush();
}
/**
* Read an array of properties from an input stream.
* The data will be read according to the standard format for Java
* property files.
* @param in the stream from which to read the data
* @return an array of sequential name value properties
* @throws IOException if an error occurred while reading the data
* @see #save(String[], Writer)
*/
public static String[] load(Reader in) throws IOException {
Vector v = Properties.load0(in, true);
Vector sorted = new Vector<>(v.size());
for (int i = 0; i < v.size(); i+=2) {
insert(sorted, v.elementAt(i), v.elementAt(i + 1));
}
String[] data = new String[sorted.size()];
sorted.copyInto(data);
return data;
}
/**
* Enumerate the properties in an array.
* @param props an array of sequential name value properties
* @return an enumeration of the properties in the array
*/
public static Enumeration enumerate(final String[] props) {
return new Enumeration() {
int pos = 0;
public boolean hasMoreElements() {
return (props != null && pos < props.length);
}
public String nextElement() {
if (props == null || pos >= props.length) {
return null;
}
else {
String current = props[pos];
pos += 2;
return current;
}
}
};
}
// --------------- INSTANCE METHODS ----------------
/**
* Get the data in this PropertyArray as a standard Properties object.
* @return a Properties object containing the same data as this PropertyArray
*/
public Map getProperties() {
return getProperties(dataA);
}
/**
* Check if the property array is mutable.
* @return true if data can be stored in this array, and false otherwise
*/
public boolean isMutable() {
return !locked;
}
/**
* Get the number of properties stored in the property array.
* @return the number of properties stored in the property array
*/
public int size() {
if (dataA == null) {
return 0;
}
else {
return dataA.length / 2;
}
}
/**
* Get the value of a named property.
* @param key the name of the desired property
* @return the value of the property, or null if it was not found
*/
public String get(String key) {
return get(dataA, key);
}
/**
* Get a copy of the data in this PropertyArray.
* @return a copy of the data, or null if there is no data.
*/
public String[] getArray() {
if (dataA == null || dataA.length == 0) {
return null;
}
return shallowCopy(dataA);
}
/**
* Put a property into the PropertyArray.
* @param key the name of the property to be added
* @param value the value of the property to be added
* @return the previous value (if any) of this property
* @throws PropertyArrayError if a null key or value is supplied.
*/
public String put(String key, String value) {
String old = null;
if (locked == true) {
throw new IllegalStateException("PropertyArray is immutable.");
}
if (key == null || value == null) {
// i'd like to make null values legal but...
throw new PropertyArrayError(
"A key or value was null. Null keys and values are illegal");
}
Vector vec = copyOutOf(dataA);
old = insert(vec, key, value);
dataA = new String[vec.size()];
vec.copyInto(dataA);
return old;
}
/**
* Remove a property.
* @param key the name of the property to be removed
*/
public void remove(String key) {
dataA = remove(dataA, key);
}
/**
* Save the properties to a stream. The data is written using the format
* for a standard Java properties file.
* @param out the stream to which to write the data
* @throws IOException if an error occurred while writing the data
*/
public void save(Writer out) throws IOException {
save(dataA, out);
}
// --------------- PRIVATE ----------------
/**
* Put a new value in, overwriting existing values.
*
* @param vec Vector to add data to. Must not be null!
*/
private static String insert(Vector vec, String key, String value) {
int lower = 0;
int upper = vec.size() - 2;
int mid = 0;
String old = null;
if (upper < 0) {
// no data yet
vec.addElement(key);
vec.addElement(value);
return old;
}
// goes at the end
String last = vec.elementAt(upper);
int cmp = key.compareTo(last);
if (cmp > 0) {
vec.addElement(key);
vec.addElement(value);
return null;
}
while (lower <= upper) {
// in next line, take care to ensure that mid is always even
mid = lower + ((upper - lower) / 4) * 2;
String e = vec.elementAt(mid);
cmp = key.compareTo(e);
if (cmp < 0) {
upper = mid - 2;
}
else if (cmp > 0) {
lower = mid + 2;
}
else {
// strings equal
vec.removeElementAt(mid);
old = vec.elementAt(mid);
vec.removeElementAt(mid);
break;
}
}
// did not find an exact match (we did not expect to)
// adjust the insert point
if (cmp > 0)
mid += 2;
vec.insertElementAt(key, mid);
vec.insertElementAt(value, mid+1);
return old;
}
private static Vector copyOutOf(String[] data) {
Vector vec = null;
if (data == null) {
vec = new Vector<>(0,2);
}
else {
vec = new Vector<>(data.length,2);
for (int i = 0; i < data.length; i++) {
vec.addElement(data[i]);
} // for
}
return vec;
}
private static String[] shallowCopy(String[] arrIn) {
if (arrIn == null) {
return null;
}
String[] arrOut = new String[arrIn.length];
// shallow copy the array
for (int i = 0; i < arrIn.length; i++) {
arrOut[i] = arrIn[i];
}
return arrOut;
}
/**
* Convert a nibble to a hex character
* @param nibble the nibble to convert.
*/
private static char toHex(int nibble) {
return hexDigit[(nibble & 0xF)];
}
/** A table of hex digits */
private static char[] hexDigit = {
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
private static final String lineSeparator = System.getProperty("line.separator");
private String[] dataA;
private boolean locked;
}