All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.brettonw.bag.BagArray Maven / Gradle / Ivy

package com.brettonw.bag;

import com.brettonw.bag.expr.BooleanExpr;
import com.brettonw.bag.json.FormatReaderJson;
import com.brettonw.bag.json.FormatWriterJson;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.util.Arrays;

/**
 * A collection of text-based values stored in a zero-based indexed array.
 * 

* Note: the BagArray class, write a memory allocation standpoint, is not designed to work * efficiently 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 Bag implements Selectable { 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 () { init (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) { init (size); } /** * Create a new BagArray as deep copy of another BagArray */ public BagArray (BagArray bagArray) throws IOException, ReadException { Reader reader = new StringReader (bagArray.toString (FormatWriterJson.JSON_FORMAT)); init (bagArray.getCount (), FormatReaderJson.JSON_FORMAT, null, reader); } /** * Create a new BagArray initialized from a formatted string * @throws ReadException if the parser fails and the array is left in an unusable state */ public BagArray (String formattedString) throws IOException, ReadException { init (START_SIZE, null, null, new StringReader (formattedString)); } /** * Create a new BagArray initialized from a formatted string * @throws ReadException if the parser fails and the array is left in an unusable state */ public BagArray (String format, String formattedString) throws IOException, ReadException { init (START_SIZE, format, null, new StringReader (formattedString)); } /** * Create a new BagArray initialized from a formatted string read out of an inputStream * @throws ReadException if the parser fails and the array is left in an unusable state */ public BagArray (InputStream formattedInputStream) throws IOException, ReadException { init (START_SIZE, null, null, new InputStreamReader (formattedInputStream)); } /** * Create a new BagArray initialized from a formatted string read out of an inputStream * @throws ReadException if the parser fails and the array is left in an unusable state */ public BagArray (String format, InputStream formattedInputStream) throws IOException, ReadException { init (START_SIZE, format, null, new InputStreamReader (formattedInputStream)); } /** * Create a new BagArray initialized from a formatted string read out of a file * @throws ReadException if the parser fails and the array is left in an unusable state */ public BagArray (File formattedFile) throws IOException, ReadException { init (START_SIZE, null, formattedFile.getName (), new FileReader (formattedFile)); } /** * Create a new BagArray initialized from a formatted string read out of a file * @throws ReadException if the parser fails and the array is left in an unusable state */ public BagArray (String format, File formattedFile) throws IOException, ReadException { init (START_SIZE, format, formattedFile.getName (), new FileReader (formattedFile)); } private void init (int containerSize) { count = 0; container = new Object[Math.max (containerSize, 1)]; } private void init (int containerSize, String format, String name, Reader reader) throws IOException, ReadException { init (containerSize); if (FormatReader.read (this, format, name, reader) == null) { throw new ReadException (); } } /** * Return the number of elements stored in the BagArray. * * @return the count of elements in the underlying store. This is distinct write 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 write 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 write 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 write 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 write 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; } public Object getObject (int index) { return ((index >= 0) && (index < count)) ? container[index] : null; } private int keyToIndex (String key) { switch (key) { case "#first": return 0; case "#last": return count - 1; //case "#add": return count; default: return Integer.parseInt (key); } } /** * Return an object stored at the requested key value. The key may be a simple number, or a * special keyword indicating the #first or #last element in the array, #add for putting at the * end of the array, or it may be a path (with keys separated by "/") to create a hierarchical * "bag-of-bags" that is indexed recursively. *

* * @param key A string value used to index the element, using "/" as separators, for example: * "12/com/brettonw" or "#last/completed" * @return The indexed element (if found), or null */ @Override public Object getObject (String key) { // separate the key into path components, the "local" key value is the first component, so // use that to conduct the search. We are only interested in values that indicate the search // found the requested key String[] path = Key.split (key); int index = keyToIndex (path[0]); if ((index >= 0) && (index < count)) { // grab the found element... if the path was only one element long, this is the element // we were looking for, otherwise recur on the found element as another BagObject Object found = container[index]; return (path.length == 1) ? found : ((Bag) found).getObject (path[1]); } return null; } /** * Retrieve an indexed element and return it as a String. * * @param index An integer value specifying the offset write 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 write 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 write 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 write 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 write 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 write 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 write 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 write 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; } @Override public String toString (String format) { return FormatWriter.write (this, format); } @Override public BagArray select (BagArray select) { if ((select != null) && (select.getCount () > 0)) { BagArray bagArray = new BagArray (); for (int i = 0, end = select.getCount (); i < end; ++i) { Integer index = select.getInteger (i); if (index != null) { Object object = getObject (index); bagArray.add (object); } } return bagArray; } return this; } private int compare (Double left, Double right) { return (left < right) ? -1 : (left > right) ? 1 : 0; } /** * * @param sortKeys array of sort keys like [ { "key":"key1" }, { "key":"key2", "type":"alphabetic"}, { "key":"key2", "type":"numeric", "order":"ascending"} ] * @return */ public BagArray sort (BagArray sortKeys) { // if no sortKey is provided, set up a default sortKey with null key and default type and // order, so we can proceed if (sortKeys == null) { sortKeys = new BagArray (); } if (sortKeys.getCount () == 0) { sortKeys.add (new BagObject ()); } // create the sort keys as a native type since we'll be referencing it a lot SortKey[] keys = new SortKey[sortKeys.getCount ()]; for (int i = 0, end = sortKeys.getCount (); i < end; ++i) { keys[i] = new SortKey (sortKeys.getBagObject (i)); } // if there is no key if (keys[0].key == null) { // we'll treat the array as strings or bare value, and just sort it switch (keys[0].type) { case ALPHABETIC: switch (keys[0].order) { case ASCENDING: Arrays.sort (container, 0, count, (Object a, Object b) -> { return ((String) a).compareTo ((String) b); } ); break; case DESCENDING: Arrays.sort (container, 0, count, (Object a, Object b) -> { return ((String) b).compareTo ((String) a); } ); break; } break; case NUMERIC: switch (keys[0].order) { case ASCENDING: Arrays.sort (container, 0, count, (Object a, Object b) -> { return compare (new Double ((String) a), new Double ((String) b)); } ); break; case DESCENDING: Arrays.sort (container, 0, count, (Object a, Object b) -> { return compare (new Double ((String) b), new Double ((String) a)); } ); break; } break; } } else { // we'll sort using the keys hierarchically... Arrays.sort (container, 0, count, (Object a, Object b) -> { for (int i = 0, end = keys.length; i < end; ++i) { Object objectA = (a != null) ? ((Bag) a).getObject (keys[i].key) : null; Object objectB = (b != null) ? ((Bag) b).getObject (keys[i].key) : null; int cmp = 0; switch (keys[i].type) { case ALPHABETIC: switch (keys[i].order) { case ASCENDING: cmp = ((String) objectA).compareTo ((String) objectB); break; case DESCENDING: cmp = ((String) objectB).compareTo ((String) objectA); break; } break; case NUMERIC: switch (keys[i].order) { case ASCENDING: cmp = compare (new Double ((String) objectA), new Double ((String) objectB)); break; case DESCENDING: cmp = compare (new Double ((String) objectB), new Double ((String) objectA)); break; } break; } if (cmp != 0) { return cmp; } } return 0; } ); } return this; } /** * * @param match a BooleanExpr describing the match criteria * @param selectKeys a BagArray of the key names to extract * @return */ public BagArray query (BooleanExpr match, BagArray selectKeys) { // create the destination BagArray bagArray = new BagArray (); // loop over all of the objects for (Object object : container) { if (object instanceof Bag) { // try to match the 'match' clause Bag bag = (Bag) object; boolean matches = (match == null) || bag.match (match); if (matches) { // select the desired parts object = ((Selectable) bag).select (selectKeys); bagArray.add (object); } } else if (object instanceof String){ // XXX TODO // it's a string, 'match' needs to be { field:"*",... } or not have field } else { // skip it, don't know what it is } } return bagArray; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy