
com.brettonw.bag.BagArray Maven / Gradle / Ivy
package com.brettonw.bag;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* A collection of text-based values stored in a zero-based indexed array.
*
* Note: the BagArray class, from a memory allocation standpoint, is not designed to efficiently
* work with dynamic storage of very large numbers of elements (more than 1,000s). It will work,
* but we have not chosen to focus on this as a potential use-case.
*/
public class BagArray extends Base {
private static final Logger log = LogManager.getLogger (BagArray.class);
private static final int START_SIZE = 1;
private static final int DOUBLING_CAP = 128;
private Object[] container;
private int count;
/**
* Create a new BagArray with a default underlying storage size.
*/
public BagArray () {
count = 0;
container = new Object[START_SIZE];
}
/**
* Create a new BagArray with hint for the underlying storage size.
*
* @param size The expected number of elements in the BagArray, treated as a hint to optimize
* memory allocation. If additional elements are stored, the BagArray will revert
* to normal allocation behavior.
*/
public BagArray (int size) {
count = 0;
container = new Object[size];
}
/**
* Return the number of elements stored in the BagArray.
*
* @return the count of elements in the underlying store. This is distinct from the capacity of
* the underlying store.
*/
public int getCount () {
return count;
}
private void grow (int gapIndex) {
// save the existing container
Object src[] = container;
// compute the number of values that will have to move, and from it, the new count - and
// therefore the new size needed to include all of the elements of the array. the cases are:
//
// 1) the gapIndex is in the area of the array already in use, some elements will have to be
// moved to make room for the new element, and the array might need to be expanded to
// accommodate that
//
// 2) the gapIndex is at the end of the array already in use, no elements will have to be
// moved to make room for it, but the array might need to be expanded to accommodate the
// new element
//
// 3) the gapIndex is outside of the range of the array already in use, no elements will
// have to be moved o make room for it, but the array might need to be expanded to
// accommodate the new element
int moveCount = count - gapIndex;
count = 1 + ((moveCount > 0) ? count : gapIndex);
// get the size of the array and resize it if necessary (copying the existing elements to
// the new array - note that this means a sparse insertion will result in null elements in
// the array
int size = container.length;
if (count > size) {
do {
// if the array is smaller than the cap then double its size, otherwise just add the block
size = (size > DOUBLING_CAP) ? (size + DOUBLING_CAP) : (size * 2);
}
while (count > size);
container = new Object[size];
System.arraycopy (src, 0, container, 0, Math.min (gapIndex, src.length));
}
// if needed, copy elements after the gapIndex
if (moveCount > 0) {
System.arraycopy (src, gapIndex, container, gapIndex + 1, moveCount);
}
}
/**
* Inserts the element at the given index of the underlying array store. The underlying store is
* shifted to make space for the new element. The shift might cause the underlying store to be
* resized if there is insufficient room.
*
* Note that null values for the element ARE stored, as the underlying store is not a sparse
* array.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @param object The element to store.
* @return The BagArray, so that operations can be chained together.
*/
public BagArray insert (int index, Object object) {
grow (index);
// note that arrays can store null objects, unlike bags
container[index] = objectify (object);
return this;
}
/**
* Adds the element at the end of the underlying array store. The underlying store might be
* resized if there is insufficient room.
*
* Note that null values for the element ARE stored, as the underlying store is not a sparse
* array.
*
* @param object The element to store.
* @return The BagArray, so that operations can be chained together.
*/
public BagArray add (Object object) {
return insert (count, object);
}
/**
* Replaces the element at the given index of the underlying array store. The underlying store
* is not shifted, and will not be resized.
*
* Note that null values for the element ARE stored, as the underlying store is not a sparse
* array.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @param object The element to store.
* @return The BagArray, so that operations can be chained together.
*/
public BagArray replace (int index, Object object) {
// note that arrays can store null objects, unlike bags
container[index] = objectify (object);
return this;
}
/**
* Removes the element at the given index of the underlying array store. The underlying store
* is shifted to cover the removed item. The underlying store will not be resized. Using invalid
* indices is ignored.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The BagArray, so that operations can be chained together.
*/
public BagArray remove (int index) {
if ((index >= 0) && (index < count)) {
int gapIndex = index + 1;
System.arraycopy (container, gapIndex, container, index, count - gapIndex);
--count;
}
return this;
}
private Object getObject (int index) {
return ((index >= 0) && (index < count)) ? container[index] : null;
}
/**
* Retrieve an indexed element and return it as a String.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a string, or null if the element is not found (or not a String).
*/
public String getString (int index) {
Object object = getObject (index);
try {
return (String) object;
} catch (ClassCastException exception) {
log.warn ("Cannot cast value type (" + object.getClass ().getName () + ") to String for index (" + index + ")");
}
return null;
}
/**
* Retrieve an indexed element and return it as a Boolean.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a Boolean, or null if the element is not found.
*/
public Boolean getBoolean (int index) {
String string = getString (index);
return (string != null) ? Boolean.parseBoolean (string) : null;
}
/**
* Retrieve an indexed element and return it as a Long.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a Long, or null if the element is not found.
*/
@SuppressWarnings ("WeakerAccess")
public Long getLong (int index) {
String string = getString (index);
return (string != null) ? Long.parseLong (string) : null;
}
/**
* Retrieve an indexed element and return it as an Integer.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as an Integer, or null if the element is not found.
*/
public Integer getInteger (int index) {
Long value = getLong (index);
return (value != null) ? value.intValue () : null;
}
/**
* Retrieve an indexed element and return it as a Double.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a Double, or null if the element is not found.
*/
public Double getDouble (int index) {
String string = getString (index);
return (string != null) ? Double.parseDouble (string) : null;
}
/**
* Retrieve an indexed element and return it as a Float.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a Float, or null if the element is not found.
*/
public Float getFloat (int index) {
Double value = getDouble (index);
return (value != null) ? value.floatValue () : null;
}
/**
* Retrieve an indexed element and return it as a BagObject.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a BagObject, or null if the element is not found.
*/
public BagObject getBagObject (int index) {
Object object = getObject (index);
try {
return (BagObject) object;
} catch (ClassCastException exception) {
log.warn ("Cannot cast value type (" + object.getClass ().getName () + ") to BagObject for index (" + index + ")");
}
return null;
}
/**
* Retrieve an indexed element and return it as a BagArray.
*
* @param index An integer value specifying the offset from the beginning of the array.
* @return The element as a BagArray, or null if the element is not found.
*/
public BagArray getBagArray (int index) {
Object object = getObject (index);
try {
return (BagArray) object;
} catch (ClassCastException exception) {
log.warn ("Cannot cast value type (" + object.getClass ().getName () + ") to BagArray for index (" + index + ")");
}
return null;
}
/**
* Returns the BagArray represented as JSON.
*
* @return A String containing the JSON representation of the underlying store.
*/
public String toJsonString () {
StringBuilder result = new StringBuilder ();
boolean first = true;
for (int i = 0; i < count; ++i) {
result.append (first ? "" : ",");
first = false;
String string = getJsonString (container[i]);
if (string != null) {
result.append (string);
}
}
return enclose (result.toString (), SQUARE_BRACKETS);
}
/**
* Returns the BagArray represented as XML.
*
* @return A String containing the XML representation of the underlying store.
*/
public String toXmlString (String name) {
StringBuilder result = new StringBuilder ();
for (int i = 0; i < count; ++i) {
String string = getXmlString (name, container[i]);
result.append (string);
}
return result.toString ();
}
/**
* Returns a BagArray extracted from a JSON representation in a string.
*
* @param input A String containing a JSON encoding of a BagArray.
* @return A new BagArray containing the elements encoded in the input.
*/
public static BagArray fromJsonString (String input) {
// parse the string out... it is assumed to be a well formed BagArray serialization
JsonParser parser = new JsonParser (input);
return parser.readBagArray ();
}
/**
* Returns a BagArray extracted from a JSON representation in an input stream.
*
* @param inputStream An input stream containing a JSON encoding of a BagArray.
* @return A new BagArray containing the elements encoded in the input.
* @throws IOException if the stream input can not be read
*/
public static BagArray fromStream (InputStream inputStream) throws IOException {
// parse the string out... it is assumed to be a well formed BagArray serialization
JsonParser parser = new JsonParser (inputStream);
return parser.readBagArray ();
}
/**
* Returns a BagArray extracted from a JSON representation in a file.
*
* @param file A file containing a JSON encoding of a BagArray.
* @return A new BagArray containing the elements encoded in the input.
* @throws IOException if the file input can not be read
*/
public static BagArray fromFile (File file) throws IOException {
// parse the string out... it is assumed to be a well formed BagArray serialization
JsonParser parser = new JsonParser (file);
return parser.readBagArray ();
}
}