/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.utils.common.channel.legacy;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import de.rcenvironment.core.utils.common.variables.legacy.TypedValue;
import de.rcenvironment.core.utils.common.variables.legacy.VariableType;
/**
* This class represents an n-dimensional array where each cell may contain its own data value and type.
*
* @author Arne Bachmann
*/
@Deprecated
public class VariantArray implements Serializable {
private static final String EXCEPTION_WRONGDIMENSIONS = "Wrong number of dimensions provided in VariantArray";
/**
* For serialization.
*/
private static final long serialVersionUID = -974534708839659363L;
/**
* Name of the array.
*/
private final String name;
/**
* Sizes of dimensions.
*/
private final int[] dimensions;
/**
* Dimension multipliers for index determination.
*/
private final int[] multipliers;
/**
* All dimensions in one array.
*/
private TypedValue[] array;
/**
* Marker for compressed state.
*/
private boolean compressed = false;
/**
* Constructor where given values are delivered.
*
* @param theName name of VariantArray
* @param values values which are delivered
* @param dimensionSizes Array of dimensions or variable number of arguments for dimension size (e.g. 3,4,5 = 3x4x5 3D-matrix).
*/
private VariantArray(final String theName, final TypedValue[] values, final int... dimensionSizes) {
if ((dimensionSizes == null) || (dimensionSizes.length <= 0)) {
throw new IllegalArgumentException(EXCEPTION_WRONGDIMENSIONS);
}
name = theName;
dimensions = dimensionSizes;
multipliers = calculateMultipliers(dimensions);
array = values;
}
/**
* The default constructor.
*
* @param dimensions Array of dimensions or variable number of arguments for dimension size (e.g. 3,4,5 = 3x4x5 3D-matrix).
*/
public VariantArray(final String theName, final int... dimensionSizes) {
if ((dimensionSizes == null) || (dimensionSizes.length <= 0)) {
throw new IllegalArgumentException(EXCEPTION_WRONGDIMENSIONS);
}
name = theName;
dimensions = dimensionSizes;
multipliers = calculateMultipliers(dimensions);
array = new TypedValue[getSize()];
setDefaultType(VariableType.Empty); // initialize all cells
}
/**
* Copy constructor.
*
* @param from The object to copy values from
*/
public VariantArray(final String theName, final VariantArray from) {
if (from == null) {
throw new IllegalArgumentException("Cannot use copy constructor with null argument");
}
name = theName;
dimensions = new int[from.dimensions.length];
multipliers = new int[from.multipliers.length];
array = new TypedValue[from.array.length];
System.arraycopy(from.dimensions, 0, dimensions, 0, from.dimensions.length);
System.arraycopy(from.multipliers, 0, multipliers, 0, from.multipliers.length);
System.arraycopy(from.array, 0, array, 0, from.array.length);
}
/**
* Set a cell value.
*
* @param value The value to set
* @param index The n dimension indexes, starting with 0
* @return this
*/
public VariantArray setValue(final TypedValue value, final int... index) {
array[index(index)] = new TypedValue(value);
return this;
}
/**
* Add VariantArray to existing VariantArray.
* Source array needs the "root" dimension which will be extended with the parameter VariantArray.
* Example:
* If arrayToAdd VariantArray va = [3,4,5]
* and main VariantArray = [1,3,4,5]
* then returning VariantArray = [2,3,4,5].
*
* @param main VariantArray
* @param arrayToAdd Variant Array parameter
* @return this Variant Array as copy
*/
public static VariantArray addValuesToVariantArray(final VariantArray main, final VariantArray arrayToAdd) {
if (main.dimensions.length - 1 != arrayToAdd.dimensions.length) {
throw new IllegalArgumentException(EXCEPTION_WRONGDIMENSIONS);
}
int counter = 0;
for (int i: arrayToAdd.dimensions) {
if (main.dimensions[counter + 1] != i) {
throw new IllegalArgumentException("Wrong structure of dimensions provided in VariantArray");
}
counter++;
}
TypedValue[] newArray = new TypedValue[main.array.length + arrayToAdd.getSize()];
System.arraycopy(main.array, 0, newArray, 0, main.array.length);
System.arraycopy(arrayToAdd.array, 0, newArray, main.array.length, arrayToAdd.array.length);
int[] newDimensions = main.dimensions;
newDimensions[0] = newDimensions[0] + 1;
return new VariantArray(main.name, newArray, newDimensions);
}
/**
* Set a cell value.
*
* @param value The value to set
* @param index The n dimension indexes, starting with 0
* @return this
* throws an illegal argument exception if type differs from previous type
*/
public VariantArray setValue(final Serializable value, final int... index) {
array[index(index)] = new TypedValue(value);
return this;
}
/**
* Get a cell value.
*
* @param index The n dimension indexes, starting with 0.
* @return The value
*/
public TypedValue getValue(final int... index) {
return array[index(index)];
}
/**
* Initialize basic type and value for all cells, to avoid illegal values when forgetting to set singular cells.
*
* @param value The value to set, of arbitrary type.
* @return this
*/
public VariantArray setDefaultValue(final Serializable value) {
for (int i = 0; i < array.length; i ++) {
array[i] = new TypedValue(value);
}
return this;
}
/**
* Define the default type for all cells, to avoid illegal values when forgetting to set singular cells.
*
* @param type The type to assume for each cell by default
* @return this
*/
public VariantArray setDefaultType(final VariableType type) {
if (type == VariableType.Empty) {
for (int i = 0; i < array.length; i ++) {
array[i] = TypedValue.EMPTY;
}
} else {
for (int i = 0; i < array.length; i ++) {
array[i] = new TypedValue(type);
}
}
return this;
}
/**
* Return the array's name.
*
* @return The name
*/
public String getName() {
return name;
}
/**
* Convenience getter.
*
* @return The dimensions
*/
public int[] getDimensions() {
return dimensions;
}
/**
* Return the number of elements in the array.
*
* @return The number of elements in the array
*/
public int getSize() {
if (compressed) {
return array.length;
}
return multipliers[0]; // all dimension sizes multiplied = number of cells
}
/**
* Return true if all values have been set and there is no empty value (set or unset) in the array.
*
* @return True if all values have been set
*/
public boolean isMatrixComplete() {
for (final TypedValue value: array) {
if ((value == null) || (value == TypedValue.EMPTY) || (value.getType() == VariableType.Empty)) {
return false;
}
}
return true;
}
/**
* Calculate index position of flat index.
*
* @param index The flat index of the array
* @return The dimensioned index from least- to most-significant
*/
public int[] getIndex(int index) {
final int max = dimensions.length - 1;
final int[] position = new int[max + 1];
position[max] = index % dimensions[max];
for (int i = max - 1; i >= 0; i --) {
position[i] = (index % multipliers[i]) / multipliers[i + 1];
}
return position;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("VariantArray ");
if (name != null) {
sb.append(name);
}
sb.append("[");
for (int d = 0; d < dimensions.length - 1; d ++) {
sb.append(Integer.toString(dimensions[d]));
sb.append(",");
}
sb.append(Integer.toString(dimensions[dimensions.length - 1]));
sb.append("]");
return sb.toString();
}
/**
* Return dimension multipliers.
*
* @param dimensions All dimension sizes
* @return The dimension multipliers/dividers
*/
protected static int[] calculateMultipliers(final int[] dimensions) {
final int[] ret = new int[dimensions.length];
int product = 1;
for (int d = dimensions.length - 1; d >= 0; d --) {
product *= dimensions[d];
ret[d] = product;
}
return ret;
}
/**
* Get the index of an element.
*
* @param indexes The index of each dimensions
* @return The index, 0-based
*/
protected int index(final int[] indexes) {
int ret = 0;
for (int i = 0; i < indexes.length - 1; i ++) {
ret += multipliers[i + 1] * indexes[i];
}
ret += indexes[indexes.length - 1]; // dimension 0 has always multiplier 1
return ret;
}
/**
* Remove all empty hyper-slices of the given dimension.
* This means fixing the dimension and checking all other values for emptyness.
* If empty, remove the fixed index and decrease it (increase it if ascending).
* If everything is pruned, an n-dimensional array with exactly one empty cell is returned.
*
* @param dimension The fixed dimensions index (0 = highest, n-1 = lowest)
* @param lowest True if checking 0..n-1 ("right-to-left")
* @param highest True if checking n-1..0 ("left-to-right")
* @return A new pruned array
*/
public VariantArray pruneDimension(final int dimension, final boolean lowest, final boolean highest) {
if (!lowest && !highest) {
return this; // nothing to do
}
int count;
int toPruneLowest = 0; // found hyper-slices to remove
int toPruneHighest = 0; // found hyper-slices to remove
int hypersliceElements = multipliers[0] / dimensions[dimension];
if (lowest) { // lowest to highest pruning
for (int i = 0; i < dimensions[dimension]; i ++) { // starting from lowest index value in fixed dimension
count = 0; // counter of empty cell in the hyperslice
for (int cell = 0; cell < multipliers[0]; cell ++) { // for all cells
final int[] tmp = getIndex(cell);
if ((array[cell].getType() == VariableType.Empty) && (tmp[dimension] == i)) { // ignore fixed
count ++;
}
}
if (count == hypersliceElements) { // hyper-slice is completely empty: mark for pruning
toPruneLowest ++;
continue;
} else {
break;
}
}
}
if (highest && (toPruneLowest < dimensions[dimension])) { // only check ohif we don't already know that everything is empty
for (int i = dimensions[dimension] - 1; i >= 0; i --) {
count = 0; // counter of empty cells
for (int cell = 0; cell < multipliers[0]; cell ++) { // for all cells
final int[] tmp = getIndex(cell);
if ((array[cell].getType() == VariableType.Empty) && (tmp[dimension] == i)) { // only count our fixed hyperslice
count ++;
}
}
if (count == hypersliceElements) { // hyper-slice is completely empty: mark for pruning
toPruneHighest ++;
continue;
} else {
break;
}
}
}
// prepare new array
if ((toPruneLowest + toPruneHighest) >= dimensions[dimension]) { // nothing left after pruning
toPruneLowest = 0;
toPruneHighest = dimensions[dimension];
}
int reduce = toPruneLowest + toPruneHighest;
if (reduce == 0) {
return new VariantArray(name, this);
}
final int[] newDimensions = new int[dimensions.length];
System.arraycopy(dimensions, 0, newDimensions, 0, dimensions.length);
newDimensions[dimension] -= reduce; // reduce dimensional size
toPruneHighest = dimensions[dimension] - toPruneHighest;
final VariantArray newArray = new VariantArray(name, newDimensions);
int newCell = 0;
for (int cell = 0; cell < multipliers[0]; cell ++) { // for every cell in the old array
final int[] tmp = getIndex(cell);
final int i = tmp[dimension];
if ((i >= toPruneLowest) && (i < toPruneHighest)) {
newArray.array[newCell ++] = array[cell]; // only copy non-pruned entries
}
}
return newArray;
}
/**
* Prune highest dimension if empty.
*
* @return VariantArray with no empty entries on lowest dimension.
*/
public VariantArray pruneDimensionZero() {
VariantArray va;
List<VariantArray> list = unitizeHighestDimension();
for (int i = (list.size() - 1); i >= 0; i--) {
VariantArray entry = list.get(i);
if (entry.isEmpty()) {
list.remove(entry);
} else {
break;
}
}
// Rebuild VariantArray
// new dimension description
int[] finalDimensions = new int[1 + list.get(0).getDimensions().length];
System.arraycopy(list.get(0).getDimensions(), 0, finalDimensions, 1, finalDimensions.length - 1);
va = new VariantArray(list.get(0).getName(), finalDimensions);
// Copy values to...
for (VariantArray slice: list) {
va = VariantArray.addValuesToVariantArray(va, slice);
}
return va;
}
/**
* Checks if content is empty.
*
* @return true if empty
*/
public boolean isEmpty() {
boolean isEmptyContent = false;
if (array.length == 0) {
isEmptyContent = true;
}
for (TypedValue val: array) {
if (val.getStringValue() == null || val.getStringValue().equals("") || val.getType() == VariableType.Empty) {
isEmptyContent = true;
} else {
isEmptyContent = false;
break;
}
}
return isEmptyContent;
}
/**
* Compresses the memory usage.
* Needs to uncompress before further usage.
* @return self
*/
public VariantArray compress() {
final List<TypedValue> list = new ArrayList<TypedValue>();
TypedValue last = null;
int count = 0;
for (final TypedValue tv: array) {
if (last == null) {
last = tv;
count = 1;
continue;
}
if (last.getType() == VariableType.Empty) {
if (tv.getType() == VariableType.Empty) {
count ++;
} else {
writeOutRle(count, last, list);
count = 1;
last = tv;
}
} else { // last was not empty
if (tv.getType() == VariableType.Empty) {
writeOutRle(count, last, list);
count = 1;
last = tv;
} else { // need to compare
if ((tv.getType() == last.getType()) && (tv.getValue().equals(last.getValue()))) {
count ++;
} else {
writeOutRle(count, last, list);
count = 1;
last = tv;
}
}
}
}
writeOutRle(count, /* end */ last, list);
compressed = true;
array = list.toArray(new TypedValue[0]); // replace data
return this;
}
/**
* Repetitive work.
*
* @param count The count to store
* @param value The value to write
* @param list The list to add to
*/
private static void writeOutRle(final int count, final TypedValue value, final List<TypedValue> list) {
if (count > 1) {
list.add(new TypedValue(VariableType.RLE, Integer.toString(count)));
}
list.add(value);
}
/**
* Uncompressed the memory usage.
* Necessary to use other methods.
* @return self
*/
public VariantArray uncompress() {
if (!compressed) {
return this;
}
final TypedValue[] old = array;
array = new TypedValue[multipliers[0]];
int source = 0;
int target = 0;
while (source < old.length) {
final TypedValue tv = old[source ++];
if (tv.getType() == VariableType.RLE) {
final TypedValue rleValue = old[source ++];
final int count = Integer.valueOf((String) tv.getValue());
for (int c = 0; c < count; c ++) {
if (rleValue.getType() == VariableType.Empty) {
array[target ++] = TypedValue.EMPTY;
} else {
array[target ++] = new TypedValue(rleValue); // copy constructor
}
}
} else {
array[target ++] = tv;
}
}
compressed = false;
return this;
}
/**
* For test classes.
*
* @return The array
*/
protected TypedValue[] getInternalArray() {
return array;
}
/**
* For external checking.
*
* @return true if compressed
*/
public boolean isCompressed() {
return compressed;
}
/**
* Unitize highest dimension of a VariantArray.
* Split every set of highest dimension array into a piece.
*
* @return list of highest dimension pieces
*/
public List<VariantArray> unitizeHighestDimension() {
List<VariantArray> list = new ArrayList<VariantArray>();
if (dimensions.length == 1) {
// On 1-dimension
int sizeDimension = dimensions[0];
for (int dimensionrunner = 0; dimensionrunner < sizeDimension; dimensionrunner++) {
TypedValue[] newValueArray = new TypedValue[1];
newValueArray[0] = getValue(dimensionrunner);
VariantArray va = new VariantArray(name, newValueArray, 1);
list.add(va);
}
} else {
int sizeOfHighestDimension = dimensions[0];
int pointerToEmptyEntry = 0;
for (int globalrunner = 1; globalrunner <= sizeOfHighestDimension; globalrunner++) {
TypedValue[] newValueArray = new TypedValue[getSize() / sizeOfHighestDimension];
for (int index = 0; index < newValueArray.length; index++) {
newValueArray[index] = getValue(getIndex(pointerToEmptyEntry));
pointerToEmptyEntry++;
}
//Create new VariantArray
int[] d = cutFirstEntry(dimensions);
VariantArray va = new VariantArray(name, newValueArray, d);
list.add(va);
}
}
return list;
}
/**
* Cut first entry from array.
*
* @param array array to cut
* @return new array without first entry
*/
private static int[] cutFirstEntry(int[] array) {
int[] result = new int[array.length - 1];
for (int i = 0; i < result.length; i++) {
result[i] = array[i + 1];
}
/*
* Special handling for 1-dimensional arrays.
*
* 1-dimension should not be described as [2], but as [2, 1].
*/
if (result.length == 1) {
int value = result[0];
result = new int[2];
result[1] = 1;
result[0] = value;
}
return result;
}
}