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

at.spardat.xma.mdl.table.TableWM Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
/*******************************************************************************
 * 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: TableWM.java 7141 2011-01-27 14:25:43Z gub $
package at.spardat.xma.mdl.table;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

import at.spardat.enterprise.util.Types;
import at.spardat.xma.mdl.Atom;
import at.spardat.xma.mdl.ISelectable;
import at.spardat.xma.mdl.ModelChangeEvent;
import at.spardat.xma.mdl.NewModelEvent;
import at.spardat.xma.mdl.NewModelEventFactory;
import at.spardat.xma.mdl.Notification;
import at.spardat.xma.mdl.WModel;
import at.spardat.xma.mdl.util.*;
import at.spardat.xma.page.Page;
import at.spardat.xma.serializer.XmaInput;
import at.spardat.xma.serializer.XmaOutput;
import at.spardat.xma.test.TestUtil;
import at.spardat.xma.util.Assert;

/**
 * A table widget model where the programmer is in full control on what rows the
 * table has. Rows may be added, replaced or removed. Every row may have
 * an additional image displayed with the row. Rows are stored in an ordered
 * collection and may be accessed either by a unique String key or by
 * a zero based row index. 

* * Besides managing the rows, a single or multiple selection state is * controlled (interface ISelectable). The selected rows are * identified by their String keys. * * @author YSD, 26.04.2003 09:27:39 */ public class TableWM extends TableBaseWM implements ISelectable, ITableWM { /** * Holds selection information. */ TransStringSet selection_; /** * Two dimensional array of Atom objects managing the data of the table. This table * has one more columns that there are TableColumns. The last columns holds * a T_BCD atom which specifies the image id of a row icon. */ TransAtomTable table_; /** * Specifies if this table model is one way. * @see S_ONE_WAY */ private boolean isOneWay_; /** * A counter which is incremented after every update. The purpose is to detect * illegal deferred usage of TableRow objects (which cannot be updated anymore). */ protected int updateCount_; /** * column types which will be set by generated code */ protected byte[] columnTypes; /** * Constructor * * @param id uniquely identifies the model within its page * @param pm the enclosing page model this widget model belongs to. * @param numColumns number of columns this table has * @param style bit or combination of the style constants S_*. */ public TableWM (short id, Page pm, int numColumns, int style) { super (id, pm, numColumns, true); if ((style & S_MULTI_SELECT) != 0) selection_ = new TransStringSetN(); else selection_ = new TransStringSet1(); if ((style & S_ONE_WAY) != 0) isOneWay_ = true; // note that the TransAtomTable has one more column. The last // (additional) column is used to store the image id table_ = new TransAtomTable(numColumns+1); columnTypes = new byte[numColumns]; } /** * @see at.spardat.xma.mdl.Transactional#changed() */ public boolean changed () { return selection_.changed() || table_.changed(); } /** * @see at.spardat.xma.mdl.Transactional#rollback() */ public void rollback () { selection_.rollback(); table_.rollback(); handle (new TableRowsChangedEvent()); handle (new SelectionChangedEvent(false)); } /** * @see at.spardat.xma.mdl.Transactional#commit() */ public void commit () { selection_.commit(); table_.commit(); } /** * @see at.spardat.xma.mdl.WModel#handle(at.spardat.xma.mdl.ModelChangeEvent) */ public boolean handle (ModelChangeEvent event) { boolean success = event.execute(); if (success) updateCount_++; return success; } /** * @see at.spardat.xma.mdl.ISelectable#select(java.lang.String) */ public void select (String key) { if (isOneWay_ || table_.containsKey(key)) { boolean success = selection_.add (key); if (success) handle (new SelectionChangedEvent (false)); } } /** * @see at.spardat.xma.mdl.ISelectable#deselect(java.lang.String) */ public void deselect (String key) { selection_.remove (key); handle (new SelectionChangedEvent (false)); } /** * @see at.spardat.xma.mdl.ISelectable#deselectAll() */ public void deselectAll() { selection_.clear(); handle (new SelectionChangedEvent (false)); } /** * @see at.spardat.xma.mdl.ISelectable#isMultiSelect() */ public boolean isMultiSelect() { return selection_ instanceof TransStringSetN; } /** * @see at.spardat.xma.mdl.ISelectable#getSelected() */ public String getSelected() { return selection_.getSome(); } /** * @see at.spardat.xma.mdl.ISelectable#getSelection() */ public String[] getSelection() { return selection_.getAll(); } /** * @see at.spardat.xma.mdl.ISelectable#getSelectionCount() */ public int getSelectionCount() { return selection_.size(); } /** * @see at.spardat.xma.mdl.ISelectable#isSelected(java.lang.String) */ public boolean isSelected(String key) { return selection_.contains(key); } /** * @see at.spardat.xma.mdl.ISelectable#isStrict() */ public boolean isStrict() { return !isOneWay_; } /** * @see at.spardat.xma.mdl.table.ITableWM#selectByModelIndex(int) */ public void selectByModelIndex (int index) { if (index < 0 || index >= size()) return; select (getRow(index).getKey()); } /** * @see at.spardat.xma.mdl.Synchronization#externalize(at.spardat.xma.serializer.XmaOutput, boolean) */ public void externalize (XmaOutput xo, boolean forceFull) throws IOException { // decide on what to write byte what = 0; boolean writeTable = forceFull || table_.changed(); boolean writeSel = forceFull || selection_.changed(); boolean forceFullTable = forceFull; /** * Special treatment of one way tables. A one way table is never synchronized * from client to server. For the other direction applies: If it has been changed * at the server, its row are fully transmitted (no deltas). */ if (isOneWay_) { if (xo.isAtServer()) { if (table_.changed()) forceFullTable = true; } else { // from client to server writeTable = false; } } // externalize if (writeTable) what |= 1; if (writeSel) what |= 2; xo.writeByte("tableOrSel", what); if (writeTable) { // externalize table table_.externalize (xo, forceFullTable); } if (writeSel) { // externalize selection selection_.externalize (xo, forceFull); } } /** * @see at.spardat.xma.mdl.Synchronization#internalize(at.spardat.xma.serializer.XmaInput) */ public void internalize (XmaInput in) throws IOException, ClassNotFoundException { byte what = in.readByte(); boolean tableChanged = ((what & 1) != 0); boolean selChanged = ((what & 2) != 0); if (tableChanged) { // table table_.internalize(in); } if (selChanged) { selection_.internalize(in); } if (tableChanged) handle (new TableRowsChangedEvent ()); if (selChanged || tableChanged) handle (new SelectionChangedEvent (false)); } /** * Returns if this table is one way. * @see #S_ONE_WAY */ public boolean isOneWay() { return isOneWay_; } /** * Returns the number of rows in this table. */ public int size () { return table_.size(); } /** * Adds a row to the table at a provided zero based row index.

* * If the row is not added at the end of the table, runtime order is O(n) * since an array is internally used to store the table. * * @param rowIndex the zero based index of the new row. Must be greater or equal * to zero and not greater than size(). * @param key the key of the new row * @param atoms Atom array * @return true if row has been added or false if this table already contained * a row with the specified key. * @exception IllegalArgumentException if rowIndex is invalid. */ protected boolean add (int rowIndex, String key, Atom[] atoms) { if (key == null) throw new IllegalArgumentException(); boolean success = table_.add(rowIndex, key, atoms); if (success) handle (new RowAddedEvent(rowIndex)); return success; } /** * Returns true if this table contains a row with the provided key. */ public boolean containsKey (String key) { return table_.containsKey(key); } /** * Returns the index at which the row with the provided key is located * or -1 if no row with key is here. This is a time consuming operation * of O(n). * * @param key the key of the row that is looked up. * @return zero based index of the key or -1 if this table does not contain * a row with the provided key. */ public int indexOf (String key) { return table_.indexOf (key); } /** * Returns the table row for a provided key.

* * The returned TableRow may be used to query and modify the row. It * must not be cached outside for later reuse because the returned row * becomes invalid if the table is modified in other ways. * * @param key the key whose row is wanted * @return the TableRow or null, if there is no row with the provided key. */ public TableRow getRow (String key) { Atom [] atoms = table_.getAtoms(key); if (atoms == null) return null; TableRow tr = new TableRow(); tr.atoms_ = atoms; tr.key_ = key; tr.table_ = this; tr.updateCount_ = updateCount_; return tr; } /** * Returns the table row at a provided zero based row index.

* * The returned TableRow may be used to query and modify the row. It * must not be cached outside for later reuse because the returned row * becomes invalid if the table is modified in other ways. * * @param rowIndex the index of the row * @return a TableRow, never null * @exception ArrayIndexOutOfBoundsException if rowIndex invalid */ public TableRow getRow (int rowIndex) { TableRow row = getRow (table_.getKey(rowIndex)); row.index_ = rowIndex; return row; } /** * Updates a row which has been previously read via getRow(). Internal * method to be used from TableRow. * * @param newRow the row that has been read via getRow. */ void updateRow (TableRow newRow) { Atom [] oldAtoms = table_.getAtoms (newRow.key_); if (oldAtoms == null) throw new IllegalArgumentException(); Atom [] newAtoms = newRow.atoms_; // determine how many atoms did change int numChanged = 0; for (int i=0; i<=columnCount_; i++) { if (oldAtoms[i] != newAtoms[i]) numChanged++; } int rowIndex = newRow.index_; if (rowIndex == -1) rowIndex = table_.indexOf(newRow.key_); if (numChanged < (columnCount_+1)/2) { // less than half the atoms changed; we therefore just update the Atoms for (int i=0; i<=columnCount_; i++) { if (oldAtoms[i] != newAtoms[i]) table_.replace(rowIndex, i, newAtoms[i]); } } else { // change the complete row table_.replace(rowIndex, newAtoms); } // fire changed event handle (new RowChangedEvent (rowIndex)); } /** * Removes a row from this table at a provided index. * * @param rowIndex the zero based row index. * @exception IndexOutOfBoundsException if rowIndex invalid. */ public void removeRow (int rowIndex) { String key = table_.getKey(rowIndex); table_.remove(rowIndex); handle (new RowRemovedEvent(rowIndex)); // update the selection state if (selection_.contains(key)) { deselect (key); } } /** * Removes a row with a particular key.

* * Note that this method is of runtime order O(n). If performance matters, * please use removeRow(int) instead. * * @param key the key whose row is to be removed. * @return true if removed, false if this table does not contain a row for the * provided key. */ public boolean removeRow (String key) { int rowIndex = table_.indexOf(key); if (rowIndex == -1) return false; removeRow (rowIndex); return true; } /** * Removes all rows from the table and deselects all rows. */ public void clear () { table_.clear(); selection_.clear(); handle (new TableRowsChangedEvent ()); handle (new SelectionChangedEvent (false)); } /** * Removes only the rows but leave selection unchanged. Internal method for * test purpose. This method is not intended to be called from outside * and result in undefined behaviour!!! */ public void internalRemoveRows () { if (isAtServer_) { table_.clear(); handle (new TableRowsChangedEvent ()); } } /** * @see at.spardat.xma.mdl.util.Descriptive#describe(at.spardat.xma.mdl.util.DNode) */ public void describe (DNode n) { super.describe(n); n.app("oneWay", isOneWay_).comma(); n.app("isMultiSel", isMultiSelect()).comma(); DNode sel = new DNode(n, "selection: "); sel.app(selection_); DNode table = new DNode(n, "tableData: "); table.app(table_); } /** * Make random changes to this */ public void randomlyChange () { if (Assert.ON) { int change = TestUtil.randomInt(0, 7); switch (change) { case 0: // select a random entry if (size() > 0) select(randomKey()); break; case 1: // deselect a random entry if (size() > 0) deselect (randomKey()); break; case 2: // deselect all deselectAll(); break; case 3: // select a key randomly choosen select (TestUtil.randomString(TestUtil.randomInt(0, 3))); break; case 4: // randomly add a row int index = TestUtil.randomInt(0, size()); String key = TestUtil.randomString(3); if (!containsKey(key)) { new TableRow(this, index, key, newRandomObjectArray(), TestUtil.randomInt(0, 1)); } break; case 5: // update a row if (size() > 0) { index = TestUtil.randomInt(0, size()-1); TableRow tr = getRow(index); tr.setCells(newRandomObjectArray()); } break; case 6: // update a cell if (size() > 0) { index = TestUtil.randomInt(0, size()-1); TableRow tr = getRow(index); tr.setCell (TestUtil.randomInt(0, columnCount_-1), TestUtil.randomNullString(0, 5)); } break; case 7: // delete a row if (size() > 0) { index = TestUtil.randomInt(0, size()-1); removeRow(index); } break; } } } // returns a key randomly choosen from the table private String randomKey () { return getRow(TestUtil.randomInt(0, size()-1)).getKey(); } // creates an object array of length columnCount_ and fills it with random data private Object [] newRandomObjectArray () { Object [] oa = new Object[columnCount_]; for (int i=0; iTypes.LAST) throw new IllegalArgumentException("unsupported type: "+type); if(size()>0) throw new IllegalStateException("column types must not be changed after adding data to the table"); columnTypes[column]=type; } // see at.spardat.xma.mdl.table.ITableWM public byte getColumnType(int column) { return columnTypes[column]; } /** * Notification event that the selection state has changed. * * @author YSD, 26.04.2003 10:36:09 */ class SelectionChangedEvent extends Notification { /** * Constructor */ public SelectionChangedEvent (boolean fromUI) { super (TableWM.this, fromUI); } } /** * Notification event sent when a row has been added to the table. * * @author YSD, 27.04.2003 10:31:30 */ class RowAddedEvent extends Notification { /** * Constructor * * @param index the row index where the row has been added */ public RowAddedEvent (int index) { super (TableWM.this, false); index_ = index; } /** * Returns the index where the row has been added. */ public int getIndex () { return index_; } private int index_; } /** * Notification event sent when a row has been removed from the table. * * @author YSD, 27.04.2003 10:31:30 */ class RowRemovedEvent extends Notification { /** * Constructor * * @param index the old row index of the removed row */ public RowRemovedEvent (int index) { super (TableWM.this, false); index_ = index; } /** * Returns the index of the old (removed) row. */ public int getIndex () { return index_; } private int index_; } /** * Notification event sent when the data of a row has been changed. * * @author YSD, 27.04.2003 10:31:30 */ class RowChangedEvent extends Notification { /** * Constructor * * @param index the row */ public RowChangedEvent (int index) { super (TableWM.this, false); index_ = index; } /** * Returns the index of the changed row */ public int getIndex () { return index_; } private int index_; } /** * Notification event sent when one or more rows of the table might * have changed, removed or added without knowning the precise * reason of the change. * * @author YSD, 27.04.2003 15:53:54 */ class TableRowsChangedEvent extends Notification { /** * Constructor * * @param wModel the widget model */ public TableRowsChangedEvent () { super (TableWM.this, false); } } /** * Modificative event that sets an entirely new selection * * @author YSD, 26.04.2003 10:44:29 */ class NewSelectionEvent extends ModelChangeEvent { /** * Constructor * * @param selectedKeys the set of keys that are to be selected * @param fromUI indicates if the events origin is the UI or not */ public NewSelectionEvent (Collection selectedKeys, boolean fromUI) { super (TableWM.this, fromUI); selectedKeys_ = selectedKeys; } /** * Selects the provided keys. Invalid keys (that are not in the table) are ignored. * * @see at.spardat.xma.mdl.ModelChangeEvent#execute() */ public boolean execute() { TableWM tableDirect = (TableWM) wModel_; TransStringSet sel = ((TableWM)wModel_).selection_; TransAtomTable table = ((TableWM)wModel_).table_; sel.clear(); Iterator iter = selectedKeys_.iterator(); while (iter.hasNext()) { String key = (String) iter.next(); if (tableDirect.isOneWay() || table.containsKey(key)) { sel.add(key); } } return true; } private Collection selectedKeys_; } /** * Event class used to notify the dynamic registration of a new TableWM. * @author gub * @since 2.1.0 * @see Page#addWModel(WModel) */ public static class NewTableWMEvent extends NewModelEvent { int columns; int style; byte[] columnTypes; /** empty constructor for deserialization */ public NewTableWMEvent() {} /** * constructor which initializes the modelType * @param columns number of columns this table has * @param style bit or combination of the style constants S_*. */ public NewTableWMEvent(int columns,int style,byte[] columnTypes) { this.columns=columns; this.style=style; this.columnTypes=columnTypes; } // see at.spardat.xma.mdl.NewModelEvent.getType() public byte getType() { return NewModelEventFactory.TableWM; } // see at.spardat.xma.mdl.NewModelEvent.createModel() public WModel createModel(short id,Page page) { TableWM newModel = new TableWM(id,page,columns,style); newModel.columnTypes=columnTypes; return newModel; } // see at.spardat.xma.mdl.NewModelEvent.serialize() public void serialize(XmaOutput out) throws IOException { super.serialize(out); out.writeInt("columns",columns); out.writeInt("style",style); for(int i=0;i





© 2015 - 2024 Weber Informatics LLC | Privacy Policy