/*
 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: *

* 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: * * 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: * * 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: * * * @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: *

* * @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: * * * @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: *

* * @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: * * 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: * * * @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: * * * @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: *

* 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: * * 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: * * 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.