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

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

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

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

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