All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.xma.mdl.util.TransAtomTable Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

// @(#) $Id: TransAtomTable.java 2089 2007-11-28 13:56:13Z s3460 $
package at.spardat.xma.mdl.util;

import java.io.IOException;
import java.util.ArrayList;

import at.spardat.xma.mdl.*;
import at.spardat.xma.mdl.Atom;
import at.spardat.xma.mdl.Transactional;
import at.spardat.xma.serializer.XmaInput;
import at.spardat.xma.serializer.XmaOutput;

/**
 * Manages a two dimensional arrays of Atom objects. Each row is
 * uniquely identified by a String-key. No two rows may have the same key. 
 * Besides accessing rows by a zero-based row index, rows may be accessed
 * using the key. 

* * Rows are specified using arrays of Atoms. Whereever a Atom array is provided * as method parameter (e.g., adding a row), a copy of the array is stored internally. So * the caller may freely reuse the array.

* * This class relies on the fact that class Atom is immutable. Making * Atom non immutable would break this class.

* * Columns are specified with zero-based column indexes. The number of columns * a table has is specified at construction time.

* * A TransAtomTable supports transactional behaviour, so its changes * made since the last syncpoint (construction, commit, rollback) may be undone. * To support this, the syncpoint state is saved if modifications are done. * The modifications themselves are also kept in a list up to a point where * the size of the changes approaches the size of the table. In this case, * changes are discarded which effects serialization.

* * Serialization is supported (methods externalize and internalize). * The caller may choose to externalize deltas or the complete state. This * class internally decides up to which point changes are tracked based on heuristics * (actually if the cumulated serialized size of changes approaches the serialized * size of the complete state). Thus the caller merely may express its wish that changes * should be externalized.

* * @author YSD, 13.04.2003 09:26:44 */ public class TransAtomTable implements Transactional, Synchronization, Descriptive { /** * Constructor. * * @param numCols number of columns in this table. */ public TransAtomTable (int numCols) { numCols_ = numCols; } /** * @see at.spardat.xma.mdl.Transactional#changed() */ public boolean changed() { return saved_ != null; } /** * @see at.spardat.xma.mdl.Transactional#rollback() */ public void rollback() { if (changed()) { data_ = saved_; streamedTableSize_ = streamedSize(); changes_ = null; streamedChangesSize_ = 0; saved_ = null; } } /** * @see at.spardat.xma.mdl.Transactional#commit() */ public void commit() { if (changed()) { saved_ = null; changes_ = null; streamedChangesSize_ = 0; } } /** * Returns the number of rows in this */ public int size () { return data_.size(); } /** * Returns an Atom by providing row and column indexes. * * @param rowIndex the index of the row * @param colIndex the index of the column * @return the retrieved Atom * @exception ArrayIndexOutOfBoundsException if a provided index is invalid */ public Atom get (int rowIndex, int colIndex) { Atom [] row = (Atom[]) data_.getValue(rowIndex); return row[colIndex]; } /** * Returns an Atom by providing a row key and a column index. * * @param key the key of the row * @param colIndex the index of the columns * @return the retrieved Atom or null if this table has no row * with the provided key. * @exception ArrayIndexOutOfBoundsException if colIndex is invalid. */ public Atom get (String key, int colIndex) { Atom [] row = (Atom[]) data_.getValue(key); return row[colIndex]; } /** * Returns a copy of the Atom array for a particular key or null, * if this has no row with the provided key. * * @param key of the wanted row * @return null, if key not found, copied Atom [] otherwise. */ public Atom [] getAtoms (String key) { Atom [] atoms = (Atom[]) data_.getValue(key); if (atoms == null) return null; Atom [] copy = new Atom[atoms.length]; System.arraycopy(atoms, 0, copy, 0, atoms.length); return copy; } /** * Returns the index of the row whose key equals the one provided.

* * This is a time consuming operation of O(n). * * @param key the key of the entry whose row is wanted. * @return the index of the found entry or -1 if the key is not * in the table. */ public int indexOf (String key) { return data_.indexOf(key); } /** * Returns the key at a provided row index. * * @exception ArrayIndexOutOfBoundsException if index out of range. */ public String getKey (int rowIndex) { return data_.getKey(rowIndex); } /** * Adds a row to this at a provided index. * * @param rowIndex the index at which the row should be added. Must * lie in the range [0, size()]. * @param key the key of the newly inserted row * @param atoms an array of Atoms. * @return true if added, false if this already contains a row with the * provided key and the row was not added. * @exception IllegalArgumentException if rowIndex out of range * or the length of atoms is unequal to the number * of columns provided at construction time. */ public boolean add (int rowIndex, String key, Atom [] atoms) { if (data_.containsKey(key)) return false; if (atoms.length != numCols_) throw new IllegalArgumentException(); if (rowIndex < 0 || rowIndex > data_.size()) throw new IllegalArgumentException(); return handle (new AddRowChangeEvent (key, rowIndex, atoms)); } /** * Adds a row at the end of this. * * @param key the key of the newly inserted row * @param atoms an array of Atoms. * @return true if added, false if this already contains a row with the * provided key * @exception IllegalArgumentException if the length of atoms * is unequal to the number * of columns provided at construction time. */ public boolean add (String key, Atom [] atoms) { return add (size(), key, atoms); } /** * Removes a row for a given key from this. This operation is of O(n). * * @param key the key whose row should be removed * @return true if this has contained a row with the given key, false * otherwise */ public boolean remove (String key) { int index = data_.indexOf(key); if (index == -1) return false; remove (index); return true; } /** * Removes a row for at a given index from this. This operation is of O(n). * * @param index the index of the row that should be removed. * @exception ArrayIndexOutOfBoundsException if index out of range */ public void remove (int index) { if (index < 0 || index >= size()) throw new ArrayIndexOutOfBoundsException(); if (!handle (new RemoveRowChangeEvent(index))) throw new InternalError(); } /** * Replaces a row at a given index. * * @param index the index at which the row is to be replaced. * @param row the new row * @exception ArrayIndexOutOfBoundsException if index is out of bounds. * @exception IllegalArgumentException if the length of atoms is unequal to the number * of columns provided at construction time. * */ public void replace (int index, Atom[] row) { if (index < 0 || index >= size()) throw new ArrayIndexOutOfBoundsException(); if (row == null || row.length != numCols_) throw new IllegalArgumentException(); if (!handle (new ReplaceRowChangeEvent(index, row))) throw new InternalError(); } /** * Replaces a particular cell in the table. * * @param rowIndex the row index at which the new cell is to be replaced * @param colIndex the column index of the cell * @param cell the new Atom * @exception ArrayIndexOutOfBoundsException if rowIndex or colIndex * are out of bounds. */ public void replace (int rowIndex, int colIndex, Atom cell) { if (rowIndex < 0 || rowIndex >= size()) throw new ArrayIndexOutOfBoundsException(); if (colIndex < 0 || colIndex >= numCols_) throw new ArrayIndexOutOfBoundsException(); if (!handle (new ReplaceAtomChangeEvent(rowIndex, colIndex, cell))) throw new InternalError(); } /** * Returns true if this table contains a row with the provided key. */ public boolean containsKey (String key) { return data_.containsKey(key); } /** * Removes all rows from this. */ public void clear () { if (!handle (new ClearEvent())) throw new InternalError(); } /** * Externalizing this either serializes the actual state of this or the accumulated * changes. The default behaviour is to serialize the changes if changes are beeing * tracked. This behaviour may be overwritten by * ignoreDeltas. If true, changes are never written. * * @param xo the serialization destination * @param ignoreDeltas if true, the actual state of this (all rows) is written * and deltas are ignored. * @throws IOException on serialization errors */ public void externalize (XmaOutput xo, boolean ignoreDeltas) throws IOException { if (changes_ != null && !ignoreDeltas) { // output a byte indicating that changes are following xo.writeBoolean ("deltas", true); // output the changes externalizeChanges (changes_, xo); } else { // output a byte indicating that no changes are following xo.writeBoolean ("deltas", false); // output all rows of the table externalizeRows (data_, xo); } } /** * Updates the state of this with information of the given XmaInput. Before * doing this, the changes in this are committed (i.e., history is discarded). * Then, if in contains changes, they are read and applied to the * preexisting state. Otherwise, all rows are replaced with the rows * from in.

* * In either case, the state after calling internalize is !changed(). * * @param in the XmaInput. * @throws IOException on serialization errors * @throws ClassNotFoundException on serialization errors */ public void internalize (XmaInput in) throws IOException, ClassNotFoundException { commit(); boolean deltas = in.readBoolean(); if (deltas) { // set of changes ArrayList changes = internalizeChanges(in); for (int i=0; iev has been executed */ private boolean handle (TableChangeEvent ev) { // make a copy of data_ if not copied yet boolean firstModification = false; // first modification since last syncpoint? if (saved_ == null) { saved_ = (KeyedList) data_.clone(); firstModification = true; } // execute the event boolean success = ev.execute(); if (!success && firstModification) { saved_ = null; } if (!success) return false; // if this has been the first modification, decide if changes should be tracked if (firstModification) { if (streamedTableSize_ > 50) { // start change tracking if there are at least 50 bytes in the table (heuristics) changes_ = new ArrayList(); streamedChangesSize_ = 0; } } // if change tracking is on (changes_ != null), add the change to the list of changes if (changes_ != null) { changes_.add(ev); streamedChangesSize_ += ev.streamedSize(); // decide if change tracking should be stopped because the cumulated size of // the changes reaches some fraction of the cumulated size of the table. if (streamedChangesSize_ > streamedTableSize_ * MAX_STREAMED_CHANGES_RATIO) { // drop change list changes_ = null; streamedChangesSize_ = 0; } } return success; } /** * Returns the length of a byte array resulting from streaming an Atom[] * * @param arr the array whose streamed size is requested. */ private static int streamedSizeOfAtomArray (Atom [] arr) { int length = 0; for (int i=arr.length-1; i>=0; i--) { length += Atom.streamedSize(arr[i]); } return length; } /** * Returns an estimator on how much bytes a provided row consumes when * serialized. * * @param key the key of the row * @param arr the Atom array of the row * @return number of bytes to serialize the provided row */ private static int streamedSizeOfRow (String key, Atom [] arr) { return 2 + key.length() + streamedSizeOfAtomArray(arr); } /** * Returns the cumulated streamed size over all rows in this table. */ private int streamedSize () { int streamedSize = 0; for (int i=data_.size()-1; i>=0; i--) { streamedSize += streamedSizeOfRow (data_.getKey(i), (Atom[])data_.getValue(i)); } return streamedSize; } /** * Copies an Atom array and returns the copy. * * @param atomsIn the array to copy * @return the copy */ private static Atom [] copyAtoms (Atom [] atomsIn) { Atom [] atoms = new Atom[atomsIn.length]; System.arraycopy(atomsIn, 0, atoms, 0, atomsIn.length); return atoms; } /** * Externalizes an Atom array * * @param atoms the array to externalize * @param xo the XMA wrapper around ObjectOutput * @throws IOException on serialization errors */ private void externalizeAtoms (Atom[] atoms, XmaOutput xo) throws IOException { xo.writeShort("numAtoms", atoms.length); for (int i=0; iXmaInput. * * @param xi the input stream * @return newly created ArrayList with newly created TableChangeEvents. Returned * ArrayList is never null. * @throws IOException on serialization errors * @throws ClassNotFoundException on serialization errors */ private ArrayList internalizeChanges (XmaInput xi) throws IOException, ClassNotFoundException { // read the number of changes int numChanges = xi.readInt(); ArrayList changes = new ArrayList(numChanges); for (int i=0; irows to the serialization destination xo. * * @param rows KeyedList which holds the rows to write. * @param xo the output destination * @throws IOException on serialization errors */ private void externalizeRows (KeyedList rows, XmaOutput o) throws IOException { int numRows = rows.size(); // write size o.writeInt("numRows", numRows); // the rows themselves for (int i=0; iO(n). * * @author YSD, 14.04.2003 09:43:51 */ public final class RemoveRowChangeEvent extends TableChangeEvent { /** * May only be used to call internalize thereafter. */ public RemoveRowChangeEvent () { } /** * Constructor * * @param index the index of the row to delete */ public RemoveRowChangeEvent (int index) { index_ = index; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#execute() */ public boolean execute() { if (updateStreamedSize_) { String key = data_.getKey(index_); Atom [] row = (Atom[]) data_.getValue(index_); streamedTableSize_ -= streamedSizeOfRow(key, row); } data_.remove(index_); return true; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#getType() */ public byte getType() { return T_REMOVEROW_EVT; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#streamedSize() */ public int streamedSize() { return 1 + // type 4; //index } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#externalize(at.spardat.xma.serializer.XmaOutput) */ public void externalize(XmaOutput out) throws IOException { out.writeInt("index", index_); } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#internalize(at.spardat.xma.serializer.XmaInput) */ public void internalize(XmaInput in) throws IOException, ClassNotFoundException { index_ = in.readInt(); } /** * @see at.spardat.xma.mdl.util.Descriptive#describe(at.spardat.xma.mdl.util.DNode) */ public void describe (DNode n) { super.describe(n); n.comma(); n.app("index", index_); } private int index_; } /** * Replaces a row at a given index with a new row. * * @author YSD, 14.04.2003 09:59:31 */ public final class ReplaceRowChangeEvent extends TableChangeEvent { /** * May only be used to call internalize thereafter. */ public ReplaceRowChangeEvent () { } /** * Constructor */ public ReplaceRowChangeEvent (int index, Atom [] row) { index_ = index; newRow_ = copyAtoms(row); } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#execute() */ public boolean execute() { String key = data_.getKey(index_); if (updateStreamedSize_) { streamedTableSize_ -= streamedSizeOfAtomArray((Atom[]) data_.getValue(index_)); streamedTableSize_ += streamedSizeOfAtomArray(newRow_); } data_.replace(key, newRow_); return true; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#getType() */ public byte getType() { return T_REPLACEROW_EVT; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#streamedSize() */ public int streamedSize() { return 1 + // type 4 + //index streamedSizeOfAtomArray(newRow_); } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#externalize(at.spardat.xma.serializer.XmaOutput) */ public void externalize(XmaOutput out) throws IOException { out.writeInt("index", index_); externalizeAtoms(newRow_, out); } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#internalize(at.spardat.xma.serializer.XmaInput) */ public void internalize(XmaInput in) throws IOException, ClassNotFoundException { index_ = in.readInt(); newRow_ = internalizeAtoms(in); } /** * @see at.spardat.xma.mdl.util.Descriptive#describe(at.spardat.xma.mdl.util.DNode) */ public void describe (DNode n) { super.describe(n); n.comma(); n.app("index", index_).comma(); n.app("newRow: "); describeAtomArray(n, newRow_); } private int index_; private Atom [] newRow_; } /** * Replaces an Atom (table cell) in the table. * * @author YSD, 14.04.2003 09:59:31 */ public final class ReplaceAtomChangeEvent extends TableChangeEvent { /** * May only be used to call internalize thereafter. */ public ReplaceAtomChangeEvent () { } /** * Constructor * * @param rowIndex the index of the row * @param colIndex the index of the column * @param atom the new Atom */ public ReplaceAtomChangeEvent (int rowIndex, int colIndex, Atom atom) { rowIndex_ = rowIndex; colIndex_ = colIndex; atom_ = atom; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#execute() */ public boolean execute() { Atom [] oldRow = (Atom[]) data_.getValue(rowIndex_); Atom [] newRow = copyAtoms(oldRow); newRow[colIndex_] = atom_; if (updateStreamedSize_) { streamedTableSize_ -= streamedSizeOfAtomArray(oldRow); streamedTableSize_ += streamedSizeOfAtomArray(newRow); } data_.replace(data_.getKey(rowIndex_), newRow); return true; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#getType() */ public byte getType() { return T_REPLACEATOM_EVT; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#streamedSize() */ public int streamedSize() { return 1 + // type 4 + // rowIndex 4 + // ColIndex Atom.streamedSize(atom_); } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#externalize(at.spardat.xma.serializer.XmaOutput) */ public void externalize(XmaOutput out) throws IOException { out.writeInt("rowIdx", rowIndex_); out.writeInt("colIdx", colIndex_); Atom.externalize(atom_, out); } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#internalize(at.spardat.xma.serializer.XmaInput) */ public void internalize(XmaInput in) throws IOException, ClassNotFoundException { rowIndex_ = in.readInt(); colIndex_ = in.readInt(); atom_ = Atom.internalize(in); } /** * Returns the row index of the row where the change is applied */ public int getRowIndex() { return rowIndex_; } /** * @see at.spardat.xma.mdl.util.Descriptive#describe(at.spardat.xma.mdl.util.DNode) */ public void describe (DNode n) { super.describe(n); n.comma(); n.app("rowIdx", rowIndex_).comma(); n.app("colIdx", colIndex_).comma(); n.app("atom", atom_); } private int rowIndex_; private int colIndex_; private Atom atom_; } /** * Clears the table * * @author YSD, 14.04.2003 11:27:21 */ public final class ClearEvent extends TableChangeEvent { /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#execute() */ public boolean execute() { data_.clear(); if (updateStreamedSize_) streamedTableSize_ = 0; return true; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#getType() */ public byte getType() { return T_CLEAR_EVT; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#streamedSize() */ public int streamedSize() { return 1; } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#externalize(at.spardat.xma.serializer.XmaOutput) */ public void externalize(XmaOutput out) throws IOException { // empty since there is no data this event consists of } /** * @see at.spardat.xma.mdl.util.TransAtomTable.TableChangeEvent#internalize(at.spardat.xma.serializer.XmaInput) */ public void internalize(XmaInput in) throws IOException, ClassNotFoundException { // empty since there is no data this event consists of } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy