/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Markus Kraetzig
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/
package com.jstatcom.model;
import javolution37.javolution.xml.XmlElement;
import javolution37.javolution.xml.XmlFormat;
import com.jstatcom.util.FArg;
import com.jstatcom.util.UMatrix;
import com.jstatcom.util.UString;
import com.jstatcom.util.UStringArray;
/**
* A wrapper for string arrays that conforms to the JSCData
* interface.
*
* A JSCSArray is empty, if the stored array is null
* or has either zero rows or columns. All rows of array values must have the
* same length.
*
* All array arguments are copied before they are set as values. The original
* arrays are untouched. In a similar way all get methods return
* either a copy of a mutable object (like String[][]) or a
* reference to an immutable object.
*
* Whenever the value of this data object changes, registered
* JSCDataListeners are notified via a JSCDataEvent.
* If not otherwise stated, the event objects always contain the values before
* the change and after the change.
*
* This class is thread-safe. All access to mutable data is synchronized. All
* events are fired outside the synchronized blocks.
*
* @author Markus Kraetzig
*/
public final class JSCSArray extends AbstractJSCData {
private String formatString = "%-12s ";
private final String name;
private String[][] value = new String[0][0];
private JSCDataEvent clearEvent = null;
private JSCDataEvent notEmptyEvent = null;
/**
* XML format field for (de)serialization.
*/
public static final XmlFormat JSCSArray_XML = new XmlFormat(
JSCSArray.class) {
public void format(JSCSArray data, XmlElement xml) {
xml.setAttribute("name", data.name);
synchronized (data) {
xml.setAttribute("rows", data.rows());
for (int i = 0; i < data.rows(); i++)
xml.add(new Row(data.value[i], i));
}
}
public JSCSArray parse(XmlElement xml) {
String nam = xml.getAttribute("name") + "";
String rowString = xml.getAttribute("rows") + "";
int rows = UString.parseToNumber(rowString).intValue();
if (rows == 0) {
JSCSArray array = new JSCSArray(nam);
return array;
}
String[][] val = new String[rows][];
for (int i = 0; i < rows; i++)
val[i] = ((Row) xml.getNext()).value;
JSCSArray array = new JSCSArray(nam, val);
return array;
}
};
/**
* Inner class for storing rows of this array as XML elements.
*/
public static class Row {
// Fields are directly accessed by enclosing class.
final String[] value;
final int index;
static final String element = "_";
/**
* XML format field for (de)serialization.
*/
public static final XmlFormat Row_XML = new XmlFormat(
Row.class) {
public void format(Row data, XmlElement xml) {
xml.setAttribute("i", data.index);
xml.setAttribute("cols", data.value.length);
for (int i = 0; i < data.value.length; i++)
if (data.value[i] != null)
xml.setAttribute(element + i, data.value[i]);
}
public Row parse(XmlElement xml) {
String colString = xml.getAttribute("cols") + "";
String indexString = xml.getAttribute("i") + "";
int cols = UString.parseToNumber(colString).intValue();
int index = UString.parseToNumber(indexString).intValue();
String[] value = new String[cols];
for (int i = 0; i < cols; i++) {
Object o = xml.getAttribute(element + i, (String) null);
if (o != null)
value[i] = o.toString();
}
return new Row(value, index);
}
};
public Row(String[] value, int i) {
this.value = value.clone();
this.index = i;
}
};
/**
* Creates an empty JSCSArray.
*
* @param name
* the identifier of that object
* @throws IllegalArgumentException
* if name is not valid or
* if (name == null)
*
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public JSCSArray(String name) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
this.value = new String[0][0];
}
/**
* Creates a JSCSArray from value. The
* elements of value are copied before.
*
* @param name
* the identifier of that object
* @param value
* the String[][] to be copied and used as value
* @throws IllegalArgumentException
* if name is not valid
* or if rows of value have different lengths or
* if (name == null)
*
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public JSCSArray(String name, String[][] value) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
if (value != null && value.length > 0)
UStringArray.checkRowLengths(value);
this.value = UStringArray.cloneStringArray(value);
}
/**
* Copy-Constructor.
*
* @param sarray
* the string array to copy
* @throws IllegalArgumentException
* if (sarray == null)
*
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public JSCSArray(JSCSArray sarray) {
super();
if (sarray == null)
throw new IllegalArgumentException("Argument was null.");
this.name = sarray.name();
this.value = sarray.stringArray();
}
/**
* Creates a JSCSArray from value. The
* elements of value are copied before.
*
* @param name
* the identifier of that object
* @param value
* the String[] to be copied and used as value
* @throws IllegalArgumentException
* if name is not valid
* or if (name == null)
*
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public JSCSArray(String name, String... value) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
JSCConstants.checkNameThrowEx(name);
this.name = name;
this.value = UStringArray.toStringMatrix(value);
}
/**
* Appends newCols to the right end of the value array. Fires
* a JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED if
* newCols is not empty
* JSCDataEventTypes.EMPTY_STATE if newCols
* is not empty and this was empty before
*
* It is possible to append columns to an empty data object. This would be
* similar to setting new values. If newCols is empty,
* nothing happens.
*
* @param newCols
* the JSCSArray to be appended
* @throws IllegalArgumentException
* if (newCols == null) or
* if (rows() > 0 && newCols.rows() > 0 && rows() != newCols.rows())
*/
public void appendCols(JSCSArray newCols) {
if (newCols == null)
throw new IllegalArgumentException("Argument was null for "
+ toString() + ".");
if (newCols.isEmpty())
// Check before obtaining lock for this to quick return.
return;
boolean wasEmpty = false;
boolean isEmpty = false;
String[][] newVal = null;
String[][] oldValue = null;
String[][] toAppend = null;
synchronized (this) {
synchronized (newCols) {
if (newCols.isEmpty())
// Check again because might have changed in the meantime.
return;
int rows = rows();
int otherRows = newCols.rows();
if (rows > 0 && rows != otherRows)
throw new IllegalArgumentException(
"Number of rows do not match between\n"
+ toString() + " and\n"
+ newCols.toString() + ".");
toAppend = newCols.stringArray();
}
if (isEmpty()) { // Special case if this was empty before.
wasEmpty = true;
oldValue = value;
value = toAppend; // has been copied already.
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because event is fired
// outside synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UStringArray.cloneStringArray(toAppend);
} else {
wasEmpty = false;
oldValue = value;
value = UStringArray.appendStringCols(value, toAppend);
// has been copied already.
isEmpty = isEmpty();
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UStringArray.cloneStringArray(value);
}
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. No comparison
// needed here.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newVal));
}
// EMPTY_STATE event.
if (wasEmpty && !isEmpty) {
if (notEmptyEvent == null)
notEmptyEvent = JSCDataEvent.valueOfEmptyState(this, false);
getEventSupport().dispatchEvent(notEmptyEvent);
}
}
/**
* Appends newRows to the bottom the value array. Fires a
* JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED if
* newRows is not empty
* JSCDataEventTypes.EMPTY_STATE if newRows
* is not empty and this was empty before
*
* It is possible to append rows to an empty data object. This would be
* similar to setting new values. If newRows is empty,
* nothing happens.
*
* @param newRows
* the JSCSArray to be appended
* @throws IllegalArgumentException
* if (newRows == null) or
* if (cols() > 0 && newRows.cols() > 0 && cols() != newRows.cols())
*/
public void appendRows(JSCSArray newRows) {
if (newRows == null)
throw new IllegalArgumentException("Argument was null for "
+ toString() + ".");
if (newRows.isEmpty())
// Check before obtaining lock for this to quick return.
return;
boolean wasEmpty = false;
boolean isEmpty = false;
String[][] newVal = null;
String[][] oldValue = null;
String[][] toAppend = null;
synchronized (this) {
synchronized (newRows) {
if (newRows.isEmpty())
// Check again because might have changed in the meantime.
return;
int cols = cols();
int otherCols = newRows.cols();
if (cols > 0 && cols != otherCols)
throw new IllegalArgumentException(
"Number of cols do not match between\n"
+ toString() + " and\n"
+ newRows.toString() + ".");
toAppend = newRows.stringArray();
}
if (isEmpty()) { // Special case if this was empty before.
wasEmpty = true;
oldValue = value;
value = toAppend; // has been copied already.
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because event is fired
// outside synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UStringArray.cloneStringArray(toAppend);
} else {
wasEmpty = false;
oldValue = value;
value = UStringArray.appendStringRows(value, toAppend);
// has been copied already.
isEmpty = isEmpty();
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UStringArray.cloneStringArray(value);
}
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. No comparison
// needed here.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newVal));
}
// EMPTY_STATE event.
if (wasEmpty && !isEmpty) {
if (notEmptyEvent == null)
notEmptyEvent = JSCDataEvent.valueOfEmptyState(this, false);
getEventSupport().dispatchEvent(notEmptyEvent);
}
}
/**
* Checks whether the indices are valid, throws exception if not.
*
* @param i
* the row index
* @param j
* the column index
* @throws IllegalArgumentException
* if indices out of range
*/
private void checkIndices(int i, int j) {
if (i >= rows() || j >= cols() || i < 0 || j < 0)
throw new IllegalArgumentException("[" + i + "," + j
+ "] is not a valid index for " + toString() + ".");
}
/**
* Clears this data object. Fires:
*
* JSCDataEventTypes.EMPTY_STATE event
* JSCDataEventTypes.VALUE_CHANGED event
*
* if it was not empty before. After calling this method
* isEmpty returns true.
*/
public void clear() {
String[][] oldValue;
synchronized (this) {
if (isEmpty()) // Nothing changes.
return;
oldValue = value;
value = new String[0][0];
}
// Dispatch event outside synchronized block.
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
if (eventSupport == null)
return;
getEventSupport().dispatchEvent(clearEvent);
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, new String[0][0]));
}
/**
* Gets the number of columns in the stored array.
*
* @return the number of columns or 0 if (isEmpty())
*
*/
public synchronized int cols() {
if (value == null || value.length == 0)
return 0;
return value[0].length;
}
/**
* @see com.jstatcom.model.JSCData
*/
public synchronized JSCSArray copy() {
return new JSCSArray(name, value);
}
/**
* Deletes column i from the value array. Fires a
* JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED in any case
* JSCDataEventTypes.EMPTY_STATE if this
* is emtpy afterwards
*
*
* @param i
* the index of the column to remove
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* if column index is invalid
*/
public void delCol(int i) {
String[][] oldValue = null;
boolean isEmpty = false;
String[][] newVal = null;
synchronized (this) {
if (isEmpty())
throw new IllegalStateException("Cannot delete column " + i
+ " from empty object " + toString() + ".");
if (i < 0 || cols() <= i)
throw new IllegalArgumentException("Bad column index " + i
+ " for " + toString() + ".");
oldValue = value;
value = UStringArray.delCol(value, i);
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because event is fired outside
// synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UStringArray.cloneStringArray(value);
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. No comparison
// needed here.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newVal));
}
// EMPTY_STATE event.
if (isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Deletes all columns of this that have a corresponding
* nonzero element in index. If index
* contains only zeros, nothing happens. If index selects all
* columns, this is cleared.
*
* This method fires a JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED if some column was
* deleted
* JSCDataEventTypes.EMPTY_STATE if this
* is emtpy afterwards
*
*
* @param index
* the cols() x 1 int array, nonzero entries
* select columns
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* if (index.length != cols()) or
* if (index == null)
*/
public void delColsIf(int[] index) {
String[][] oldValue = null;
boolean isEmpty = false;
String[][] newVal = null;
boolean changed = UMatrix.getNonzeroIntCount(index) > 0;
synchronized (this) {
if (isEmpty())
throw new IllegalStateException(
"Cannot delete columns from empty object " + toString()
+ ".");
if (index.length != cols())
throw new IllegalArgumentException(
"Bad column selection index (" + index.length + "x"
+ "1) for " + toString() + ".");
oldValue = value;
value = UStringArray.delif(value, index);
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because event is fired outside
// synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0
&& changed)
newVal = UStringArray.cloneStringArray(value);
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. No comparison
// needed here.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0
&& changed) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newVal));
}
// EMPTY_STATE event.
if (isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Deletes row i from the value array. Fires a
* JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED in any case
* JSCDataEventTypes.EMPTY_STATE if this
* is emtpy afterwards
*
*
* @param i
* the index of the row to remove
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* if row index is invalid
*/
public void delRow(int i) {
String[][] oldValue = null;
boolean isEmpty = false;
String[][] newVal = null;
synchronized (this) {
if (isEmpty())
throw new IllegalStateException("Cannot delete row " + i
+ " from empty object " + toString() + ".");
if (i < 0 || rows() <= i)
throw new IllegalArgumentException("Bad row index " + i
+ " for " + toString() + ".");
oldValue = value;
value = UStringArray.delRow(value, i);
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because event is fired outside
// synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UStringArray.cloneStringArray(value);
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. No comparison
// needed here.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newVal));
}
// EMPTY_STATE event.
if (isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Deletes all rows of this that have a corresponding nonzero
* element in index. If index contains only
* zeros, nothing happens. If index selects all rows,
* this is cleared.
*
* This method fires a JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED if some row was
* deleted
* JSCDataEventTypes.EMPTY_STATE if this
* is emtpy afterwards
*
*
* @param index
* the rows() x 1 int array, nonzero entries
* select rows
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* if (index.length != rows()) or
* if (index == null)
*/
public void delRowsIf(int[] index) {
String[][] oldValue = null;
boolean isEmpty = false;
String[][] newVal = null;
boolean changed = UMatrix.getNonzeroIntCount(index) > 0;
synchronized (this) {
if (isEmpty())
throw new IllegalStateException(
"Cannot delete rows from empty object " + toString()
+ ".");
if (index.length != rows())
throw new IllegalArgumentException("Bad row selection index ("
+ index.length + "x" + "1) for " + toString() + ".");
oldValue = value;
value = UStringArray.delRowsIf(value, index);
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because event is fired outside
// synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0
&& changed)
newVal = UStringArray.cloneStringArray(value);
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. No comparison
// needed here.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0
&& changed) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newVal));
}
// EMPTY_STATE event.
if (isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Gets a string representing the String[][] stored.
*
* @return "{}" if isEmpty == true, otherwise a formatted
* string with all elements in table form, the format parameters can
* be changed via setFormatString
*/
public synchronized String display() {
if (isEmpty())
return "{}";
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < rows(); i++) {
for (int j = 0; j < cols(); j++)
buffer.append(FArg.sprintf(formatString, new FArg(value[i][j]
+ "")));
buffer.append("\r\n");
}
return buffer.toString();
}
/**
* Gets whether this is empty. This method returns
* true if one of the following conditions is met:
*
* value() == null
* rows() == 0
* cols() == 0
*
*
* @return true if empty
*/
public synchronized boolean isEmpty() {
return (value == null || value.length == 0 || value[0].length == 0);
}
/**
* @see com.jstatcom.model.JSCData
*/
public synchronized boolean isEqual(JSCData o) {
if (o == this)
return true;
if (!(o instanceof JSCSArray))
return false;
JSCSArray otherSArray = (JSCSArray) o;
boolean ret = true;
synchronized (this) {
synchronized (o) {
if (isEmpty() && otherSArray.isEmpty())
return true;
ret = UStringArray.compareStringArrays(value, otherSArray
.stringArray());
}
}
return ret;
}
/**
* @see com.jstatcom.model.JSCData
*/
public String name() {
return name;
}
/**
* Gets the number of rows in the stored array.
*
* @return the number of rows or 0 if (isEmpty())
*
*/
public synchronized int rows() {
if (value == null)
return 0;
return value.length;
}
/**
* Sets a new format string. This string is used by the display
* method to format the string array. The default format string is
*
* "%-12s ".
*
* For a complete description of all legal arguments, see the
* PrintfFormat documentation. The string must contain
* exactly one conversion character.
*
* @param newFormatString
* the format string to be used for display
* @throws IllegalArgumentException
* if newFormatString is not valid
* @see com.jstatcom.util.PrintfFormat
*/
public void setFormatString(String newFormatString) {
try {
FArg.sprintf(newFormatString, new FArg("test")); // For testing.
} catch (Throwable thr) {
throw new IllegalArgumentException("\"" + newFormatString
+ "\" is not a valid format string.");
}
formatString = newFormatString;
}
/**
* Sets the value of this to newValue. Fires a
* JSCDataEvent of type:
*
* JSCDataEventTypes.VALUE_CHANGED if
* newValue is different from current value
* JSCDataEventTypes.EMPTY_STATE if
* newValue is not empty and this was empty before
* JSCDataEventTypes.EMPTY_STATE if
* newValue is empty and this was not empty before
*
* A value is empty if it is either null or at least one
* dimension is zero. The newValue is only compared to the
* existing value if a listener to
* JSCDataEventTypes.VALUE_CHANGED is registered.
*
* @param newValue
* the new value
* @throws IllegalArgumentException
* if rows of newValue have different lengths
*/
public void setVal(String[][] newValue) {
if (newValue == null) {
clear();
return;
}
if (newValue.length > 0)
UStringArray.checkRowLengths(newValue);
boolean wasEmpty = false;
boolean isEmpty = false;
String[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = UStringArray.cloneStringArray(newValue);
isEmpty = isEmpty();
}
// Nothing changed.
if (wasEmpty && isEmpty)
return;
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. Check to
// avoid expensive comparison.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
if (!UStringArray.compareStringArrays(oldValue, newValue))
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, newValue));
}
// EMPTY_STATE event.
if (wasEmpty && !isEmpty) {
if (notEmptyEvent == null)
notEmptyEvent = JSCDataEvent.valueOfEmptyState(this, false);
getEventSupport().dispatchEvent(notEmptyEvent);
}
// EMPTY_STATE event.
if (!wasEmpty && isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Sets the value of this to newValue, see
* {@link #setVal(String[][] newValue)}.
*
* @param newValue
* the new value
*/
public void setVal(String... newValue) {
if (newValue == null) {
clear();
return;
}
boolean wasEmpty = false;
boolean isEmpty = false;
String[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = UStringArray.toStringMatrix(newValue);
isEmpty = isEmpty();
}
// Nothing changed.
if (wasEmpty && isEmpty)
return;
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. Check to
// avoid expensive comparison.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
String[][] toCompare = UStringArray.toStringMatrix(newValue);
if (!UStringArray.compareStringArrays(oldValue, toCompare))
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue, toCompare));
}
// EMPTY_STATE event.
if (wasEmpty && !isEmpty) {
if (notEmptyEvent == null)
notEmptyEvent = JSCDataEvent.valueOfEmptyState(this, false);
getEventSupport().dispatchEvent(notEmptyEvent);
}
// EMPTY_STATE event.
if (!wasEmpty && isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Sets the values stored in val to this
* JSCSArray. Future changes in val have no
* effect on this.
*
* @param val
* instance of JSCSArray
* @throws IllegalArgumentException
* if (val == null)
*/
public void setVal(JSCSArray val) {
if (val == null)
throw new IllegalArgumentException("Argument was null for "
+ toString() + ".");
String[][] newValue = val.stringArray();
if (newValue == null) {
clear();
return;
}
boolean wasEmpty = false;
boolean isEmpty = false;
String[][] valToCompare = null;
String[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = newValue; // has been copied already.
isEmpty = isEmpty();
// Copied if VALUE_CHANGED listeners, because comparison is done
// outside synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
valToCompare = UStringArray.cloneStringArray(newValue);
}
// Nothing changed.
if (wasEmpty && isEmpty)
return;
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. Check to
// avoid expensive comparison.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
if (!UStringArray.compareStringArrays(oldValue, valToCompare))
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue,
valToCompare));
}
// EMPTY_STATE event.
if (wasEmpty && !isEmpty) {
if (notEmptyEvent == null)
notEmptyEvent = JSCDataEvent.valueOfEmptyState(this, false);
getEventSupport().dispatchEvent(notEmptyEvent);
}
// EMPTY_STATE event.
if (!wasEmpty && isEmpty) {
if (clearEvent == null)
clearEvent = JSCDataEvent.valueOfEmptyState(this, true);
getEventSupport().dispatchEvent(clearEvent);
}
}
/**
* Sets the String element stored at index i, j. Fires a
* JSCDataEvent of type
* JSCDataEventTypes.VALUE_CHANGED if the new value is
* different from the one currently stored at that position. The event
* object contains the following values:
*
* oldValue-
* new Object[] { oldString, new Integer(i), new Integer(j) }
* newValue-
* new Object[] { newString, new Integer(i), new Integer(j) }
*
* Thus listeners can exactly pin down, where the change in the array
* happened.
*
* @param val
* the String to store at the given position
* @param i
* the row index, zero based
* @param j
* the column index, zero based
* @throws IllegalArgumentException
* if indices out of range
*/
public void setValAt(String val, int i, int j) {
String oldVal;
synchronized (this) {
checkIndices(i, j);
oldVal = value[i][j];
value[i][j] = val;
}
if (eventSupport == null)
return;
// Fire event outside synchronized block.
if (!oldVal.equals(val)) {
Integer intI = new Integer(i);
Integer intJ = new Integer(j);
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, new Object[] { oldVal,
intI, intJ }, new Object[] { val, intI, intJ }));
}
}
/**
* Gets a copy of the underlying value as a String[][].
*
* @return the string array
* if this is empty new String[0][0]
* is returned
*/
public synchronized String[][] stringArray() {
if (value == null)
return new String[0][0];
return UStringArray.cloneStringArray(value);
}
/**
* Gets the columns of this object stacked into a String[].
*
* @return vectorized version of this
* if this is empty new String[0] is
* returned
*/
public synchronized String[] vec() {
if (value == null || value.length == 0)
return new String[0];
String[] rtn = new String[value[0].length * value.length];
int index = 0;
for (int i = 0; i < value[0].length; i++)
for (int j = 0; j < value.length; j++)
rtn[index++] = value[j][i];
return rtn;
}
/**
* Gets the String element stored at index i, j.
*
* @param i
* the row index, zero based
* @param j
* the column index, zero based
* @return the String stored at this array position
* @throws IllegalArgumentException
* if indices out of range
*/
public synchronized String stringAt(int i, int j) {
checkIndices(i, j);
return value[i][j];
}
/**
* Gets a string representation of this JSCSArray instance.
* The value is only represented with a summary string, because the
* underlying array might be large. To print the complete value, use
* display instead.
*
* @return string with name, type and value information
*/
public synchronized String toString() {
String display;
if (isEmpty())
display = "{}";
else
display = value.length + "x" + value[0].length + ",[0,0]="
+ value[0][0];
return getClass().getName() + '@' + Integer.toHexString(hashCode())
+ " [name=" + name + ",type=" + JSCTypes.SARRAY + ",value="
+ display + "]";
}
/**
* Gets the type of this JSCData object.
*
* @return JSCTypes.SARRAY
*/
public JSCTypes type() {
return JSCTypes.SARRAY;
}
/**
* Gets a copy of the underlying value, which is stored as a
* String[][].
*
* @return the value object
* if this is empty new String[0][0]
* is returned
*/
public synchronized Object value() {
if (value == null)
return new String[0][0];
return UStringArray.cloneStringArray(value);
}
/**
* Transposes the underlying array. Nothing happens if this
* is empty. Fires a JSCDataEvent of type
* JSCDataEventTypes.VALUE_CHANGED if the stored array is not
* symmetric.
*/
public void transpose() {
String[][] valToCompare = null;
String[][] oldValue = null;
synchronized (this) {
if (isEmpty())
return;
String[][] newValue = new String[value[0].length][value.length];
for (int i = 0; i < newValue.length; i++)
for (int j = 0; j < newValue[0].length; j++)
newValue[i][j] = value[j][i];
oldValue = value;
value = newValue; // has been copied already.
// Copied if VALUE_CHANGED listeners, because comparison is done
// outside synched block.
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
valToCompare = UStringArray.cloneStringArray(newValue);
}
// No listeners.
if (eventSupport == null)
return;
// VALUE_CHANGED if listeners exist and something changed. Check to
// avoid expensive comparison.
// Only fired if this is not symmetric.
if (eventSupport.getListenersForType(JSCDataEventTypes.VALUE_CHANGED)
.size() > 0) {
if (!UStringArray.compareStringArrays(oldValue, valToCompare))
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue,
valToCompare));
}
}
/**
* Gets all rows of the underlying string array that have a nonzero
* corresponding element in rowIndex.
*
* @param rowIndex
* this.rows() x 1 vector selecting rows
* @return new array with the extracted rows, new string[0][0]
* if index contains only 0's, a copy of the underlying values if
* rowIndex contains only nonzero elements
* @throws IllegalArgumentException
* if (rowIndex == null) or
* if (this.isEmpty() && rowIndex > 0) or
* if (this.rows() != rowIndex)
*/
public synchronized String[][] selRowsIf(int[] rowIndex) {
return UStringArray.selRowsIf(value, rowIndex);
}
/**
* Gets the column of the underlying string array specified with
* colIndex. The index starts with 0 as usual.
*
* @param colIndex
* the index of the column to extract (starting from 0)
* @return a one dimensional string array with the column data
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* colIndex is outside of valid array bounds
*/
public synchronized String[] getCol(int colIndex) {
if (isEmpty())
throw new IllegalStateException(
"Cannot get cols from empty object " + toString() + ".");
return UStringArray.getStringCol(value, colIndex);
}
}
Ads help maintain this website.