/*
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 java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javolution37.javolution.xml.XmlElement;
import javolution37.javolution.xml.XmlFormat;
import org.apache.log4j.Logger;
import Jama.Matrix;
import com.jstatcom.util.FArg;
import com.jstatcom.util.UMatrix;
import com.jstatcom.util.UString;
/**
* A wrapper for 2-dimensional number arrays that conforms to the
* JSCData interface. Number arrays are stored as
* double[][]. It provides some convenience methods for matrix
* transformation, but it is not intended to serve as a general purpose matrix
* class. Instead it provides an adapter to set and get instances of the
* Jama.Matrix class, which is probably the de facto standard
* matrix class for JAVA .
*
* A JSCNArray 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 double[][]) 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 JSCNArray extends AbstractJSCData {
private static final Logger log = Logger.getLogger(JSCNArray.class);
private String formatString = "%- 8.4f ";
private final String name;
private double[][] value = new double[0][0];
private JSCDataEvent clearEvent = null;
private JSCDataEvent notEmptyEvent = null;
/**
* XML format field for (de)serialization.
*/
public static final XmlFormat JSCNArray_XML = new XmlFormat(
JSCNArray.class) {
public void format(JSCNArray data, XmlElement xml) {
xml.setAttribute("name", data.name);
synchronized (data) {
try {
StringBuffer valBuffer = new StringBuffer();
valBuffer.append(data.rows() + " " + data.cols() + " ");
for (int i = 0; i < data.rows(); i++)
for (int j = 0; j < data.cols(); j++)
valBuffer.append(data.value[i][j] + " ");
xml.setAttribute("values", valBuffer.toString().trim());
} catch (Throwable ex) {
log.error("Error in XML serialization of \"" + data.name
+ "\"", ex);
}
}
}
public JSCNArray parse(XmlElement xml) {
String nam = xml.getAttribute("name") + "";
String valString = xml.getAttribute("values") + "";
String[] tokenizer = valString.split(" ");
int rows = Integer.parseInt(tokenizer[0]);
int cols = Integer.parseInt(tokenizer[1]);
double[][] val = new double[rows][cols];
int tokenCount = 2;
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
val[i][j] = UString.parseToNumber(tokenizer[tokenCount++]);
JSCNArray array = new JSCNArray(nam, val);
return array;
}
};
/**
* Creates an empty JSCNArray.
*
* @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 JSCNArray(String name) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
this.value = new double[0][0];
}
/**
* Creates a JSCNArray from value. The
* elements of value are copied before.
*
* @param name
* the identifier of that object
* @param value
* the double[][] 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 JSCNArray(String name, double[][] value) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
if (value != null && value.length > 0)
UMatrix.checkRowLengths(value);
this.value = UMatrix.cloneDoubleArray(value);
}
/**
* Copy-Constructor.
*
* @param narray
* the number array to copy
* @throws IllegalArgumentException
* if (narray == null)
*
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public JSCNArray(JSCNArray narray) {
super();
if (narray == null)
throw new IllegalArgumentException("Argument was null.");
this.name = narray.name();
this.value = narray.doubleArray();
}
/**
* Creates a JSCNArray from value. The
* elements of value are copied before.
*
* @param name
* the identifier of that object
* @param value
* the double[] 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 JSCNArray(String name, double... value) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
this.value = UMatrix.toDoubleMatrix(value);
}
/**
* Creates a JSCNArray from value. The
* elements of value are copied before.
*
* @param name
* the identifier of that object
* @param value
* the int[] 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 JSCNArray(String name, int[] value) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
this.value = UMatrix.toDoubleMatrix(value);
}
/**
* Creates a JSCNArray from value. The
* elements of value are copied before.
*
* @param name
* the identifier of that object
* @param value
* the int[][] 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 JSCNArray(String name, int[][] value) {
super();
if (name == null)
throw new IllegalArgumentException("Argument was null.");
name = JSCConstants.checkNameThrowEx(name);
this.name = name;
if (value != null && value.length > 0)
UMatrix.checkRowLengths(value);
this.value = UMatrix.toDoubleMatrix(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 JSCNArray to be appended
* @throws IllegalArgumentException
* if (newCols == null) or
* if (rows() > 0 && newCols.rows() > 0 && rows() != newCols.rows())
*/
public void appendCols(JSCNArray 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;
double[][] newVal = null;
double[][] oldValue = null;
double[][] 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.doubleArray();
}
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 = UMatrix.cloneDoubleArray(toAppend);
} else {
wasEmpty = false;
oldValue = value;
value = UMatrix.appendDoubleCols(value, toAppend);
// has been copied already.
isEmpty = isEmpty();
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UMatrix.cloneDoubleArray(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 JSCNArray to be appended
* @throws IllegalArgumentException
* if (newRows == null) or
* if (cols() > 0 && newRows.cols() > 0 && cols() != newRows.cols())
*/
public void appendRows(JSCNArray 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;
double[][] newVal = null;
double[][] oldValue = null;
double[][] 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.doubleArray();
}
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 = UMatrix.cloneDoubleArray(toAppend);
} else {
wasEmpty = false;
oldValue = value;
value = UMatrix.appendDoubleRows(value, toAppend);
// has been copied already.
isEmpty = isEmpty();
if (eventSupport != null
&& eventSupport.getListenersForType(
JSCDataEventTypes.VALUE_CHANGED).size() > 0)
newVal = UMatrix.cloneDoubleArray(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() {
double[][] oldValue;
synchronized (this) {
if (isEmpty()) // Nothing changes.
return;
oldValue = value;
value = new double[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 double[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 JSCNArray copy() {
return new JSCNArray(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) {
double[][] oldValue = null;
boolean isEmpty = false;
double[][] 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 = UMatrix.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 = UMatrix.cloneDoubleArray(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())
* if (index == null)
*/
public void delColsIf(int... index) {
double[][] oldValue = null;
boolean isEmpty = false;
double[][] 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 = UMatrix.delColsIf(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 = UMatrix.cloneDoubleArray(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) {
double[][] oldValue = null;
boolean isEmpty = false;
double[][] 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 = UMatrix.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 = UMatrix.cloneDoubleArray(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) {
double[][] oldValue = null;
boolean isEmpty = false;
double[][] 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 = UMatrix.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 = UMatrix.cloneDoubleArray(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 double[][] 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 a copy of the underlying value as a double[][].
*
* @return the double array
* if this is empty new double[0][0]
* is returned
*/
public synchronized double[][] doubleArray() {
if (value == null)
return new double[0][0];
return UMatrix.cloneDoubleArray(value);
}
/**
* Gets the double element stored at index i, j.
*
* @param i
* the row index, zero based
* @param j
* the column index, zero based
* @return the double stored at this array position
* @throws IllegalArgumentException
* if indices out of range
*/
public synchronized double doubleAt(int i, int j) {
checkIndices(i, j);
return value[i][j];
}
/**
* Gets the Double element stored at index i, j.
*
* @param i
* the row index, zero based
* @param j
* the column index, zero based
* @return the Double stored at this array position
* @throws IllegalArgumentException
* if indices out of range
*/
public synchronized Double doubleValAt(int i, int j) {
checkIndices(i, j);
return new Double(value[i][j]);
}
/**
* Gets a copy of the underlying value as a int[][]. The
* following special int values for element i, j
* are returned:
*
* - 0 if
value[i][j] == Double.NaN
* -
*
Integer.MAX_VALUE if value[i][j] == Double.POSITIVE_INFINITY
* -
*
Integer.MIN_VALUE if value[i][j] == Double.NEGATIVE_INFINITY
*
* Note that during this operation information is potentially lost. Only the
* integer part is preserved, which is different from rounding.
*
* @return the double array
* if this is empty, then either null
* or new int[0][0] is returned, depending on what
* has been set before
*/
public synchronized int[][] intArray() {
return UMatrix.toIntMatrix(value);
}
/**
* Gets the int element stored at index i, j. Note that
* during this operation information is potentially lost. Only the integer
* part is preserved, which is different from rounding. The following
* special int values are returned:
*
* - 0 if
value == Double.NaN
* Integer.MAX_VALUE if value == Double.POSITIVE_INFINITY
* Integer.MIN_VALUE if value == Double.NEGATIVE_INFINITY
*
*
* @param i
* the row index, zero based
* @param j
* the column index, zero based
* @return the int stored at this array position
* @throws IllegalArgumentException
* if indices out of range
*/
public synchronized int intAt(int i, int j) {
checkIndices(i, j);
return (int) value[i][j];
}
/**
* 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 boolean isEqual(JSCData o) {
if (o == this)
return true;
if (!(o instanceof JSCNArray))
return false;
JSCNArray otherNArray = (JSCNArray) o;
boolean ret = true;
synchronized (this) {
synchronized (o) {
if (isEmpty() && otherNArray.isEmpty())
return true;
ret = UMatrix.compareDoubleArrays(value, otherNArray
.doubleArray());
}
}
return ret;
}
/**
* Gets whether the element stored at index i, j is of type
* Double.NaN.
*
* @param i
* the row index
* @param j
* the column index
* @return true if the array element is a missing value
* @throws IllegalArgumentException
* if indices out of range
*/
public synchronized boolean isNaN(int i, int j) {
checkIndices(i, j);
return Double.isNaN(value[i][j]);
}
/**
* Gets the values stored in this JSCNArray as an instance of
* Jama.Matrix. The underlying array is copied before.
*
* @return instance of Jama.Matrix initialized with the
* stored values
* @throws IllegalStateException
* if this is empty
* @see Jama.Matrix
*/
public synchronized Matrix jamaMatrix() {
if (isEmpty())
throw new IllegalStateException(
"Cannot construct Jama.Matrix from empty data object "
+ toString() + ".");
return Matrix.constructWithCopy(value);
}
/**
* @see com.jstatcom.model.JSCData
*/
public String name() {
return name;
}
/**
* Gets the number of double elements that are equal to
* Double.NaN.
*
* @return the number of missing values
*/
public synchronized int nanCount() {
int count = 0;
for (int i = 0; i < rows(); i++)
for (int j = 0; j < cols(); j++)
count = (isNaN(i, j)) ? ++count : count;
return count;
}
/**
* Gets the rank of the stored value. The computation is delegated to a
* Jama.Matrix instance.
*
* @return the rank of the double[][] value
* @throws IllegalStateException
* if this is empty
*/
public synchronized int rank() {
if (isEmpty())
throw new IllegalStateException(
"Cannot compute rank for empty data object " + toString()
+ ".");
return new Matrix(value).rank();
}
/**
* 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 double array. The default format string is
*
* "%- 8.4f ".
*
* 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(0.0)); // 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(double[][] newValue) {
if (newValue == null) {
clear();
return;
}
if (newValue.length > 0)
UMatrix.checkRowLengths(newValue);
boolean wasEmpty = false;
boolean isEmpty = false;
double[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = UMatrix.cloneDoubleArray(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 (!UMatrix.compareDoubleArrays(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(double[][] newValue)}.
*
* @param newValue
* the new value
* @throws IllegalArgumentException
* if rows of newValue have different lengths
*/
public void setVal(int[][] newValue) {
if (newValue == null) {
clear();
return;
}
if (newValue.length > 0)
UMatrix.checkRowLengths(newValue);
boolean wasEmpty = false;
boolean isEmpty = false;
double[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = UMatrix.toDoubleMatrix(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) {
double[][] toCompare = UMatrix.toDoubleMatrix(newValue);
if (!UMatrix.compareDoubleArrays(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 value of this to newValue, see
* {@link #setVal(double[][] newValue)}.
*
* @param newValue
* the new value
*/
public void setVal(double[] newValue) {
if (newValue == null) {
clear();
return;
}
boolean wasEmpty = false;
boolean isEmpty = false;
double[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = UMatrix.toDoubleMatrix(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) {
double[][] toCompare = UMatrix.toDoubleMatrix(newValue);
if (!UMatrix.compareDoubleArrays(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 value of this to newValue, see
* {@link #setVal(double[][] newValue)}.
*
* @param newValue
* the new value
*/
public void setVal(int[] newValue) {
if (newValue == null) {
clear();
return;
}
boolean wasEmpty = false;
boolean isEmpty = false;
double[][] oldValue = null;
synchronized (this) {
wasEmpty = isEmpty();
oldValue = value;
value = UMatrix.toDoubleMatrix(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) {
double[][] toCompare = UMatrix.toDoubleMatrix(newValue);
if (!UMatrix.compareDoubleArrays(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
* JSCNArray. Future changes in val have no
* effect on this.
*
* @param val
* instance of JSCNArray
* @throws IllegalArgumentException
* if (val == null)
*/
public void setVal(JSCNArray val) {
if (val == null)
throw new IllegalArgumentException("Argument was null for "
+ toString() + ".");
double[][] newValue = val.doubleArray();
if (newValue == null) {
clear();
return;
}
boolean wasEmpty = false;
boolean isEmpty = false;
double[][] valToCompare = null;
double[][] 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 = UMatrix.cloneDoubleArray(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 (!UMatrix.compareDoubleArrays(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 values stored in mat to this
* JSCNArray. The underlying array is copied before.
*
* @param mat
* instance of Jama.Matrix
* @see Jama.Matrix
*/
public void setVal(Matrix mat) {
setVal(mat.getArray());
}
/**
* Sets the double 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 double[] { oldNumber, i, j }
* newValue-
* new double[] { newNumber, i, j }
*
* Thus listeners can exactly pin down, where the change in the array
* happened.
*
* @param val
* the double 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(double val, int i, int j) {
double oldVal;
synchronized (this) {
checkIndices(i, j);
oldVal = value[i][j];
value[i][j] = val;
}
if (eventSupport == null)
return;
// Fire event outside synchronized block.
if (Double.doubleToLongBits(oldVal) != Double.doubleToLongBits(val)) {
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, new double[] { oldVal, i,
j }, new double[] { val, i, j }));
}
}
/**
* Gets a string representation of this JSCNArray 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.NARRAY + ",value="
+ display + "]";
}
/**
* 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() {
double[][] valToCompare = null;
double[][] oldValue = null;
synchronized (this) {
if (isEmpty())
return;
double[][] newValue = new Matrix(value).transpose().getArray();
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 = UMatrix.cloneDoubleArray(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 (!UMatrix.compareDoubleArrays(oldValue, valToCompare))
getEventSupport().dispatchEvent(
JSCDataEvent.valueOfChanged(this, oldValue,
valToCompare));
}
}
/**
* Gets the type of this JSCData object.
*
* @return JSCTypes.NARRAY
*/
public JSCTypes type() {
return JSCTypes.NARRAY;
}
/**
* Gets a copy of the underlying value, which is stored as a
* double[][].
*
* @return the value object
* if this is empty new double[0][0]
* is returned
*/
public synchronized Object value() {
if (value == null)
return new double[0][0];
return UMatrix.cloneDoubleArray(value);
}
/**
* Gets the number of double elements that are equal to zero.
*
* @return the number of zero elements
*/
public synchronized int zeroCount() {
int count = 0;
for (int i = 0; i < rows(); i++)
for (int j = 0; j < cols(); j++)
count = (value[i][j] == 0.0) ? ++count : count;
return count;
}
/**
* Creates an instance of this class from a data file. The method parses the
* elements file into a double array according to the
* following conventions:
*
* - the number of rows of the array is the number of nonempty rows in
*
file
* - the number of cols of the array is the number of tokens of the row
* with the most tokens
*
- entries are parsed to numbers, exponential notation is allowed, all
* entries that cannot be parsed to a number are set to
*
Double.NaN
*
* This means that if file is empty, an empty
* JSCNArray will be returned.
*
* @param file
* the file to parse the double array from
* @param name
* the name of the new JSCNArray to create
* @return an instance of JSCNArray
* @throws IllegalArgumentException
* if (file == null || name == null), or if
* name is not legal or
* if (!file.isFile())
*/
public static JSCNArray valueOf(File file, String name) {
if (file == null || name == null)
throw new IllegalArgumentException("Argument was null.");
JSCConstants.checkNameThrowEx(name);
if (!file.isFile())
throw new IllegalArgumentException("File " + file
+ " does not exist.");
List list = new ArrayList();
boolean inComment = false;
try {
String str = null;
BufferedReader in = new BufferedReader(new FileReader(file));
while ((str = in.readLine()) != null) {
str = str.replaceAll("#", "");
str = str.replaceAll("/\\*", "#");
str = str.replaceAll("\\*/", "#");
if (!inComment) {
str = str.replaceAll("#[^#]*#", "");
int index = str.indexOf("#");
if (index > -1) { // must be an open ended comment
inComment = true;
str = str.substring(0, index);
}
} else {
int index = str.indexOf("#");
if (index > -1) {
inComment = false;
str = str.substring(index + 1, str.length());
str = str.replaceAll("#[^#]*#", "");
index = str.indexOf("#");
if (index > -1) { // must be another open ended
// comment
inComment = true;
str = str.substring(0, index);
}
} else
str = "";
}
str = str.trim();
if (str.length() > 0)
list.add(str);
}
in.close();
} catch (IOException e) {
// ignored
}
// empty file
if (list.size() == 0)
return new JSCNArray(name);
// cols are max number of elements in a row
int maxCols = 0;
for (int i = 0; i < list.size(); i++) {
StringTokenizer tokenizer = new StringTokenizer(list.get(i) + "");
int count = tokenizer.countTokens();
maxCols = (count > maxCols) ? count : maxCols;
}
double[][] dataArray = new double[list.size()][maxCols];
for (int i = 0; i < dataArray.length; i++) {
StringTokenizer t = new StringTokenizer(list.get(i) + "");
int k = 0;
while (t.hasMoreTokens() && k < maxCols) {
Double val = UString.parseToNumber(t.nextToken());
if (val != null)
dataArray[i][k++] = val.doubleValue();
else
dataArray[i][k++] = Double.NaN;
}
// just in case there are not enough tokens in a row
for (int j = k; j < maxCols; j++)
dataArray[i][j] = Double.NaN;
}
return new JSCNArray(name, dataArray);
}
/**
* Gets the column of the underlying double 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 double array with the column data
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* colIndex is outside of valid array bounds
*/
public synchronized double[] getCol(int colIndex) {
if (isEmpty())
throw new IllegalStateException(
"Cannot get cols from empty object " + toString() + ".");
return UMatrix.getDoubleCol(value, colIndex);
}
/**
* Gets a vector stacked with the columns of the underlying data array.
*
* @return a one dimensional double array with all values starting with the
* first column
*/
public synchronized double[] vec() {
if (isEmpty())
return new double[0];
return UMatrix.vec(value);
}
/**
* Gets the row of the underlying double array specified with
* rowIndex. The index starts with 0 as usual.
*
* @param rowIndex
* the index of the row to extract (starting from 0)
* @return a one dimensional double array with the row data
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* rowIndex is outside of valid array bounds
*/
public synchronized double[] getRow(int rowIndex) {
if (isEmpty())
throw new IllegalStateException(
"Cannot get rows from empty object " + toString() + ".");
return UMatrix.getDoubleRow(value, rowIndex);
}
/**
* Gets all columns of the underlying double array defined by
* startCol:endCol. The indices start with 0 as usual.
*
* @param startCol
* the index of the first column to extract (starting from 0)
* @param endCol
* the index of the last column to extract (starting from 0)
* @return a two dimensional double array with the column data
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* if (startCol > endCol) or if the indices
* are outside the valid array bounds
*/
public synchronized double[][] getCols(int startCol, int endCol) {
if (isEmpty())
throw new IllegalStateException(
"Cannot get cols from empty object " + toString() + ".");
return UMatrix.getDoubleCols(value, startCol, endCol);
}
/**
* Gets all rows of the underlying double array defined by
* startRow:endRow. The indices start with 0 as usual.
*
* @param startRow
* the index of the first row to extract (starting from 0)
* @param endRow
* the index of the last row to extract (starting from 0)
* @return a two dimensional double array with the row data
* @throws IllegalStateException
* if this is empty
* @throws IllegalArgumentException
* if (startRow > endRow) or if the indices
* are outside the valid array bounds
*/
public synchronized double[][] getRows(int startRow, int endRow) {
if (isEmpty())
throw new IllegalStateException(
"Cannot get rows from empty object " + toString() + ".");
return UMatrix.getDoubleRows(value, startRow, endRow);
}
/**
* Gets all columns of the underlying double array that have a nonzero
* corresponding element in colIndex.
*
* @param colIndex
* this.cols() x 1 vector selecting columns
* @return new array with the extracted columns,
* new double[0][0] if index contains only 0's, a
* copy of the underlying values if colIndex contains
* only nonzero elements
* @throws IllegalArgumentException
* if (colIndex == null) or
* if (this.isEmpty() && colIndex > 0) or
* if (this.cols() != colIndex)
*/
public synchronized double[][] selColsIf(int... colIndex) {
return UMatrix.selColsIf(value, colIndex);
}
/**
* Gets all rows of the underlying double 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 double[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 double[][] selRowsIf(int... rowIndex) {
return UMatrix.selRowsIf(value, rowIndex);
}
/**
* Gets a new double array with elements transposed.
*
* @return transposed data
*/
public synchronized double[][] getTransposed() {
if (value == null)
value = new double[0][0];
return UMatrix.transpose(value);
}
/**
* Gets a new double array with the elements raised to the power of
* pow.
*
* @param pow
* the power to raise each element of the data array to
* @return double array with changed elements
*/
public synchronized double[][] getPow(double pow) {
if (value == null)
value = new double[0][0];
return UMatrix.pow(value, pow);
}
}
Ads help maintain this website.