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

xdev.vt.VirtualTable Maven / Gradle / Ivy

/*
 * XDEV Application Framework - XDEV Application Framework
 * Copyright © 2003 XDEV Software (https://xdev.software)
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 program.  If not, see .
 */
package xdev.vt;


import java.beans.Beans;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.swing.JFileChooser;
import javax.swing.JTable;
import javax.swing.event.EventListenerList;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

import xdev.db.ColumnMetaData;
import xdev.db.DBConnection;
import xdev.db.DBDataSource;
import xdev.db.DBException;
import xdev.db.DBMetaData.TableInfo;
import xdev.db.DBMetaData.TableMetaData;
import xdev.db.DBMetaData.TableType;
import xdev.db.DBUtils;
import xdev.db.DataType;
import xdev.db.Index;
import xdev.db.Index.IndexType;
import xdev.db.JoinType;
import xdev.db.NullDBDataSource;
import xdev.db.QueryInfo;
import xdev.db.Result;
import xdev.db.Transaction;
import xdev.db.WriteRequest;
import xdev.db.WriteResult;
import xdev.db.locking.LockFactory;
import xdev.db.locking.LockingException;
import xdev.db.locking.PessimisticLock;
import xdev.db.sql.DELETE;
import xdev.db.sql.INSERT;
import xdev.db.sql.SELECT;
import xdev.db.sql.Table;
import xdev.db.sql.UPDATE;
import xdev.db.sql.WHERE;
import xdev.db.sql.WritingQuery;
import xdev.io.ByteHolder;
import xdev.io.CharHolder;
import xdev.io.XdevObjectInputStream;
import xdev.io.XdevObjectOutputStream;
import xdev.io.csv.CSVParser;
import xdev.io.csv.CSVReader;
import xdev.io.csv.CSVWriter;
import xdev.lang.Copyable;
import xdev.lang.XDEV;
import xdev.ui.Formular;
import xdev.ui.ItemList;
import xdev.ui.UIUtils;
import xdev.ui.XdevTable;
import xdev.ui.XdevTree;
import xdev.ui.text.TextFormat;
import xdev.ui.tree.DefaultXdevTreeManager;
import xdev.ui.tree.XdevTreeModel;
import xdev.ui.tree.XdevTreeNode;
import xdev.ui.tree.XdevTreeRenderer;
import xdev.util.ArrayUtils;
import xdev.util.ConversionUtils;
import xdev.util.DataSource;
import xdev.util.IntList;
import xdev.util.MathUtils;
import xdev.util.ObjectConversionException;
import xdev.util.Settings;
import xdev.util.StringUtils;
import xdev.util.StringUtils.ParameterProvider;
import xdev.util.XdevList;
import xdev.util.logging.LoggerFactory;
import xdev.util.logging.XdevLogger;
import xdev.vt.VirtualTableEvent.Type;


/**
 * 

* A Virtual Table is the core element of the data layer of the XDEV Application * Framework and the connector of the presentation layer (GUI) and a * {@link DataSource}. *

*

* In most cases a Virtual Table has a similiar counterpart in a * {@link DataSource}, normally a table in a database which it can be * synchronized with. *

*

* The Virtual Table stores data in a relational way, organized in * {@link VirtualTableColumn}s and {@link VirtualTableRow}s. *

*

* Several connectors to gui beans are provided by default, e.g. * {@link VirtualTableModel}. All updates in the VirtualTable are automatically * reflected in the gui model and vice versa. To connect a Virtual Table with a * gui bean use the bean's setModel(...) methods, e.g. * {@link XdevTable#setModel(VirtualTable)}. *

*

* There are several ways to get data into a Virtual Table:
* - use one of the {@link #queryAndFill()} methods,
* - via the {@link XDEV#Query(xdev.lang.cmd.Query)} wizard of the XDEV IDE,
* - call the e.g. {@link XdevTable#setModel(VirtualTable, String, boolean)} * with the queryData parameter set to true
* - and many more ... *

*

* If you want to override the default {@link SELECT} statement which is used to * fill this Virtual Table ({@link #getSelect()}), use * {@link #setSelect(SELECT)}. *

*

* If you want to provide your own validation of inserted values, see * {@link VirtualTableColumn#addValidator(Validator)}. *

*

* To synchronize the Virtual Table with the underlying data source either
* - call the data manipulation methods, e.g {@link #addRow(List, boolean)} with * the synchronizedDB parameter set to true
* - or invoke {@link #synchronizeChangedRows()}. *

*

* Virtual Tables can reflect an entire database. Therefor the tables can be * connected via {@link EntityRelationshipModel}s. *

* * @see VirtualTableColumn * @see VirtualTableRow * @see KeyValues * @see EntityRelationships * @see DataSouce * @see DBDataSource * @see Table * * @author XDEV Software */ public class VirtualTable extends Table implements Serializable, Iterable, Copyable { private static final long serialVersionUID = -8132819103802989330L; /** * Logger instance for this class. */ private static final XdevLogger log = LoggerFactory .getLogger(VirtualTable.class); /** * the default Character set used within Virtual Tables. */ public final static String CHARSET = "UTF-8"; /** * the default DateFormat used within Virtual Tables. */ public final static String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; private final static String FILE_HEADER = "%CX VIRTUAL TABLE%"; private String name; private String dbSchema; private String dbAlias; private transient DBDataSource dataSource; private VirtualTableColumn primaryColumn; private int cursorpos = -1; private VirtualTableData data; private final static int FLAG_UNIQUE = 0x0001; // columns + info protected VirtualTableColumn[] columns; // private long[] maxVal; private int[] columnFlags; private final List indices = new ArrayList( 3); // behaviour configuration private boolean checkUniqueIndexDoubleValues = true; /** * @since 4.1 */ private boolean allowMultipleNullsInUniqueIndices = false; /** * @since 3.1 */ private boolean allowMissingFillColumns = true; /** * @since 3.1 */ private boolean allowSuperfluousFillColumns = true; /** * @since 4.0 */ private SELECT userDefinedSelect; // caches private final Map> columnToIndex = new Hashtable(); private final Map columnNameToIndex = new Hashtable(); private final Map columnSimpleNameToIndex = new Hashtable(); private String[] autoValueColumns = null; private transient QueryInfo lastQuery; private transient int[] lastQueryIndices; private final Collection removedRowKeys = new HashSet(); private transient EventListenerList listenerList = new EventListenerList(); // Serialization stuff private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.listenerList = new EventListenerList(); } /** * Constructor for creating a new instance of a {@link VirtualTable}. * * @param rs * a {@link Result} containing the data to be wrapped by this * {@link VirtualTable} */ public VirtualTable(final Result rs) { this(rs,false); } /** * Constructor for creating a new instance of a {@link VirtualTable}. * * @param rs * a {@link Result} containing the data to be wrapped by this * {@link VirtualTable} * @param withData * boolean flag, indicating if the provided Result contains data */ public VirtualTable(final Result rs, final boolean withData) { super(""); this.name = ""; this.data = new VirtualTableData(withData ? 100 : 10); final int cols = rs.getColumnCount(); // maxVal = new long[cols]; this.columns = new VirtualTableColumn[cols]; this.columnFlags = new int[cols]; final int[] columnIndices = new int[cols]; for(int i = 0; i < cols; i++) { final ColumnMetaData meta = rs.getMetadata(i); this.columns[i] = new VirtualTableColumn(meta); this.columns[i].setNullable(true); if(i == 0) { this.name = meta.getTable(); if(this.name == null) { this.name = ""; } } else if(this.name.length() > 0 && !this.name.equals(meta.getTable())) { this.name = ""; } // maxVal[i] = 0l; this.columnFlags[i] = 0; columnIndices[i] = i; } this.dbAlias = this.name; connectWithColumns(); this.primaryColumn = this.columns[0]; if(withData) { try { addData(rs,columnIndices); } catch(final Exception e) { throw new VirtualTableException(this,e); } } } /** * Constructor for creating a new instance of a {@link VirtualTable}. * * @param name * {@link String} containing the name of this Virtual Table. * @param dbAlias * {@link String} containing the dbAlias * @param columns * a number of {@link VirtualTableColumn} instances containing * all columns for this Virtual Table */ public VirtualTable(final String name, final String dbAlias, final VirtualTableColumn... columns) { this(name,null,dbAlias,columns); } /** * Constructor for creating a new instance of a {@link VirtualTable}. * * @param name * {@link String} containing the name of this Virtual Table. * @param dbSchema * {@link String} containing the dbSchema, may be * null * @param dbAlias * {@link String} containing the dbAlias * @param columns * a number of {@link VirtualTableColumn} instances containing * all columns for this Virtual Table */ public VirtualTable(final String name, final String dbSchema, final String dbAlias, final VirtualTableColumn... columns) { this(name,dbSchema,dbAlias,columns,columns[0],null); } /** * Constructor for creating a new instance of a {@link VirtualTable}. * * @param name * {@link String} containing the name of this Virtual Table. * @param dbAlias * {@link String} containing the dbAlias * @param columns * an array of {@link VirtualTableColumn} containing all columns * for this Virtual Table. * @param primaryColumn * {@link VirtualTableColumn} containing the primary (key) column * for this Virtual Table * @param data * a two-dimensional array of {@link Object} instances containing * the data for this Virtual Table */ public VirtualTable(final String name, final String dbAlias, final VirtualTableColumn[] columns, final VirtualTableColumn primaryColumn, final Object[][] data) { this(name,null,dbAlias,columns,primaryColumn,data); } /** * Constructor for creating a new instance of a {@link VirtualTable}. * * @param name * {@link String} containing the name of this Virtual Table. * @param dbSchema * {@link String} containing the dbSchema, may be * null * @param dbAlias * {@link String} containing the dbAlias * @param columns * an array of {@link VirtualTableColumn} containing all columns * for this Virtual Table. * @param primaryColumn * {@link VirtualTableColumn} containing the primary (key) column * for this Virtual Table * @param data * a two-dimensional array of {@link Object} instances containing * the data for this Virtual Table */ public VirtualTable(final String name, final String dbSchema, final String dbAlias, final VirtualTableColumn[] columns, final VirtualTableColumn primaryColumn, final Object[][] data) { super(dbSchema != null && dbSchema.length() > 0 ? dbSchema : null,dbAlias,null); this.name = name; this.dbSchema = "".equals(dbSchema) ? null : dbSchema; this.dbAlias = dbAlias; this.columns = columns; connectWithColumns(); final int cols = columns.length; // this.maxVal = new long[cols]; this.columnFlags = new int[cols]; for(int i = 0; i < cols; i++) { // this.maxVal[i] = 0l; this.columnFlags[i] = 0; } this.primaryColumn = primaryColumn != null ? primaryColumn : columns[0]; if(data != null) { this.data = new VirtualTableData(data.length); for(int r = 0; r < data.length; r++) { final VirtualTableRow row = new VirtualTableRow(cols); for(int c = 0; c < cols; c++) { row.set(c,data[r][c],false,false); } this.data.add(row,false); } } else { this.data = new VirtualTableData(10); } } /** * This method returns a copy of this {@link VirtualTable} instance without * the data. * * @return a copied {@link VirtualTable} of this instance */ public VirtualTable copyHeader() { return clone(false); } /** * This method returns a copy of this {@link VirtualTable} instance. The * data of the table won't be copied * * @return a copied {@link VirtualTable} of this instance */ @Override public VirtualTable clone() { return clone(false); } /** * This method returns a copy of this {@link VirtualTable} instance. * * @param withData * boolean flag, indicating if data should be copied, too. * @return a copied {@link VirtualTable} of this instance */ public VirtualTable clone(final boolean withData) { try { final VirtualTable vt = getClass().newInstance(); vt.takeValues(this,withData); // reconnect static columns (see issue 11929) connectWithColumns(); return vt; } catch(final Exception e) { // shouldn't happen log.error(e); throw new RuntimeException(e); } } /* * Used by #clone */ private VirtualTable() { super(""); } private void takeValues(final VirtualTable vt, final boolean withData) { this.name = vt.name; this.dataSource = vt.dataSource; this.dbSchema = vt.dbSchema; this.dbAlias = vt.dbAlias; this.lastQuery = vt.lastQuery; this.columns = new VirtualTableColumn[vt.columns.length]; for(int i = 0; i < vt.columns.length; i++) { this.columns[i] = vt.columns[i].clone(); } connectWithColumns(); this.primaryColumn = this.columns[vt.getColumnIndex(vt.primaryColumn)]; // maxVal = new long[columns.length]; this.columnFlags = new int[this.columns.length]; for(int i = 0; i < this.columns.length; i++) { // maxVal[i] = 0l; this.columnFlags[0] = 0; } this.indices.clear(); for(final VirtualTableIndex vtIndex : vt.indices) { addIndex(vtIndex._originalIndex); } this.checkUniqueIndexDoubleValues = vt.checkUniqueIndexDoubleValues; this.allowMultipleNullsInUniqueIndices = vt.allowMultipleNullsInUniqueIndices; this.allowMissingFillColumns = vt.allowMissingFillColumns; this.allowSuperfluousFillColumns = vt.allowSuperfluousFillColumns; if(vt.userDefinedSelect != null) { this.userDefinedSelect = vt.userDefinedSelect.clone(); } if(withData) { final int rc = vt.getRowCount(); this.data = new VirtualTableData(rc); for(int i = 0; i < rc; i++) { this.data.add(new VirtualTableRow(vt.getRow(i)),false); } } else { this.data = new VirtualTableData(10); } } void connectWithColumns() { _updateDelegate(this.dbSchema != null && this.dbSchema.length() > 0 ? this.dbSchema : null, this.dbAlias,null); for(final VirtualTableColumn column : this.columns) { column.setVirtualTable(this); } } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { if(obj == this) { return true; } if(obj instanceof VirtualTable) { return _getIdentifier().equals(((VirtualTable)obj)._getIdentifier()); } return false; } /** * used by {@link #equals(Object)} * * @since 3.2 */ private String _getIdentifier() { String name = getName(); if(name == null || name.length() == 0) { name = getClass().getName(); } return name; } /** * Adds an {@link Index} to this {@link VirtualTable}. * * @param index * the {@link Index} to be added. * @throws NullPointerException * if the Index contains columns not found in this Virtual Table */ public void addIndex(final Index index) throws NullPointerException { final VirtualTableIndex vtIndex = new VirtualTableIndex(index); this.indices.add(vtIndex); for(final String columnName : index.getColumns()) { final int colIndex = getColumnIndex(columnName); if(colIndex == -1) { throw new NullPointerException("Column '" + columnName + "' not found"); } List columnIndices = this.columnToIndex.get(colIndex); if(columnIndices == null) { columnIndices = new ArrayList(2); this.columnToIndex.put(colIndex,columnIndices); } columnIndices.add(vtIndex); if(index.isUnique()) { this.columnFlags[colIndex] |= FLAG_UNIQUE; } } } /** * Returns true if all elements of this {@link VirtualTableColumn} are * unique. * * @param column * the {@link VirtualTableColumn} to be checked. * @return true, if unique */ public boolean isUnique(final VirtualTableColumn column) { return (this.columnFlags[getColumnIndex(column)] & FLAG_UNIQUE) == FLAG_UNIQUE; } void checkColumnDefaults() { for(int i = 0; i < this.columns.length; i++) { final Object def = this.columns[i].getDefaultValue(); this.columns[i].setDefaultValue(checkValue(i,def)); } } /** * Adds a {@link VirtualTableListener} to this {@link VirtualTable}. *
    *
  • a row is inserted, updated or deleted
  • *
  • the data of the Virtual Table changes
  • *
  • a structural change of the Virtual Table happens, e.g. a column gets * removed
  • *
* * @param l * {@link VirtualTableListener} to be added */ public void addVirtualTableListener(final VirtualTableListener l) { this.listenerList.add(VirtualTableListener.class,l); } /** * Removes a {@link VirtualTableListener} from this {@link VirtualTable}. * The listener gets notified, whenever: *
    *
  • a row is inserted, updated or deleted
  • *
  • the data of the Virtual Table changes
  • *
  • a structural change of the Virtual Table happens, e.g. a column gets * removed
  • *
* * @param l * {@link VirtualTableListener} to be removed */ public void removeVirtualTableListener(final VirtualTableListener l) { this.listenerList.remove(VirtualTableListener.class,l); } /** * This method notifies all registered listeners, whenever a row gets * deleted. * * @param row * the {@link VirtualTableRow} to be deleted, * @param rowIndex * the index of the row to be deleted within the Virtual Table. */ protected void fireRowDeleted(final VirtualTableRow row, final int rowIndex) { this.cursorpos = -1; final VirtualTableListener[] listeners = this.listenerList .getListeners(VirtualTableListener.class); if(listeners != null && listeners.length > 0) { final VirtualTableEvent event = new VirtualTableEvent(this,Type.REMOVED,row,rowIndex); for(final VirtualTableListener listener : listeners) { listener.virtualTableRowDeleted(event); } } } /** * This method notifies all registered listeners, whenever a row gets * inserted. * * @param row * the {@link VirtualTableRow} to be deleted, * @param rowIndex * the index where the row gets inserted to the Virtual Table. */ protected void fireRowInserted(final VirtualTableRow row, final int rowIndex) { this.cursorpos = -1; final VirtualTableListener[] listeners = this.listenerList .getListeners(VirtualTableListener.class); if(listeners != null && listeners.length > 0) { final VirtualTableEvent event = new VirtualTableEvent(this,Type.INSERTED,row,rowIndex); for(final VirtualTableListener listener : listeners) { listener.virtualTableRowInserted(event); } } } /** * This method notifies all registered listeners, whenever a row gets * updated. * * @param row * the {@link VirtualTableRow} to be updated, * @param rowIndex * the index of the row to be updated within the Virtual Table. */ protected void fireRowUpdated(final VirtualTableRow row, final int rowIndex) { final VirtualTableListener[] listeners = this.listenerList .getListeners(VirtualTableListener.class); if(listeners != null && listeners.length > 0) { final VirtualTableEvent event = new VirtualTableEvent(this,Type.UPDATED,row,rowIndex); for(final VirtualTableListener listener : listeners) { listener.virtualTableRowUpdated(event); } } } /** * This method notifies all registered listeners, whenever a cell gets * updated. * * @param row * the {@link VirtualTableRow} to be updated, * @param rowIndex * the index of the row to be updated within the Virtual Table. * @param columnIndex * the index of the column to be updated within the Virtual * Table. * @since 3.2 */ protected void fireCellUpdated(final VirtualTableRow row, final int rowIndex, final int columnIndex) { final VirtualTableListener[] listeners = this.listenerList .getListeners(VirtualTableListener.class); if(listeners != null && listeners.length > 0) { final VirtualTableEvent event = new VirtualTableEvent(this,Type.UPDATED,row,rowIndex, columnIndex); for(final VirtualTableListener listener : listeners) { listener.virtualTableRowUpdated(event); } } } /** * This method notifies all registered listeners, whenever the data of this * Virtual Table changes. */ protected void fireDataChanged() { this.cursorpos = -1; final VirtualTableListener[] listeners = this.listenerList .getListeners(VirtualTableListener.class); if(listeners != null && listeners.length > 0) { final VirtualTableEvent event = new VirtualTableEvent(this); for(final VirtualTableListener listener : listeners) { listener.virtualTableDataChanged(event); } } } /** * This method notifies all registered listeners, whenever the structure of * this Virtual Table is modified. One example for such a modification is * the removal of a column. */ protected void fireStructureChanged() { this.cursorpos = -1; final VirtualTableListener[] listeners = this.listenerList .getListeners(VirtualTableListener.class); if(listeners != null && listeners.length > 0) { final VirtualTableEvent event = new VirtualTableEvent(this); for(final VirtualTableListener listener : listeners) { listener.virtualTableStructureChanged(event); } } } /** * Returns the name of this {@link VirtualTable}. * * @return {@link String} containing the name */ public String getName() { return this.name; } /** * Sets an explicit {@link DataSource} for this VirtualTable. * * @param dataSource * the new data source, or null if the application's * default data source should be used */ public void setDataSource(final DBDataSource dataSource) { this.dataSource = dataSource; } /** * Returns true if {@link #setDataSource(DBDataSource)} has * been invoked with a non-null value., false otherwise. * * @return true if {@link #setDataSource(DBDataSource)} has * been invoked with a non-null value. */ public boolean isDataSourceSet() { return this.dataSource != null; } /** * Returns the {@link DBDataSource} of this {@link VirtualTable}. If this * {@link VirtualTable} has no DBDataSource, the current * DBDataSource will be returned. * * @return a {@link DBDataSource} * @throws DBException */ public DBDataSource getDataSource() throws DBException { if(Beans.isDesignTime()) { return NullDBDataSource.instance; } if(this.dataSource != null) { return this.dataSource; } return DBUtils.getCurrentDataSource(); } /** * Returns the database schema of this {@link VirtualTable}, may be * null. * * @return the database schema of this {@link VirtualTable}. */ public String getDatabaseSchema() { return this.dbSchema; } /** * Returns the database alias of this {@link VirtualTable}. * * @return the database alias of this {@link VirtualTable}. */ public String getDatabaseAlias() { return this.dbAlias; } /** * Returns the primary column of this {@link VirtualTable}. * * @return the {@link VirtualTableColumn} instance of the primary column. * May be null. */ public VirtualTableColumn getPrimaryColumn() { return this.primaryColumn; } /** * Sets the primary column of this {@link VirtualTable}. The previous value * get overwritten. * * @param primaryColumn * a {@link VirtualTableColumn} instance for the new primary * column * @throws IllegalArgumentException * if the primary column is not contained in this Virtual Table. */ public void setPrimaryColumn(final VirtualTableColumn primaryColumn) throws IllegalArgumentException { if(!ArrayUtils.contains(this.columns,primaryColumn)) { throw new IllegalArgumentException("Not a column of this VirtualTable"); } this.primaryColumn = primaryColumn; } /** * Returns the number of columns. * * @return the number of columns as int value. */ public int getColumnCount() { return this.columns.length; } /** * Returns the {@link VirtualTableColumn} with the specified name. * * @param name * as a {@link String} * @return the {@link VirtualTableColumn} for the specified name, or null if * no column is found. */ public VirtualTableColumn getColumn(final String name) { final int i = getColumnIndex(name); if(i == -1) { return null; } return this.columns[i]; } /** * Returns the {@link VirtualTableColumn}s with the specified names. * * @param names * as a {@link String} array * @return the {@link VirtualTableColumn}s for the specified names, if a * column couldn't be found, the array's element if * null * @since 3.2 */ public VirtualTableColumn[] getColumns(final String[] names) { final int c = names.length; final VirtualTableColumn[] columns = new VirtualTableColumn[c]; for(int i = 0; i < c; i++) { columns[i] = getColumn(names[i]); } return columns; } /** * Returns the index of the column with the specified name. * * @param name * as a {@link String} * @return the index for the specified column as int value, -1 is returned * if no column is found. */ public int getColumnIndex(String name) { name = name.toUpperCase(); Integer index = this.columnNameToIndex.get(name); if(index != null) { return index; } int i; for(i = 0; i < this.columns.length; i++) { if(this.columns[i].getName().equalsIgnoreCase(name)) { this.columnNameToIndex.put(name,i); return i; } } i = name.lastIndexOf('.'); if(i >= 0) { name = name.substring(i + 1); index = this.columnSimpleNameToIndex.get(name); if(index != null) { return index; } for(i = 0; i < this.columns.length; i++) { if(this.columns[i].getName().equalsIgnoreCase(name)) { this.columnSimpleNameToIndex.put(name,i); return i; } } } return -1; } /** * Returns a {@link List} of {@link String} objects with the column names of * this {@link VirtualTable}. * * @param exceptCols * indices of columns, not to be included within the list of * columns. * @return a {@link List} with all column names, except the ones specified * by param exceptCols. */ public List getColumnNames(final int... exceptCols) { final int c = this.columns.length; final List list = new ArrayList(c); for(int i = 0; i < c; i++) { if(exceptCols == null || !ArrayUtils.contains(exceptCols,i)) { list.add(this.columns[i].getName()); } } return list; } /** * Returns the name of the column with the specified index. The first column * has the index 0. *

* Example: * *

	 * ExampleVT.getColumnName(3);
	 * 
* *

* * @param col * the index of the column, which name is to be returned * @return the name of the column at the specified index * @see #getCaptions() * @see #getColumnIndex(String) * @see #getRowCount() */ public String getColumnName(final int col) { return this.columns[col].getName(); } /** * Returns the caption of the column with the specified index. The first * column has index 0. If the caption is empty, the name of the column will * be returned. * * @param col * the index of the column, which name is to be returned * @return the caption of the column at the specified index */ public String getColumnCaption(final int col) { final String caption = this.columns[col].getCaption(); if(caption != null && caption.length() > 0) { return caption; } return this.columns[col].getName(); } /** * Returns a array of {@link String} objects with all captions. * * @return all captions as String array. */ public String[] getColumnCaptions() { final int len = this.columns.length; final String[] s = new String[len]; for(int col = 0; col < len; col++) { s[col] = getColumnCaption(col); } return s; } /** * Return the index for the specified {@link VirtualTableColumn}. * * @param col * the {@link VirtualTableColumn}, whose index is to be returned * @return the index for the specified column. -1 is returned if * VirtualTableColumn is not found. */ public int getColumnIndex(final VirtualTableColumn col) { for(int i = 0; i < this.columns.length; i++) { if(this.columns[i].equals(col)) { return i; } } return -1; } /** * Return the indices for the specified {@link VirtualTableColumn}. * * @param columns * the {@link VirtualTableColumn}s, whose index is to be returned * @return the indices for the specified columns. */ public int[] getColumnIndices(final VirtualTableColumn... columns) { final int[] indices = new int[columns.length]; for(int i = 0; i < columns.length; i++) { indices[i] = getColumnIndex(columns[i]); } return indices; } /** * Returns the {@link VirtualTableColumn} at the specified index position. * * @param index * the index of the {@link VirtualTableColumn} to be returned * @return the {@link VirtualTableColumn} at the specified index * @throws ArrayIndexOutOfBoundsException * if index < -1 or * index > getColumnCount-1 */ public VirtualTableColumn getColumnAt(final int index) throws ArrayIndexOutOfBoundsException { return this.columns[index]; } /** * Returns the {@link VirtualTableColumn}s at the specified positions. * * @param indices * the indices of the {@link VirtualTableColumn}s to be returned * @return the {@link VirtualTableColumn}s at the specified indices * @throws ArrayIndexOutOfBoundsException * if indices[i] < -1 or * indices[i] > getColumnCount-1 */ public VirtualTableColumn[] getColumnsAt(final int[] indices) throws ArrayIndexOutOfBoundsException { final VirtualTableColumn[] columns = new VirtualTableColumn[indices.length]; for(int i = 0; i < indices.length; i++) { columns[i] = this.columns[indices[i]]; } return columns; } /** * Returns an iterator over all columns of this VirtualTable in their * natural order. *

* Note: The returned iterator doesn't support {@link Iterator#remove()}. *

* * * @return An interator over the columns */ @Override public Iterator iterator() { return ArrayUtils.getIterator(this.columns); } /** * Returns an iterator over all columns of this VirtualTable in their * natural order. *

* Note: The returned iterator doesn't support {@link Iterator#remove()}. *

* * * @return An interator over the columns */ public Iterable columns() { return this; } /** * Returns an iterator over all rows of this VirtualTable in their natural * order. *

* Note: The returned iterator doesn't support {@link Iterator#remove()}. *

* * * @return An interator over the rows */ public Iterable rows() { return this.data; } /** * Returns the number of rows for this {@link VirtualTable}. * * @return the number of rows */ public int getRowCount() { return this.data.size(); } /** * Returns the value of the cell specified by column name and the row of the * current cursor position. * * @param columnName * the name of the column * @return the value of the cell at the specified position. */ public Object getValueAt(final String columnName) { return getValueAt(getColumnIndex(columnName)); } /** * Returns the value of the cell specified by column index and the row of * the current cursor position. * * @param col * the index of the column (0-based). * @return the value of the cell at the specified position. */ public Object getValueAt(final int col) { return getValueAt(this.cursorpos,col); } /** * Returns the value of the cell specified by the row index and column * index. *

* Example: * *

	 * ExampleVT.addRow();
	 * ExampleVT.getValueAt(0,2);
	 * 
* *

* * @param row * the index of the row (0-based) * @param col * the index of the column (0-based) * @return the value of the cell at the specified position */ public Object getValueAt(final int row, final int col) { return this.data.get(row).get(col); } /** * Returns the value of the cell specified by column name and the row of the * current cursor position. * * @param row * the index of the row (0-based) * @param columnName * the name of the column * @return the value of the cell at the specified position. */ public Object getValueAt(final int row, final String columnName) { final int col = getColumnIndex(columnName); if(col == -1) { throw new IllegalArgumentException("Column '" + columnName + "' not found"); } return getValueAt(row,col); } /** * Returns the value of the cell specified by a {@link VirtualTableColumn} * and the row of the current cursor position. * * @param row * the index of the row (0-based) * @param column * {@link VirtualTableColumn} * * @return the value of the cell at the specified position. * * @throws IllegalArgumentException * if column is not not within this Virtual Table */ public T getValueAt(final int row, final VirtualTableColumn column) throws IllegalArgumentException { final int columnIndex = ArrayUtils.indexOf(this.columns,column); if(columnIndex == -1) { throw new IllegalArgumentException("Column doesn't belong to this VirtualTable"); } return (T)getValueAt(row,columnIndex); } /** * Returns a formatted representation for the value at the cell position * specified by row and col. The {@link TextFormat} of the * {@link VirtualTableColumn} at index col is used to format the value. * * @param row * the index of the row (0-based) * @param col * the index of the column (0-based) * @return a formatted {@link String} representation of the specified value. */ public String getFormattedValueAt(final int row, final int col) { return formatValue(getValueAt(row,col),col); } /** * Returns a formatted string, with the {@link VirtualTableRow} at * row used as ParameterProvider. *

* This is a shortcut for getRow(row).format(str). * * @param row * the row index * @param str * the {@link String} to format * @return a formatted {@link String} * @see VirtualTableRow#format(String) */ public String format(final int row, final String str) { return getRow(row).format(str); } /** * Returns a formatted representation for the value at the cell position * specified by row and column name. The {@link TextFormat} of the * {@link VirtualTableColumn} with name columnName is used to format the * value. * * @param row * the index of the row (0-based) * @param columnName * the name of the column * @return a formatted {@link String} representation of the specified value. */ public String getFormattedValueAt(final int row, final String columnName) { final int col = getColumnIndex(columnName); if(col == -1) { return ""; } return getFormattedValueAt(row,col); } /** * Returns a formatted representation for the value at the cell position * specified by row and a {@link VirtualTableColumn}. The {@link TextFormat} * of the {@link VirtualTableColumn} is used to format the value. * * @param row * the index of the row (0-based) * @param column * the {@link VirtualTableColumn} * @return a formatted {@link String} representation of the specified value. */ public String getFormattedValueAt(final int row, final VirtualTableColumn column) { final int columnIndex = ArrayUtils.indexOf(this.columns,column); if(columnIndex == -1) { throw new IllegalArgumentException("Column doesn't belong to this VirtualTable"); } return getFormattedValueAt(row,columnIndex); } /** * Returns a formatted representation of the specified value using the * {@link TextFormat} of the {@link VirtualTableColumn} specified by index * col. * * @param value * the value to be formatted * @param col * the index of the {@link VirtualTableColumn} * @return a formatted {@link String} representation of the specified value. */ public String formatValue(final Object value, final int col) { if(value == null) { return ""; } return this.columns[col].getTextFormat().format(value); } /** * Returns a formatted representation of the specified value using the * {@link TextFormat} of the {@link VirtualTableColumn} specified by * columnName. * * @param value * the value to be formatted * @param columnName * the name of the {@link VirtualTableColumn} * @return a formatted {@link String} representation of the specified value. * @since 3.2 */ public String formatValue(final Object value, final String columnName) { if(value == null) { return ""; } return getColumn(columnName).getTextFormat().format(value); } /** * Sets the value of a cell at the position specified by the current cursor * position and the column name. * * @param val * the value to be set * @param columnName * the name of the column * @return the new value of the cell * @throws VirtualTableException * if the value is not appropriate for the specified column * @throws IllegalArgumentException * if the column columnName does not exist in this * {@link VirtualTable} */ public Object setValueAt(final Object val, final String columnName) throws VirtualTableException, IllegalArgumentException { return setValueAt(val,this.cursorpos,columnName); } /** * Sets the value of a cell at the position specified by the row and the * column name. * * @param val * the value to be set * @param row * the index of the row (0-based) * @param columnName * the name of the column * @return the new value of the the cell * @throws VirtualTableException * if the value is not appropriate for the specified column * @throws IllegalArgumentException * if the column columnName does not exist in this * {@link VirtualTable} */ public Object setValueAt(final Object val, final int row, final String columnName) throws VirtualTableException, IllegalArgumentException { final int col = getColumnIndex(columnName); if(col == -1) { throw new IllegalArgumentException("column '" + columnName + "' not found"); } return setValueAt(val,row,col); } /** * Sets the value of a cell at the position specified by the current cursor * position and the column index. * * @param val * the value to be set * @param col * the index of the column (0-based) * @return the new value of the cell * @throws VirtualTableException * if the value is not appropriate for the specified column */ public Object setValueAt(final Object val, final int col) throws VirtualTableException { return setValueAt(val,this.cursorpos,col); } /** * Sets the value of a cell at the position specified by the row index and * the column index. * * @param val * the value to be set * @param row * the index of the row (0-based). * @param col * the index of the column (0-based). * * @return the new value of the cell * * @throws VirtualTableException * if the value is not appropriate for the specified column */ public Object setValueAt(final Object val, final int row, final int col) throws VirtualTableException { return setValueAt(val,row,col,false); } /** * Sets the value of a cell at the position specified by the row index and * the column index. * * @param val * the value to be set * @param row * the index of the row (0-based). * @param col * the index of the column (0-based). * @param synchronizeDB * boolean, if set to true, value will be written to the * underlying data source * * @return the new value of the cell * * @throws VirtualTableException * if synchronizeDB is true and no * primary key is specified for this Virtual Table, or if the * value is not appropriate for the specified column */ public Object setValueAt(final Object val, final int row, final int col, final boolean synchronizeDB) throws VirtualTableException { return setValueAt(val,row,col,synchronizeDB,true); } /** * Sets the value of a cell at the position specified by the row index and * the column index. * * @param val * the value to be set * * @param row * the index of the row (0-based). * * @param col * the index of the column (0-based). * * @param synchronizeDB * boolean, if set to true, value will be written to the * underlying data source * * @param markAsUpdated * boolean, if set to true, the row containing the cell will be * marked as changed. * * @return the new value of the cell * * @throws VirtualTableException * if synchronizeDB is true and no * primary key is specified for this Virtual Table, or if the * value is not appropriate for the specified column */ public Object setValueAt(final Object val, final int row, final int col, final boolean synchronizeDB, final boolean markAsUpdated) throws VirtualTableException { return setValueAt(val,row,col,synchronizeDB,markAsUpdated,true); } /** * Sets the value of a cell at the position specified by the row index and * the column index. * * @param val * the value to be set * * @param row * the index of the row (0-based). * * @param col * the index of the column (0-based). * * @param synchronizeDB * boolean, if set to true, value will be written to the * underlying data source * * @param markAsUpdated * boolean, if set to true, the row containing the cell will be * marked as changed. * * @param checkUnique * boolean, if set to true, the value will checked for * uniqueness. If the value ist not unique a * UniqueIndexDoubleValuesException is thrown. * * @return the new value of the cell * * @throws VirtualTableException * if synchronizeDB is true and no * primary key is specified for this Virtual Table, or if the * value is not appropriate for the specified column */ public Object setValueAt(Object val, final int row, final int col, final boolean synchronizeDB, final boolean markAsUpdated, final boolean checkUnique) throws VirtualTableException { final Object old = getValueAt(row,col); if(equals(val,old,getColumnAt(col).getType())) { return old; } val = checkValue(col,val); final VirtualTableRow vtRow = this.data.get(row); vtRow.set(col,val,markAsUpdated,checkUnique); if(markAsUpdated) { fireCellUpdated(vtRow,row,col); } if(synchronizeDB) { final VirtualTableColumn[] pk = getPrimaryKeyColumns(); if(pk.length > 0) { final UPDATE update = new UPDATE(this); final List values = new ArrayList(pk.length); update.SET(this.columns[col],"?"); values.add(val); final WHERE where = new WHERE(); for(int i = 0; i < pk.length; i++) { where.and(pk[i].toSqlField().eq("?")); values.add(getValueAt(row,getColumnIndex(pk[i]))); } update.WHERE(where); final Object[] params = values.toArray(new Object[values.size()]); try { writeDB(null,update,false,params); } catch(final DBException e) { throw new VirtualTableException(this,e); } } else { throw new VirtualTableException(this,"No primary key specified"); } } return val; } /** * Clears the data of this {@link VirtualTable}. *

* This method is an alias for {@link #clearData()}. *

*/ public void clear() { clearData(); } /** * Clears the data of this {@link VirtualTable}. */ public void clearData() { this.data.clear(); // for(int i = 0; i < maxVal.length; i++) // { // maxVal[i] = 0l; // } for(final VirtualTableIndex index : this.indices) { index.clear(); } this.removedRowKeys.clear(); fireDataChanged(); } /** * Returns the preferred column width within a JTable for a specified * column. If the column has a preferred width set, this width gets used. * Otherwise the maximum of the two following values gets returned: *

    *
  • a default value depending on the type of the specified column
  • *
  • the width of the column of the specified JTable
  • *
* * @param col * the index of the column (0-based) * @param header * {@link JTableHeader} instance of the JTable * * @param tableCol * the index of the column within the JTable * * @return the preferred width */ public int getPreferredColWidth(final int col, final JTableHeader header, final int tableCol) { int width = this.columns[col].getPreferredWidth(); if(width > 0) { return width; } switch(this.columns[col].getType()) { case CHAR: case VARCHAR: case LONGVARCHAR: case CLOB: width = Math.min(300,Math.max(50,this.columns[col].getLength() * 7)); break; case TINYINT: case SMALLINT: width = 50; break; case INTEGER: case BIGINT: case REAL: case FLOAT: case DOUBLE: case DECIMAL: case NUMERIC: width = 100; break; case DATE: case TIME: width = 100; break; case TIMESTAMP: width = 150; break; default: width = 100; } TableCellRenderer renderer = header.getColumnModel().getColumn(tableCol) .getHeaderRenderer(); if(renderer == null) { renderer = header.getDefaultRenderer(); } return Math .max(width, renderer.getTableCellRendererComponent(header.getTable(), getColumnCaption(col),false,false,0,tableCol) .getPreferredSize().width); } /** * Creates and returns a new {@link VirtualTableRow}. The values within the * row get set to their default values.
* Autoincremented values are set to -1. * * @return a new {@link VirtualTableRow} */ public VirtualTableRow createRow() { return createRowImpl(null); } private VirtualTableRow createRowForInsert(final List> except) { return createRowImpl(except); } private VirtualTableRow createRowImpl(final List> except) { final VirtualTableRow row = new VirtualTableRow(this.columns.length); for(int col = 0; col < this.columns.length; col++) { if(this.columns[col].isAutoIncrement()) { row.set(col,getNextAutoVal(this.columns[col]),false,false); } else if(except == null || !except.contains(this.columns[col])) { // row.set(col,checkValue(col,columns[col].getDefaultValue()),false,false); // Don't check, this is done when the row is inserted in the VT row.set(col,this.columns[col].getDefaultValue(),false,false); } } return row; } /** * Adds the data contained in the specified result to the * {@link VirtualTable}. *

* Alias method for * {@link #addData(Result, VirtualTableColumn[], VirtualTableFillMethod)}. *

* * @param result * a {@link Result} containing data to be added * @param columns * array of {@link VirtualTableColumn} objects for retrieving * column indices to be used for this add operation. If columns * is null, the indices of the result are used. * @throws VirtualTableException * @throws DBException * if access to the result fails * @see VirtualTableFillMethod */ public void addData(final Result result, final VirtualTableColumn[] columns) throws VirtualTableException, DBException { addData(result,columns,VirtualTableFillMethod.APPEND); } /** * Adds the data contained in the specified result to the * {@link VirtualTable}. *

* Alias method for {@link #addData(Result, int[], VirtualTableFillMethod)}. *

* * @param result * a {@link Result} containing data to be added * @param columns * array of {@link VirtualTableColumn} objects for retrieving * column indices to be used for this add operation. If columns * is null, the indices of the result are used. * @param method * depending of the value of this enum: *
    *
  • existing data get overwritten
  • *
  • new data gets prepended in front of existing data
  • *
  • new data gets appended after existing data
  • *
* @throws VirtualTableException * @throws DBException * if access to the result fails * @see VirtualTableFillMethod */ public void addData(final Result result, final VirtualTableColumn[] columns, final VirtualTableFillMethod method) throws VirtualTableException, DBException { final int[] colsToFill = new int[columns.length]; for(int i = 0; i < columns.length; i++) { colsToFill[i] = getColumnIndex(columns[i]); } addData(result,colsToFill,method); } /** * Adds the data contained in the specified result to the * {@link VirtualTable}. The new data gets appended in front of existing * data. *

* Alias method for {@link #addData(Result, VirtualTableFillMethod)}. *

* * @param result * a {@link Result} containing data to be added * @throws VirtualTableException * @throws DBException * if access to the result fails */ public void addData(final Result result) throws VirtualTableException, DBException { addData(result,VirtualTableFillMethod.APPEND); } /** * Adds the data contained in the specified result to the * {@link VirtualTable}. *

* Alias method for {@link #addData(Result, int[], VirtualTableFillMethod)}. *

* * @param result * a {@link Result} containing data to be added * @param method * depending of the value of this enum: *
    *
  • existing data get overwritten
  • *
  • new data gets prepended in front of existing data
  • *
  • new data gets appended after existing data
  • *
* * @throws VirtualTableException * * @throws DBException * if access to the result fails * * @see VirtualTableFillMethod */ public void addData(final Result result, final VirtualTableFillMethod method) throws VirtualTableException, DBException { addData(result,getColumnIndices(result,true),method); } /** * Adds the data contained in the specified result to the * {@link VirtualTable}. The new data gets appended in front of existing * data. *

* Alias method for * {@link #addData(Result, VirtualTableColumn[], VirtualTableFillMethod)}. *

* * @param result * result a {@link Result} containing data to be added * @param colIndices * column indices to be used for this add operation. If * colIndices is null, the indices of the result are used. * @throws VirtualTableException * @throws DBException * if access to the result fails */ public void addData(final Result result, final int[] colIndices) throws VirtualTableException, DBException { addData(result,colIndices,VirtualTableFillMethod.APPEND); } /** * Adds the data contained in the specified result to the * {@link VirtualTable}. * * @param result * a {@link Result} containing data to be added * @param colIndices * column indices to be used for this add operation. If * colIndices is null, the indices of the result are used. * @param method * depending of the value of this enum: *
    *
  • existing data get overwritten
  • *
  • new data gets prepended in front of existing data
  • *
  • new data gets appended after existing data
  • *
* * @throws VirtualTableException * * @throws DBException * if access to the result fails * * @see VirtualTableFillMethod */ public void addData(final Result result, final int[] colIndices, final VirtualTableFillMethod method) throws VirtualTableException, DBException { try { addDataImpl(result,colIndices,method); } finally { result.close(); } } private void addDataImpl(final Result result, int[] colIndices, final VirtualTableFillMethod method) throws VirtualTableException, DBException { setLastQuery(result.getQueryInfo()); if(colIndices == null) { colIndices = getColumnIndices(result,true); } this.lastQueryIndices = colIndices; final int[] srcCols = new int[this.columns.length]; // long[] nextAutoVal = new long[columns.length]; for(int c = 0; c < this.columns.length; c++) { srcCols[c] = -1; // nextAutoVal[c] = 0; } for(int c = 0; c < colIndices.length; c++) { if(colIndices[c] != -1) { srcCols[colIndices[c]] = c; } } // for(int c = 0; c < columns.length; c++) // { // if(srcCols[c] == -1 && columns[c].isAutoIncrement()) // { // nextAutoVal[c] = getNextAutoVal(c); // } // } if(method == VirtualTableFillMethod.OVERWRITE) { clear(); } VirtualTableRow row; Object val; int index = 0; while(result.next()) { row = new VirtualTableRow(this.columns.length); for(int c = 0; c < this.columns.length; c++) { if(srcCols[c] == -1) { if(this.columns[c].isAutoIncrement()) { // long auto_val = nextAutoVal[c]++; row.set(c,getNextAutoVal(this.columns[c]),false,false); } else { row.set(c,this.columns[c].getDefaultValue(),false,false); } } else { val = result.getObject(srcCols[c]); row.set(c,checkValue(c,val),false,false); } } switch(method) { case PREPEND: this.data.insert(row,index,false); break; default: this.data.add(row,false); break; } index++; } fireDataChanged(); } /** * Returns always -1.
* It's inconsistent with the database anyway -> get the original values. * * @return -1 * @see VTWriteDBHandler */ private Number getNextAutoVal(final VirtualTableColumn col) { switch(col.getType()) { case TINYINT: return (byte)-1; case SMALLINT: return (short)-1; case INTEGER: return -1; case BIGINT: return (long)-1; } throw new VirtualTableException(this, col.getName() + "(" + col.getType() + ") is not auto incrementable"); // return ++maxVal[col]; } /** * Returns a {@link XdevList} of the column captions for this * . * * @return the column captions */ public XdevList getCaptions() { final XdevList captions = new XdevList(this.columns.length); for(int i = 0; i < this.columns.length; i++) { captions.addElement(this.columns[i].getName()); } return captions; } /** * Fills the elements of a form, with the values of a specified row. * * @param form * the {@link Formular} to be filled * @param rowForForm * the index (0-based) of the row, which value is to be used for * filling the form * * @throws VirtualTableException * * @throws IndexOutOfBoundsException * if rowForForm is not within the range of rows for this * Virtual Table. */ public void fillFormular(final Formular form, final int rowForForm) throws VirtualTableException, IndexOutOfBoundsException { MathUtils.checkRange(rowForForm,0,getRowCount() - 1); form.setModel(this.data.get(rowForForm)); } /** * Stores the data of this Virtual Table in a local file specified by the * user. This method opens a file chosser and lets the user specify the * file. * * @throws IOException * * @see #loadLocal() */ public void saveLocal() throws IOException { try { final JFileChooser fc = UIUtils.getSharedFileChooser(); if(fc.showSaveDialog(UIUtils.getActiveWindow()) == JFileChooser.APPROVE_OPTION) { final File f = fc.getSelectedFile(); final XdevObjectOutputStream out = new XdevObjectOutputStream( new GZIPOutputStream(new FileOutputStream(f))); try { write(out); } finally { out.close(); } } } catch(final IOException e) { throw new IOException(e); } } /** * Writes the data of this VirtualTable to a specified * XdevObjectOutputStream. * * @param out * {@link XdevObjectOutputStream} to write the data to * @throws IOException * if data can't be written */ public void write(final XdevObjectOutputStream out) throws IOException { try { out.writeUTF(FILE_HEADER); out.writeInt(0); out.writeUTF(this.name); final int rowc = getRowCount(); out.writeInt(rowc); out.writeInt(this.columns.length); for(int ri = 0; ri < rowc; ri++) { for(int ci = 0; ci < this.columns.length; ci++) { try { out.writeObject(getValueAt(ri,ci)); } catch(final Exception e) { throw new IOException(e.getMessage()); } } } } catch(final IOException e) { throw new IOException(e); } } /** * Loads stored data from a user specified file into this * {@link VirtualTable}. This method opens a file chosser and lets the user * specify the file. * * @throws IOException * if data can't be read * @see #saveLocal() */ public void loadLocal() throws IOException { try { final JFileChooser fc = UIUtils.getSharedFileChooser(); if(fc.showOpenDialog(UIUtils.getActiveWindow()) == JFileChooser.APPROVE_OPTION) { final File f = fc.getSelectedFile(); final XdevObjectInputStream in = new XdevObjectInputStream( new GZIPInputStream(new FileInputStream(f))); try { read(in); } finally { in.close(); } } } catch(final IOException e) { throw new IOException(e); } } /** * Reads data from an {@link XdevObjectInputStream}. * * @param in * {@link XdevObjectInputStream} to read from * @throws IOException * if file couldn't be opened for loading data */ public void read(final XdevObjectInputStream in) throws IOException { try { String error = null; if(in.readUTF().equals(FILE_HEADER)) { in.readInt(); in.readUTF(); final int rowc = in.readInt(), colc = in.readInt(); if(colc == this.columns.length) { this.data = new VirtualTableData(rowc); for(int i = 0; i < rowc; i++) { final VirtualTableRow rowData = new VirtualTableRow(colc); for(int j = 0; j < colc; j++) { try { rowData.set(j,in.readObject(),false,false); } catch(final Exception e) { throw new IOException(e.getMessage()); } } this.data.add(rowData,false); } } else { error = "Wrong VT"; } } else { error = "Invalid file"; } if(error != null) { throw new IOException(error); } } catch(final IOException e) { throw new IOException(e); } } /** * Imports data from CSV (Comma Separated Values) information. *

* This is an alias for * {@link #importCSV(Reader, char, String, boolean, int)}. *

* * @param reader * {@link Reader} to read the CSV information from * @throws IOException * if reading of CSV file fails * @throws VirtualTableException */ public void importCSV(final Reader reader) throws IOException, VirtualTableException { importCSV(reader,',',"null",true,0); } /** * Imports data from CSV (Comma Separated Values) information. *

* This is an alias for * {@link #importCSV(Reader, char, String, boolean, int)}. *

* * @param reader * {@link Reader} to read the CSV information from * @param separator * char used to separate the column values (defaults to comma) * @throws IOException * if reading of CSV file fails * @throws VirtualTableException */ public void importCSV(final Reader reader, final char separator) throws IOException, VirtualTableException { importCSV(reader,separator,"null",true,0); } /** * Imports data from CSV (Comma Separated Values) information. *

* This is an alias for * {@link #importCSV(Reader, char, String, boolean, int)}. *

* * @param reader * {@link Reader} to read the CSV information from * @param separator * char used to separate the column values (defaults to comma) * @param nullValue * a {@link String} which is interpreted as null by * the VirtualTable * @throws IOException * if reading of CSV file fails * @throws VirtualTableException */ public void importCSV(final Reader reader, final char separator, final String nullValue) throws IOException, VirtualTableException { importCSV(reader,separator,nullValue,true,0); } /** * Imports data from CSV (Comma Separated Values) information. *

* This is an alias for * {@link #importCSV(Reader, char, String, boolean, int)}. *

* * @param reader * {@link Reader} to read the CSV information from * @param separator * char used to separate the column values (defaults to comma) * @param nullValue * a {@link String} which is interpreted as null by * the VirtualTable * @param useCsvHeaders * if set to true, the first line of the CSV file is interpreted * as column names * @throws IOException * if reading of CSV file fails * @throws VirtualTableException */ public void importCSV(final Reader reader, final char separator, final String nullValue, final boolean useCsvHeaders) throws IOException, VirtualTableException { importCSV(reader,separator,nullValue,useCsvHeaders,0); } /** * Imports data from CSV (Comma Separated Values) information. * * @param reader * {@link Reader} to read the CSV information from * @param separator * char used to separate the column values (defaults to comma) * @param nullValue * a {@link String} which is interpreted as null by * the VirtualTable * @param useCsvHeaders * if set to true, the first line of the CSV file is interpreted * as column names * @param skipLines * int value, skips the specified number of lines at the start, * when reading the CSV file * @throws IOException * if reading of CSV file fails * * @throws VirtualTableException * * @see #exportCSV(Writer, int, String, char, List, boolean) */ public synchronized void importCSV(final Reader reader, final char separator, final String nullValue, final boolean useCsvHeaders, final int skipLines) throws IOException, VirtualTableException { importCSV(reader,separator,CSVParser.DEFAULT_QUOTE_CHARACTER,nullValue,useCsvHeaders, skipLines); } /** * Imports data from CSV (Comma Separated Values) information. * * @param reader * {@link Reader} to read the CSV information from * @param separator * char used to separate the column values (defaults to comma) * @param quotechar * the character to use for quoted elements * @param nullValue * a {@link String} which is interpreted as null by * the VirtualTable * @param useCsvHeaders * if set to true, the first line of the CSV file is interpreted * as column names * @param skipLines * int value, skips the specified number of lines at the start, * when reading the CSV file * @throws IOException * if reading of CSV file fails * * @throws VirtualTableException * * @see #exportCSV(Writer, int, String, char, List, boolean) * * @since 3.1 */ public synchronized void importCSV(final Reader reader, final char separator, final char quotechar, final String nullValue, final boolean useCsvHeaders, final int skipLines) throws IOException, VirtualTableException { final CSVReader csvReader = new CSVReader(reader,separator,quotechar); importCSV(csvReader,nullValue,useCsvHeaders,skipLines); } /** * Imports data from CSV (Comma Separated Values) information. * * @param csvReader * the input source * @param nullValue * a {@link String} which is interpreted as null by * the VirtualTable * @param useCsvHeaders * if set to true, the first line of the CSV file is interpreted * as column names * @param skipLines * int value, skips the specified number of lines at the start, * when reading the CSV file * @throws IOException * if reading of CSV file fails * * @throws VirtualTableException * * @see #exportCSV(Writer, int, String, char, List, boolean) * * @since 3.1 */ public synchronized void importCSV(final CSVReader csvReader, final String nullValue, final boolean useCsvHeaders, final int skipLines) throws IOException, VirtualTableException { final List> columns = new ArrayList(); if(useCsvHeaders) { final String[] record = csvReader.readNext(); if(record == null) { return; } for(final String name : record) { final VirtualTableColumn col = getColumn(name); if(col != null) { columns.add(col); } } } else { for(int i = 0; i < this.columns.length; i++) { columns.add(this.columns[i]); } } for(int i = 0; i < skipLines; i++) { if(csvReader.readNext() == null) { return; } } final int vtColumnCount = columns.size(); final int[] vtColumnIndex = new int[vtColumnCount]; for(int i = 0; i < vtColumnCount; i++) { vtColumnIndex[i] = getColumnIndex(columns.get(i)); } final List rowData = new ArrayList(); String[] record; while((record = csvReader.readNext()) != null) { final VirtualTableRow row = createRowForInsert(columns); final int readerColumnCount = record.length; for(int i = 0; i < readerColumnCount; i++) { Object value; final String str = record[i]; if(nullValue.equals(str)) { value = null; } else { value = checkValue(vtColumnIndex[i],str); } row.set(vtColumnIndex[i],value,false,true); } this.data.add(row,true); fireRowInserted(row,row.index); rowData.clear(); } } /** * Exports data as CSV information to a specified {@link Writer}. *

* This is an alias for {@link #exportCSV(Writer, int)}. *

* * @param writer * a {@link Writer} to write CSV information to * @throws IOException * if CSV file couldn't be written */ public void exportCSV(final Writer writer) throws IOException { exportCSV(writer,0); } /** * Exports data as CSV information to a specified {@link Writer}. *

* This is an alias for {@link #exportCSV(Writer, int, String)}. *

* * @param writer * a {@link Writer} to write CSV information to * @param startRow * the start row within the data of the Virtual Table * @throws IOException * if CSV file couldn't be written */ public void exportCSV(final Writer writer, final int startRow) throws IOException { exportCSV(writer,startRow,"null"); } /** * Exports data as CSV information to a specified {@link Writer}. *

* This is an alias for {@link #exportCSV(Writer, int, String, char)}. *

* * @param writer * a {@link Writer} to write CSV information to * @param startRow * the start row within the data of the Virtual Table * @param nullValue * a {@link String} representation for null-values * in the VirtualTable * @throws IOException * if CSV file couldn't be written */ public void exportCSV(final Writer writer, final int startRow, final String nullValue) throws IOException { exportCSV(writer,startRow,nullValue,','); } /** * Exports data as CSV information to a specified {@link Writer}. *

* This is an alias for {@link #exportCSV(Writer, int, String, char, List)}. *

* * @param writer * a {@link Writer} to write CSV information to * @param startRow * the start row within the data of the Virtual Table * @param nullValue * a {@link String} representation for null-values * in the VirtualTable * @param delimiter * the delimiter used to separate fields in the CSV file * @throws IOException * if CSV file couldn't be written */ public void exportCSV(final Writer writer, final int startRow, final String nullValue, final char delimiter) throws IOException { exportCSV(writer,startRow,nullValue,delimiter,getColumnNames()); } /** * Exports data as CSV information to a specified {@link Writer}. *

* This is an alias for * {@link #exportCSV(Writer, int, String, char, List, boolean)}. *

* * @param writer * a {@link Writer} to write CSV information to * @param startRow * the start row within the data of the Virtual Table * @param nullValue * a {@link String} representation for null-values * in the VirtualTable * @param delimiter * the delimiter used to separate fields in the CSV file * @param columnNames * a list of the columnNames used as headers in the CSV file. No * header is generated if this value is null. * @throws IOException * if CSV file couldn't be written */ public void exportCSV(final Writer writer, final int startRow, final String nullValue, final char delimiter, final List columnNames) throws IOException { exportCSV(writer,startRow,nullValue,delimiter,columnNames,true); } /** * Exports data as CSV information to a specified {@link Writer}. * * @param writer * a {@link Writer} to write CSV information to * @param startRow * the start row within the data of the Virtual Table * @param nullValue * a {@link String} representation for null-values * in the VirtualTable * @param delimiter * the delimiter used to separate fields in the CSV file * @param columnNames * a list of the columnNames used as headers in the CSV file. No * header is generated if this value is null. * @param writeColumnNames * if set to true, a header line is generated in the CSV-file * @throws IOException * if CSV file couldn't be written */ public synchronized void exportCSV(final Writer writer, final int startRow, final String nullValue, final char delimiter, final List columnNames, final boolean writeColumnNames) throws IOException { exportCSV(writer,startRow,nullValue,delimiter,CSVParser.DEFAULT_QUOTE_CHARACTER,columnNames, writeColumnNames); } /** * Exports data as CSV information to a specified {@link Writer}. * * @param writer * a {@link Writer} to write CSV information to * @param startRow * the start row within the data of the Virtual Table * @param nullValue * a {@link String} representation for null-values * in the VirtualTable * @param delimiter * the delimiter used to separate fields in the CSV file * @param quotechar * the character to use for quoted elements * @param columnNames * a list of the columnNames used as headers in the CSV file. No * header is generated if this value is null. * @param writeColumnNames * if set to true, a header line is generated in the CSV-file * @throws IOException * if CSV file couldn't be written * * @since 3.1 */ public synchronized void exportCSV(final Writer writer, final int startRow, final String nullValue, final char delimiter, final char quotechar, final List columnNames, final boolean writeColumnNames) throws IOException { final CSVWriter csvWriter = new CSVWriter(writer,delimiter,quotechar); exportCSV(csvWriter,startRow,nullValue,columnNames,writeColumnNames); } /** * Exports data as CSV information to a specified {@link Writer}. * * @param csvWriter * the csv writer * @param startRow * the start row within the data of the Virtual Table * @param nullValue * a {@link String} representation for null-values * in the VirtualTable * @param columnNames * a list of the columnNames used as headers in the CSV file. No * header is generated if this value is null. * @param writeColumnNames * if set to true, a header line is generated in the CSV-file * @throws IOException * if CSV file couldn't be written * * @since 3.1 */ public synchronized void exportCSV(final CSVWriter csvWriter, final int startRow, final String nullValue, List columnNames, boolean writeColumnNames) throws IOException { if(columnNames == null) { writeColumnNames = false; columnNames = getColumnNames(); } final int columnCount = columnNames.size(); final int[] colIndices = new int[columnCount]; for(int i = 0; i < columnCount; i++) { final String columnName = columnNames.get(i).toString(); colIndices[i] = getColumnIndex(columnName); if(colIndices[i] == -1) { throw new VirtualTableException(this,"Column '" + columnName + "' not found"); } } if(writeColumnNames) { csvWriter.writeNext(columnNames.toArray(new String[columnNames.size()])); } final String[] line = new String[columnCount]; final int rowCount = getRowCount(); for(int row = startRow; row < rowCount; row++) { for(int col = 0; col < columnCount; col++) { final Object o = getValueAt(row,colIndices[col]); line[col] = o == null ? nullValue : formatValue(o,colIndices[col]); } csvWriter.writeNext(line); } } /** * Returns the current cursor position (row number) of this * {@link VirtualTable}. * * @return the current cursor position */ public int getCursorPos() { return this.cursorpos; } /** * Sets the current cursor position (row number) for this * {@link VirtualTable}. * * @param cursorpos * the current cursor position */ public void setCursorPos(int cursorpos) { if(cursorpos < -1) { cursorpos = -1; } this.cursorpos = cursorpos; } /** * Resets the current cursor position (row number) for this * {@link VirtualTable}. This positions the cursor in front of the first row * in the table */ public void resetCursor() { this.cursorpos = -1; } /** * Positions the cursor at the next row. * * @return true, if the next position points at a valid row */ public boolean next() { this.cursorpos++; return this.cursorpos < getRowCount(); } /** * Returns the row index of the first occurence of the specified value * within the specified column. * * @param columnName * the name of the column to search for the value * @param val * the value to search for * @return the row index or -1, if no occurence of value is found */ public int getRowIndex(final String columnName, final Object val) { return getRowIndex(columnName,val,0); } /** * Returns the row index of the first occurence of the specified value * within the specified column. * * @param columnName * the name of the column to search for the value * @param val * the value to search for * @param startIndex * the row index to start the search from * @return the row index or -1, if no occurence of value is found */ public int getRowIndex(final String columnName, final Object val, final int startIndex) { int index = -1; final int col = getColumnIndex(columnName); if(col != -1) { final DataType type = getColumnAt(col).getType(); final int max = getRowCount(); Object o; for(int row = startIndex; row < max && index == -1; row++) { o = getValueAt(row,col); if(equals(o,val,type)) { index = row; } } } return index; } /** * Returns an array of all primary key columns for this {@link VirtualTable} * . * * @return an array of {@link VirtualTableColumn} instances representing all * primary key columns */ public VirtualTableColumn[] getPrimaryKeyColumns() { for(final VirtualTableIndex vtIndex : this.indices) { if(vtIndex.type == IndexType.PRIMARY_KEY) { return vtIndex.columns; } } return VirtualTableColumn.NO_COLUMN; } /** * This is a synonym for * {@link #setModelFor(XdevTree, String, Object, String, String, String, String)} * * @param tree * the target tree * @param rootsColumnName * the column name for the root condition * @param rootsID * the value for the root condition * @param idColumnName * the id column name * @param ownerColumnName * the referenced owner column name * @param captionColumnName * the column name for the caption of the nodes * @param dataColumnName * the data column name, or null if the * {@link VirtualTableRow}s should be used as data object * * @throws VirtualTableException * if one of the columns cannot be found */ public void fillTree(final XdevTree tree, final String rootsColumnName, final Object rootsID, final String idColumnName, final String ownerColumnName, final String captionColumnName, final String dataColumnName) throws VirtualTableException { setModelFor(tree,rootsColumnName,rootsID,idColumnName,ownerColumnName,captionColumnName, dataColumnName); } /** * This is a synonym for * {@link #setModelFor(XdevTree, String, Object, String, String, String, String, boolean)} * * @param tree * the target tree * @param rootsColumnName * the column name for the root condition * @param rootsID * the value for the root condition * @param idColumnName * the id column name * @param ownerColumnName * the referenced owner column name * @param captionColumnName * the column name for the caption of the nodes * @param dataColumnName * the data column name, or null if the * {@link VirtualTableRow}s should be used as data object * @param multipleRoots * true for a dummy root with multiple sub-roots, or * false for a single root * * @throws VirtualTableException * if one of the columns cannot be found * * @since 3.1 */ public void fillTree(final XdevTree tree, final String rootsColumnName, final Object rootsID, final String idColumnName, final String ownerColumnName, final String captionColumnName, final String dataColumnName, final boolean multipleRoots) throws VirtualTableException { setModelFor(tree,rootsColumnName,rootsID,idColumnName,ownerColumnName,captionColumnName, dataColumnName,multipleRoots); } /** * Creates a tree model for the tree containing data of this * VirtualTable by creating {@link XdevTreeNode}s out of * {@link VirtualTableRow}s. *

* This is a synonym for: * *

	 * setModelFor(tree,rootsColumnName,rootsID,idColumnName,ownerColumnName,captionColumnName,
	 * 		dataColumnName,false);
	 * 
*

* For a detailed description see * {@link #createTree(String, Object, String, String, String, String, boolean)} * * @param tree * the target tree * @param rootsColumnName * the column name for the root condition * @param rootsID * the value for the root condition * @param idColumnName * the id column name * @param ownerColumnName * the referenced owner column name * @param captionColumnName * the column name for the caption of the nodes * @param dataColumnName * the data column name, or null if the * {@link VirtualTableRow}s should be used as data object * * @throws VirtualTableException * if one of the columns cannot be found */ public void setModelFor(final XdevTree tree, final String rootsColumnName, final Object rootsID, final String idColumnName, final String ownerColumnName, final String captionColumnName, final String dataColumnName) throws VirtualTableException { setModelFor(tree,rootsColumnName,rootsID,idColumnName,ownerColumnName,captionColumnName, dataColumnName,false); } /** * Creates a tree model for the tree containing data of this * VirtualTable by creating {@link XdevTreeNode}s out of * {@link VirtualTableRow}s. *

* For a detailed description see * {@link #createTree(String, Object, String, String, String, String, boolean)} * * @param tree * the target tree * @param rootsColumnName * the column name for the root condition * @param rootsID * the value for the root condition * @param idColumnName * the id column name * @param ownerColumnName * the referenced owner column name * @param captionColumnName * the column name for the caption of the nodes * @param dataColumnName * the data column name, or null if the * {@link VirtualTableRow}s should be used as data object * @param multipleRoots * true for a dummy root with multiple sub-roots, or * false for a single root * * @throws VirtualTableException * if one of the columns cannot be found * * @since 3.1 */ public void setModelFor(final XdevTree tree, final String rootsColumnName, final Object rootsID, final String idColumnName, final String ownerColumnName, final String captionColumnName, final String dataColumnName, final boolean multipleRoots) throws VirtualTableException { if(tree == null) { throw new NullPointerException(); } final XdevTreeNode root = createTree(rootsColumnName,rootsID,idColumnName,ownerColumnName, captionColumnName,dataColumnName,multipleRoots); if(root != null) { tree.setCellRenderer(new XdevTreeRenderer(new DefaultXdevTreeManager(tree))); tree.setRootVisible(!multipleRoots); tree.setModel(new XdevTreeModel(root)); } } /** * Creates a tree structure containing data of this VirtualTable by creating * {@link XdevTreeNode}s out of {@link VirtualTableRow}s. *

* This is a synonym for: * *

	 * createTree(rootsColumnName,rootsID,idColumnName,ownerColumnName,captionColumnName,
	 * 		dataColumnName,false);
	 * 
*

* For a detailed description see * {@link #createTree(String, Object, String, String, String, String, boolean)} * * @param rootsColumnName * the column name for the root condition * @param rootsID * the value for the root condition * @param idColumnName * the id column name * @param ownerColumnName * the referenced owner column name * @param captionColumnName * the column name for the caption of the nodes * @param dataColumnName * the data column name, or null if the * {@link VirtualTableRow}s should be used as data object * * @return the root node of the created tree * * @throws VirtualTableException * if one of the columns cannot be found */ public XdevTreeNode createTree(final String rootsColumnName, final Object rootsID, final String idColumnName, final String ownerColumnName, final String captionColumnName, final String dataColumnName) throws VirtualTableException { return createTree(rootsColumnName,rootsID,idColumnName,ownerColumnName,captionColumnName, dataColumnName,false); } /** * Creates a tree structure containing data of this VirtualTable by creating * {@link XdevTreeNode}s out of {@link VirtualTableRow}s. *

* First you have to define which rows should be the root of the tree. This * is done with the first two parameters: rootsColumnName and * rootsID. Meaning every row where the value of the column * rootsColumnName equals the value rootsID.
* If multipleRoots is true, all columns where * this condition is met are taken and connected to a dummy root node which * will be hidden in the tree. Otherwise only the first matching row will be * the root itself. *

* The subtrees are created by connecting the id column ( * idColumnName) to the owner column ( * ownerColumnName). In other words every row is connected as a * child node to another node where the value in the column * idColumnName matches the owner rows' value in the column * ownerColumnName. *

* Each created {@link XdevTreeNode} consist of a caption, which is taken * from the column captionColumnnName, and a data object which * is taken from the column dataColumnName. If * dataColumnName is null or the column is not * found, the {@link VirtualTableRow} itself is used as data object. *

* Example: *

*

* * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
IDOWNER_IDNAME
10Africa
20America
30Asia
40Europe
51Egypt
61Cameroon
72Canada
82USA
93Japan
103Thailand
114Germany
114Spain
128California
138Colorado
*
* * * * * * * * * * * * *
* rootsColumnName = "OWNER_ID"
* rootsID = 0
* idColumnName = "ID"
* ownerColumnName = "OWNER_ID"
* captionColumnName = "NAME"
* dataColumnName = null
* multipleRoots = true
*
* ------------------------> *
* root
* +-- Africa
* |   +-- Egypt
* |   +-- Cameroon
* +-- America
* |   +-- Canada
* |   +-- USA
* |       +-- California
* |       +-- Colorado
* +-- Asia
* |   +-- Japan
* |   +-- Thailand
* +-- Europe
*     +-- Germany
*     +-- Spain *
*
*
*
* rootsColumnName = "ID"
* rootsID = 2
* idColumnName = "ID"
* ownerColumnName = "OWNER_ID"
* captionColumnName = "NAME"
* dataColumnName = null
* multipleRoots = false
*
* ------------------------> *
* America (single root)
* +-- Canada
* +-- USA
*     +-- California
*     +-- Colorado *
*
* * @param rootsColumnName * the column name for the root condition * @param rootsID * the value for the root condition * @param idColumnName * the id column name * @param ownerColumnName * the referenced owner column name * @param captionColumnName * the column name for the caption of the nodes * @param dataColumnName * the data column name, or null if the * {@link VirtualTableRow}s should be used as data object * @param multipleRoots * true for a dummy root with multiple sub-roots, or * false for a single root * * @return the root node of the created tree * * @throws VirtualTableException * if one of the columns cannot be found * * @since 3.1 */ public XdevTreeNode createTree(final String rootsColumnName, final Object rootsID, final String idColumnName, final String ownerColumnName, final String captionColumnName, final String dataColumnName, final boolean multipleRoots) throws VirtualTableException { final int rootsCol = getColumnIndex(rootsColumnName); if(rootsCol == -1) { VirtualTableException.throwColumnNotFound(this,rootsColumnName); } final int idCol = getColumnIndex(idColumnName); if(idCol == -1) { VirtualTableException.throwColumnNotFound(this,idColumnName); } final int ownerCol = getColumnIndex(ownerColumnName); if(ownerCol == -1) { VirtualTableException.throwColumnNotFound(this,ownerColumnName); } final int captionCol = getColumnIndex(captionColumnName); if(captionCol == -1) { VirtualTableException.throwColumnNotFound(this,captionColumnName); } final IntList rootRows = new IntList(); Object o; for(int i = 0; i < getRowCount(); i++) { o = getValueAt(i,rootsCol); if(equals(o,rootsID,null)) { rootRows.add(i); if(!multipleRoots) { break; } } } if(rootRows.size() == 0) { return null; } final int dataCol = dataColumnName == null ? -1 : getColumnIndex(dataColumnName); XdevTreeNode root; if(multipleRoots) { root = new XdevTreeNode("root"); for(int i = 0, c = rootRows.size(); i < c; i++) { final int rootRow = rootRows.get(i); final XdevTreeNode node = new XdevTreeNode( dataCol >= 0 ? getValueAt(rootRow,dataCol) : "", getFormattedValueAt(rootRow,captionCol)); root.add(node); createTree(node,getValueAt(rootRow,idCol),idCol,ownerCol,captionCol,dataCol); } } else { final int rootRow = rootRows.get(0); root = new XdevTreeNode(dataCol >= 0 ? getValueAt(rootRow,dataCol) : "", getFormattedValueAt(rootRow,captionCol)); createTree(root,getValueAt(rootRow,idCol),idCol,ownerCol,captionCol,dataCol); } return root; } private void createTree(final XdevTreeNode owner, final Object id, final int idCol, final int ownerCol, final int captionCol, final int dataCol) { Object o; for(int row = 0; row < getRowCount(); row++) { o = getValueAt(row,ownerCol); if(equals(o,id,null)) { final Object data = dataCol >= 0 ? getValueAt(row,dataCol) : getRow(row); final XdevTreeNode node = new XdevTreeNode(data, getFormattedValueAt(row,captionCol)); owner.add(node); createTree(node,getValueAt(row,idCol),idCol,ownerCol,captionCol,dataCol); } } } /** * Creates and set the table model for a specified JTable. *

* This is an alias for {@link #setModelFor(JTable)}. *

* * @param table * the {@link JTable} to set the model for. */ public void fillTable(final JTable table) { setModelFor(table); } /** * Creates and set the table model for a specified JTable. *

* This is an alias for {@link #setModelFor(JTable)}. *

* * @param table * the {@link JTable} to set the model for. */ public void setModelFor(final JTable table) { table.setModel(createTableModel()); } /** * Creates and returns a {@link TableModel} for this {@link VirtualTable}. * Only Visible Columns are included. * * @return the {@link TableModel} */ public TableModel createTableModel() { return createTableModel(getVisibleColumnIndices()); } /** * Creates and returns a {@link TableModel} for this {@link VirtualTable}. * * @param columnNames * a array of columnNames to be included in the * {@link TableModel}. * * @return a {@link TableModel} */ public TableModel createTableModel(final String... columnNames) { final IntList list = new IntList(); for(final String name : columnNames) { final int columnIndex = getColumnIndex(name); if(columnIndex >= 0) { list.add(columnIndex); } } return createTableModel(list.toArray()); } /** * Creates and returns a {@link TableModel} for this {@link VirtualTable}. * * @param columnNames * a {@link List} of columnNames to be included in the * {@link TableModel}. * * @return a {@link TableModel} */ public TableModel createTableModel(final List columnNames) { final IntList list = new IntList(); for(final String name : columnNames) { final int columnIndex = getColumnIndex(name); if(columnIndex >= 0) { list.add(columnIndex); } } return createTableModel(list.toArray()); } /** * Creates and returns a {@link TableModel} for this {@link VirtualTable}. * * @param columnIndices * the column indexed to be included in the {@link TableModel}. * @return a {@link TableModel} */ public TableModel createTableModel(final int... columnIndices) { return new VirtualTableModel(this,columnIndices); } /** * @deprecated Typo, use {@link #createTableModel(int...)} instead */ @Deprecated public TableModel createTabelModel(final int... columnIndices) { return createTableModel(columnIndices); } /** * Helper method interface to select columns. * * @see VirtualTable#getColumns(ColumnSelector) * @see VirtualTable#getColumnIndices(ColumnSelector) */ public static interface ColumnSelector { /** * @return true if the column should be selected, * false otherwise */ public boolean select(VirtualTableColumn column); } /** * Returns all {@link VirtualTableColumn}s of this VirtualTable according to * selector. * * @param selector * the {@link ColumnSelector} * @return an array of {@link VirtualTableColumn}s according to * selector */ public VirtualTableColumn[] getColumns(final ColumnSelector selector) { final List> list = new ArrayList(); for(final VirtualTableColumn column : this.columns) { if(selector.select(column)) { list.add(column); } } return list.toArray(new VirtualTableColumn[list.size()]); } /** * Returns all colum indices of this VirtualTable's columns according to * selector. * * @param selector * the {@link ColumnSelector} * @return the column's indices according to selector */ public int[] getColumnIndices(final ColumnSelector selector) { final IntList list = new IntList(); for(int i = 0; i < this.columns.length; i++) { if(selector.select(this.columns[i])) { list.add(i); } } return list.toArray(); } /** * Returns an array with all indexes of the visible columns. * * @return an int array with indexes of visible columns * @see VirtualTableColumn#isVisible() */ public int[] getVisibleColumnIndices() { return getColumnIndices(new ColumnSelector() { @Override public boolean select(final VirtualTableColumn column) { return column.isVisible(); } }); } /** * Returns an array with all indexes of the persistent columns. * * @return an int array with indexes of persistent columns * @see VirtualTableColumn#isPersistent() */ public int[] getPersistentColumnIndices() { return getColumnIndices(new ColumnSelector() { @Override public boolean select(final VirtualTableColumn column) { return column.isPersistent(); } }); } /** * Returns an array with all indexes of the non persistent columns. * * @return an int array with indexes of non persistent columns * @see VirtualTableColumn#isPersistent() */ public int[] getNonPersistentColumnIndices() { return getColumnIndices(new ColumnSelector() { @Override public boolean select(final VirtualTableColumn column) { return !column.isPersistent(); } }); } /** * Returns an array with all indexes of the visible and non persistent * columns. * * @return an int array with indexes of visible and non persistent columns * @see VirtualTableColumn#isPersistent() * @see VirtualTableColumn#isVisible() */ public int[] getNonPersistentVisibleColumnIndices() { return getColumnIndices(new ColumnSelector() { @Override public boolean select(final VirtualTableColumn column) { return !column.isPersistent() && column.isVisible(); } }); } /** * Checks if this VirtualTable has a non persistent column. * * @return true if this VirtualTable has a non persistent * column, false otherwise * @see VirtualTableColumn#isPersistent() */ public boolean hasNonPersistentColumns() { for(final VirtualTableColumn column : this.columns) { if(!column.isPersistent()) { return true; } } return false; } /** * Checks if this VirtualTable has a linked column. * * @return true if this VirtualTable has a linked column, * false otherwise * @see VirtualTableColumn#getTableColumnLink() * * @since 3.2 */ public boolean hasLinkedColumns() { for(final VirtualTableColumn column : this.columns) { if(column.getTableColumnLink() != null) { return true; } } return false; } /** * Override of Objects toString method. * * @return the name of the {@link VirtualTable}, or "VirtualTable" if name * is empty */ @Override public String toString() { return this.name != null && this.name.length() > 0 ? this.name : "VirtualTable"; } /** * Returns a {@link String} representation for this {@link VirtualTable}. * The first line of the array contains the captions of the columns * * @return a two dimensional array of Strings with the captions and data for * this VirtualTable. */ public String[][] toFormattedStrings() { final String[][] s = new String[getRowCount() + 1][]; s[0] = getColumnCaptions(); return this.data.putFormattedStrings(s,1); } /** * Returns a {@link TableMetaData} representation of this * {@link VirtualTable}. */ public TableMetaData toTableMetaData() { final TableInfo tableInfo = new TableInfo(TableType.TABLE,this.dbSchema,this.dbAlias); final List columnMeta = new ArrayList(); for(final VirtualTableColumn column : this.columns) { if(column.isPersistent()) { columnMeta.add(column.toColumnMetaData(this)); } } final int c = this.indices.size(); final Index[] indexMeta = new Index[c]; for(int i = 0; i < c; i++) { indexMeta[i] = this.indices.get(i)._originalIndex.clone(); } return new TableMetaData(tableInfo, columnMeta.toArray(new ColumnMetaData[columnMeta.size()]),indexMeta); } /** * Returns a {@link XdevList} for all rows containing further XdevLists for * all values of the rows. *

* This is an alias for {@link #toLists()} *

* * @return a {@link XdevList} of {@link XdevList}s containing the data of * the Virtual Table */ public XdevList getValues() { return toLists(); } /** * Returns a {@link XdevList} containing all rows as further XdevLists * containing all values of the rows. * * @return a {@link XdevList} of {@link XdevList}s containing the data of * the Virtual Table */ public XdevList toLists() { return toLists(-1); } /** * Returns a {@link XdevList} containing all rows as further XdevLists * containing all values of the rows. * * @param exceptCol * a column index to be excluded from the lists of values * @return a {@link XdevList} of {@link XdevList}s containing the data of * the Virtual Table */ public XdevList toLists(final int exceptCol) { final XdevList all = new XdevList(); for(int i = 0; i < this.data.size(); i++) { all.addElement(getRow(i,exceptCol)); } return all; } /** * Returns the {@link VirtualTableRow} for the specified row index. * * @param index * the row index * * @return a {@link VirtualTableRow} * * @throws IndexOutOfBoundsException * if index is not within range of row indices */ public VirtualTableRow getRow(final int index) throws IndexOutOfBoundsException { return this.data.get(index); } /** * Returns all rows of this VirtualTable. If this VirtualTable has no data * an empty array is returned. * * @return all rows of this VirtualTable. */ public VirtualTableRow[] getRows() { return this.data.getAll(); } /** * Returns a subset of rows of this VirtualTable. * * @param startIndex * the start index, inclusive * @param endIndex * the end index, exclusive * @return a subset of rows. * * @throws IndexOutOfBoundsException * if index is not within range of row indices */ public VirtualTableRow[] getRows(final int startIndex, final int endIndex) throws IndexOutOfBoundsException { return this.data.get(startIndex,endIndex); } /** * Returns the last row of this {@link VirtualTable}. * * @return the last {@link VirtualTableRow} */ public VirtualTableRow getLastRow() { return this.data.get(this.data.size() - 1); } /** * Returns the {@link VirtualTableRow} for the specified primary key values. * * @param pkValues * {@link KeyValues} instance with the primary key values of the * row * * @return a {@link VirtualTableRow}, or null if no row is * found */ public VirtualTableRow getRow(final KeyValues pkValues) { for(int i = 0; i < this.data.size(); i++) { final VirtualTableRow row = this.data.get(i); if(pkValues.equals(row)) { return row; } } return null; } /** * Returns a {@link XdevList} representing the row at the current cursor * position. *

* This is an alias for {@link #getRowAsList(int)}. *

* * @return a {@link XdevList} */ public XdevList getRowAsList() { return getRowAsList(this.cursorpos); } /** * Returns a {@link XdevList} representing a specified row. *

* This is an alias for {@link #getRow(int, int)}. *

* * @param row * the index of the row to be returned as a XdevList * @return a {@link XdevList} */ public XdevList getRowAsList(final int row) { return getRow(row,-1); } /** * Returns a {@link XdevList} representing a specified row. *

* This is an alias for {@link #getRowData(int, int)}. *

* * @param row * the index of the row to be returned as a XdevList * @param exceptCol * the index of a column to be excluded from the XdevList * @return a {@link XdevList} */ private XdevList getRow(final int row, final int exceptCol) { return getRowData(row,exceptCol); } /** * Returns a {@link XdevList} representing a specified row. * * @param row * the index of the row to be returned as a XdevList * @param exceptCol * the index of a column to be excluded from the XdevList * @return a {@link XdevList} */ public XdevList getRowData(final int row, final int exceptCol) { final int columnCount = getColumnCount(); final XdevList rowData = new XdevList(columnCount); for(int col = 0; col < columnCount; col++) { if(col != exceptCol) { rowData.addElement(getValueAt(row,col)); } } return rowData; } /** * Returns a {@link XdevList} with all values for a specified column index. * * @param col * the index of the column to be returned as a list * @return a {@link XdevList} with all values of the specified column. */ public XdevList getColumnData(final int col) { final int rowCount = getRowCount(); final XdevList colData = new XdevList(rowCount); for(int row = 0; row < rowCount; row++) { colData.add(getValueAt(row,col)); } return colData; } /** * Creates and returns a {@link Map} representation of a specified row. The * keys of the map are the column names, the entries are the values of the * row. * * @return a {@link Map} for the specified row */ public Map getRowAsMap() { return getRowAsMap(this.cursorpos); } /** * Creates and returns a {@link Map} representation of a specified row. The * keys of the map are the column names, the entries are the values of the * row. * * @param row * the index of the row to return as a map * @return a {@link Map} for the specified row */ public Map getRowAsMap(final int row) { final Map rowData = new HashMap(this.columns.length); fillInMap(row,rowData); return rowData; } /** * Fills the provided {@link Map} with data from the given row. * * @param row * row index to get the data from * @param ht * {@link Map} to fill */ public void fillInMap(final int row, final Map ht) { for(int col = 0; col < this.columns.length; col++) { ht.put(this.columns[col].getName(),getValueAt(row,col)); } } /** * Adds all row data (formatted) of this {@link VirtualTable} to a specified * {@link ItemList}. *

* This is an alias method for * {@link ItemList#setModel(VirtualTable, String, String)} *

* * @param list * {@link ItemList} * * @param itemCol * columnname to fill item from or string with * variables like "{%SURNAME} {%NAME} - {%AGE}" * * @param dataCol * columnname to fill data from * * @see ItemList#setModel(VirtualTable, String, String) */ public void fillInItemList(final ItemList list, final String itemCol, final String dataCol) { list.setModel(this,itemCol,dataCol); } /** * Returns a {@link List} of {@link String} representations of column values * for a specified column index. * * @param col * the column index * @return the column values as a List of Strings */ public List getColumnAsString(final int col) { final int c = getRowCount(); final List list = new ArrayList(c); for(int i = 0; i < c; i++) { list.add(getFormattedValueAt(i,col)); } return list; } /** * Sorts the data of a {@link VirtualTable} for a specified column name. The * sort order depends on the {@link Comparator} of the selected * {@link VirtualTableColumn}. * * @param columnName * the name of the column to sort * @param ascending * if set to true, sort order will be ascending, * else descending */ public void sortByCol(final String columnName, final boolean ascending) { final int col = getColumnIndex(columnName); if(col != -1) { sortByCol(col,ascending); } } /** * Sorts the data of a {@link VirtualTable} for a specified column name. The * sort order depends on the {@link Comparator} of the selected * {@link VirtualTableColumn}. * * @param col * the index of the column to sort * @param ascending * if set to true, sort order will be ascending, * else descending */ public void sortByCol(final int col, final boolean ascending) { this.data.sortByCol(col,ascending); } /** * Sorts the data of a {@link VirtualTable} The sort order depends on the * specified {@link Comparator}. * * @param comparator * a {@link Comparator} of Type {@link VirtualTableRow} for * specifying a custom sort order */ public void sort(final Comparator comparator) { this.data.sortByCol(comparator); } /** * Creates a new, empty {@link VirtualTableRow} and adds it at the end of * this {@link VirtualTable} */ public VirtualTableRow addRow() { final VirtualTableRow row = createRowForInsert(null); this.data.add(row,true); fireRowInserted(row,row.index); return row; } /** * Creates a new {@link VirtualTableRow} and adds it to this * {@link VirtualTable}. *

* Note: The order of the values has to match the order of the * columns in the {@link VirtualTable}. *

* * * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * * @param values * a number of values to be inserted as a new row * * @throws VirtualTableException * thrown if a column of the map was not found in the * {@link VirtualTable} * * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final boolean synchronizeDB, final Object... values) throws VirtualTableException, DBException { final Map ht = new HashMap(this.columns.length); for(int ci = 0, li = 0; ci < this.columns.length && li < values.length; ci++) { if(!this.columns[ci].isAutoIncrement()) { ht.put(this.columns[ci].getName(),values[li++]); } } return addRow(ht,synchronizeDB); } /** * Creates a new {@link VirtualTableRow} and adds it to this * {@link VirtualTable}. *

* Note: The order of the entries in the list has to * match the order of the columns in the {@link VirtualTable}. *

* * @param list * a {@link List} of values to be inserted as a new row * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * @throws VirtualTableException * thrown if a column of the map was not found in the * {@link VirtualTable} * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final List list, final boolean synchronizeDB) throws VirtualTableException, DBException { final Map ht = new HashMap(this.columns.length); for(int ci = 0, li = 0; ci < this.columns.length && li < list.size(); ci++) { if(!this.columns[ci].isAutoIncrement()) { ht.put(this.columns[ci].getName(),list.get(li++)); } } return addRow(ht,synchronizeDB); } /** * Creates a new {@link VirtualTableRow} and adds it to this * {@link VirtualTable}. * * @param form * a {@link Formular}, which values are to be added as a new row. * * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * * @throws VirtualTableException * thrown if a column of the map was not found in the * {@link VirtualTable} * * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final Formular form, final boolean synchronizeDB) throws VirtualTableException, DBException { return addRow(form.getData(true),synchronizeDB); } /** * Creates a new {@link VirtualTableRow} and adds it to this * {@link VirtualTable}. *

* This is an alias for addRow(map,synchronizeDB,false). *

* * @param map * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the new * row. * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * @throws VirtualTableException * thrown if a column of the map was not found in the * {@link VirtualTable} * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final Map map, final boolean synchronizeDB) throws VirtualTableException, DBException { return addRow(map,synchronizeDB,false); } /** * Creates a new {@link VirtualTableRow} and adds it to this * {@link VirtualTable}. *

* This is an alias for * addRow(null,map,synchronizeDB,ignoreWarnings). *

* * @param map * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the new * row. * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * @param ignoreWarnings * if set to true, warnings because of unmapped * columns get ignored. * @throws VirtualTableException * thrown if a column of the map was not found in the * {@link VirtualTable} and ignoreWarning is set to * false * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final Map map, final boolean synchronizeDB, final boolean ignoreWarnings) throws VirtualTableException, DBException { return addRow(null,map,synchronizeDB,ignoreWarnings); } /** * Adds the {@link VirtualTableRow} row to this {@link VirtualTable}. *

* This is an alias for addRow(row,synchronizeDB,false). *

* * @param row * the row to add * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final VirtualTableRow row, final boolean synchronizeDB) throws VirtualTableException, DBException { return addRow(row,synchronizeDB,false); } /** * Adds the {@link VirtualTableRow} row to this {@link VirtualTable}. * * @param row * the row to add * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * @param ignoreWarnings * if set to true, warnings because of unmapped * columns get ignored. * @throws VirtualTableException * if row doesn't belong to this * {@link VirtualTable}. * @throws DBException * thrown if synchronization with database failed. */ public VirtualTableRow addRow(final VirtualTableRow row, final boolean synchronizeDB, final boolean ignoreWarnings) throws VirtualTableException, DBException { return addRow(row,null,synchronizeDB,ignoreWarnings); } /** * Adds a new {@link VirtualTableRow} to this {@link VirtualTable}. * * @param row * the {@link VirtualTableRow} to add. If it is null * , a new VirtualTableRow will be created. * @param map * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the new * row. * @param synchronizeDB * if set to true, the new row will be inserted in * the database automatically. * @param ignoreWarnings * if set to true, warnings because of unmapped * columns get ignored. * @throws VirtualTableException * thrown if a column of the map was not found in the * {@link VirtualTable} and ignoreWarning is set to * false * @throws VirtualTableException * if row doesn't belong to this * {@link VirtualTable}. * @throws DBException * thrown if synchronization with database failed. */ private VirtualTableRow addRow(final VirtualTableRow row, final Map map, final boolean synchronizeDB, final boolean ignoreWarnings) throws VirtualTableException, DBException { return addRow(row,map,synchronizeDB,ignoreWarnings,null); } private VirtualTableRow addRow(VirtualTableRow row, Map map, final boolean synchronizeDB, final boolean ignoreWarnings, final DBConnection connection) throws VirtualTableException, DBException { INSERT insert = null; List values = null; if(synchronizeDB) { insert = new INSERT().INTO(this); values = new ArrayList(); } if(row != null) { if(row.getVirtualTable() != this) { if(!row.getVirtualTable().getName().equals(getName())) { throw new VirtualTableException(this, "The row does not belong to this VirtualTable"); } map = row.toMap(); row = createRow(); } } else { final List> used = new ArrayList(); for(int i = 0; i < this.columns.length; i++) { final Object key = getKey(this.columns[i].getName(),map); if(key != null) { used.add(this.columns[i]); } } row = createRowForInsert(used); } if(map != null) { for(int i = 0; i < this.columns.length; i++) { final Object key = getKey(this.columns[i].getName(),map); if(key != null) { final VirtualTableColumn column = this.columns[i]; Object value = map.remove(key); if(value == null) { if(column.isAutoIncrement()) { value = getNextAutoVal(column); } else { value = column.getDefaultValue(); } } value = checkValue(i,value); if(synchronizeDB && !column.isAutoIncrement() && column.isPersistent()) { insert.assign(column,"?"); values.add(value); } row.set(i,value,false,false); } } if(map.size() > 0 && !ignoreWarnings) { final StringBuffer sb = new StringBuffer("Column(s) not found: "); sb.append(StringUtils.concat(", ",map.keySet())); throw new VirtualTableException(sb.toString()); } } else if(synchronizeDB) { for(int i = 0; i < this.columns.length; i++) { final VirtualTableColumn column = this.columns[i]; if(synchronizeDB && !column.isAutoIncrement() && column.isPersistent()) { insert.assign(column,"?"); values.add(row.get(i)); } } } boolean markAsAdded = true; try { if(synchronizeDB && values.size() > 0) { final Object[] params = values.toArray(new Object[values.size()]); final WriteResult result = writeDB(connection,insert,getAutoValueColumns(),params); try { if(result.hasGeneratedKeys()) { row.updateKeys(result.getGeneratedKeys()); } } finally { result.close(); } markAsAdded = false; row.rowState = RowState.UNCHANGED; } } finally { this.data.add(row,markAsAdded); fireRowInserted(row,row.index); } return row; } /** * Removes a specified row from this {@link VirtualTable}. *

* The corresponding row in the database table gets deleted too. *

*

* This is a synonym for: removeRow(row,true); *

* * @param row * the index of the row to remove * @throws VirtualTableException * thrown if no primary key is specified for this * {@link VirtualTable}. * @throws DBException * thrown if deletion of row in database failed. */ public void deleteRow(final int row) throws VirtualTableException, DBException { removeRow(row,true); } /** * Removes a specified row from this {@link VirtualTable}. * * @param row * the index of the row to remove * @param synchronizeDB * if set to true, the corresponding row in the * database table gets deleted too. * @throws VirtualTableException * thrown if no primary key is specified for this * {@link VirtualTable}. * @throws DBException * thrown if deletion of row in database failed. */ public void removeRow(final int row, final boolean synchronizeDB) throws VirtualTableException, DBException { final VirtualTableColumn[] pk = getPrimaryKeyColumns(); if(synchronizeDB && pk.length == 0) { throw new VirtualTableException(this,"No primary key specified"); } final KeyValues pkValue = new KeyValues(this.data.get(row)); this.data.remove(row); if(synchronizeDB) { deleteDB(pkValue); } else { this.removedRowKeys.add(pkValue); } } /** * Removes rows from the {@link VirtualTable} as specified by a * {@link KeyValues} object. *

* The corresponding rows in the database table get deleted too. *

*

* This is a synonym for: removeRows(keyValues,true); *

* * @param keyValues * the {@link KeyValues} object specifying the condition for the * removal of rows. * * @throws VirtualTableException * * @throws DBException * thrown if deletion of row in database failed. */ public void deleteRows(final KeyValues keyValues) throws VirtualTableException, DBException { removeRows(keyValues,true); } /** * Removes rows from the {@link VirtualTable} as specified by a * {@link KeyValues} object. * * @param keyValues * the {@link KeyValues} object specifying the condition for the * removal of rows. * * @param synchronizeDB * if set to true, the corresponding rows in the * database table get deleted too. * * @throws VirtualTableException * * @throws DBException * thrown if deletion of row in database failed. */ public void removeRows(final KeyValues keyValues, final boolean synchronizeDB) throws VirtualTableException, DBException { checkKeyValues(keyValues); for(int r = getRowCount() - 1; r >= 0; r--) { if(keyValues.equals(this.data.get(r))) { this.data.remove(r); } } if(synchronizeDB) { deleteDB(keyValues); } else { this.removedRowKeys.add(keyValues); } } private void deleteDB(final KeyValues keyValues) throws DBException, VirtualTableException { if(keyValues.isEmpty()) { throw new VirtualTableException(this, "Cannot synchronize with database: No primary key defined"); } final WHERE where = new WHERE(); final List values = new ArrayList(); keyValues.appendCondition(where,values); final DELETE delete = new DELETE().FROM(this).WHERE(where); final Object[] params = values.toArray(new Object[values.size()]); writeDB(null,delete,false,params); } /** * Returns the primary key values of the removed rows since the last * synchronization.
* If no row was deleted an empty array is returned. *

* This values are used by the {@link #synchronizeChangedRows()} methods to * remove the data from the underlying datasource. * * @return the primary key values of the removed rows since the last sync. * * @see #synchronizeChangedRows() * @see RowState * * @since 3.1 */ public KeyValues[] getRemovedRowsKeyValues() { return this.removedRowKeys.toArray(new KeyValues[this.removedRowKeys.size()]); } /** * Updates rows from the {@link VirtualTable} as specified by a * {@link Formular} . * * @param form * {@link Formular}, which values are to be used to update the * rows. * @param keyValues * the {@link KeyValues} object specifying the condition for the * rows to update. * @param synchronizeDB * if set to true, the corresponding rows in the * database table get updated too. * @return the number of updated rows * @throws VirtualTableException * @throws DBException * thrown if the update of rows in database failed. */ public int updateRows(final Formular form, final KeyValues keyValues, final boolean synchronizeDB) throws VirtualTableException, DBException { return updateRows(form.getData(true),keyValues,synchronizeDB); } /** * Updates rows from the {@link VirtualTable} as specified by a * {@link KeyValues} object. *

* Note: The order of the items in the list has to match the order of * the columns in the {@link VirtualTable}. *

* * @param list * a {@link Collection} of values containing the new values for * the updated columns * * @param keyValues * the {@link KeyValues} object specifying the condition for the * rows to update. * * @param synchronizeDB * if set to true, the corresponding rows in the * database table get updated too. * * @return the number of updated rows * * @throws VirtualTableException * * @throws DBException * thrown if the update of rows in database failed. */ public int updateRows(final Collection list, final KeyValues keyValues, final boolean synchronizeDB) throws VirtualTableException, DBException { final Map map = new HashMap(); final Object[] values = list.toArray(); final int max = Math.min(values.length,this.columns.length); for(int i = 0; i < max; i++) { map.put(this.columns[i].getName(),values[i]); } return updateRows(map,keyValues,synchronizeDB); } /** * Updates rows from the {@link VirtualTable} as specified by a * {@link KeyValues} object. * * @param map * a {@link Map} of {@link String} keys and {@link Object} * values, containing the column names and values for the updated * row. * @param keyValues * the {@link KeyValues} object specifying the condition for the * rows to update. * @param synchronizeDB * if set to true, the corresponding rows in the * database table get updated too. * @return the number of updated rows * @throws VirtualTableException * @throws DBException * thrown if the update of rows in database failed. */ public int updateRows(final Map map, final KeyValues keyValues, final boolean synchronizeDB) throws VirtualTableException, DBException { return updateRows(map,keyValues,synchronizeDB,null); } private int updateRows(final Map map, final KeyValues keyValues, final boolean synchronizeDB, final DBConnection connection) throws VirtualTableException, DBException { checkKeyValues(keyValues); final IntList updatedRows = new IntList(); final int max = getRowCount(); for(int row = 0; row < max; row++) { if(keyValues.equals(this.data.get(row))) { updatedRows.add(row); for(int c = 0; c < this.columns.length; c++) { if(!this.columns[c].isAutoIncrement()) { final String key = getKey(this.columns[c].getName(),map); if(key != null) { setValueAt(map.get(key),row,c); } } } } } final int updateCount = updatedRows.size(); if(synchronizeDB && updateCount > 0) { final UPDATE update = new UPDATE(this); final List values = new ArrayList(); boolean write = false; for(int c = 0; c < this.columns.length; c++) { final VirtualTableColumn column = this.columns[c]; if(column.isPersistent() && !column.isAutoIncrement()) { final Object key = getKey(column.getName(),map); if(key != null) { write = true; update.SET(column.toSqlField(),"?"); values.add(map.get(key)); } } } if(write) { final WHERE where = new WHERE(); keyValues.appendCondition(where,values); update.WHERE(where); final Object[] params = values.toArray(new Object[values.size()]); writeDB(connection,update,false,params); for(int i = 0; i < updateCount; i++) { this.data.get(updatedRows.get(i)).rowState = RowState.UNCHANGED; } } } return updateCount; } /** * Updates a row from the {@link VirtualTable} as specified by a row index. * * @param map * a {@link Map} of {@link String} keys and {@link Object} * values, containing the column names and values for the updated * row. * @param row * the index of the row to update * @param synchronizeDB * if set to true, the corresponding row in the * database table gets updated too. * * @throws VirtualTableException * * @throws DBException * thrown if deletion of row in database failed. */ public void updateRow(final Map map, final int row, final boolean synchronizeDB) throws VirtualTableException, DBException { updateRow(map,row,synchronizeDB,null); } private void updateRow(final Map map, final int row, final boolean synchronizeDB, final DBConnection connection) throws VirtualTableException, DBException { if(row < 0 || row >= this.data.size) { throw new IndexOutOfBoundsException( "Row index (" + row + ") out of range in '" + this.name + "'"); } final VirtualTableRow vtRow = getRow(row); if(vtRow.getRowState() == RowState.ADDED && synchronizeDB) { // row is in vt, but not in db -> INSERT final INSERT insert = new INSERT().INTO(this); final List values = new ArrayList(); for(int c = 0; c < this.columns.length; c++) { final VirtualTableColumn cd = this.columns[c]; if(cd.isPersistent() && !cd.isAutoIncrement()) { final Object key = getKey(cd.getName(),map); if(key != null) { setValueAt(map.get(key),row,c,false,false); } if(synchronizeDB) { insert.assign(cd,"?"); values.add(vtRow.get(c)); } } } final Object[] params = values.toArray(new Object[values.size()]); final WriteResult result = writeDB(connection,insert,getAutoValueColumns(),params); try { if(result.hasGeneratedKeys()) { vtRow.updateKeys(result.getGeneratedKeys()); } } finally { result.close(); } vtRow.rowState = RowState.UNCHANGED; } else if(map != null && map.size() > 0) { UPDATE update = null; List values = null; if(synchronizeDB) { update = new UPDATE(this); values = new ArrayList(); } boolean write = false; for(int c = 0; c < this.columns.length; c++) { final VirtualTableColumn cd = this.columns[c]; if(cd.isPersistent() && !cd.isAutoIncrement()) { final Object key = getKey(cd.getName(),map); if(key != null) { final Object val = setValueAt(map.get(key),row,c,false,false); write = true; if(synchronizeDB) { update.SET(cd.toSqlField(),"?"); values.add(val); } } } } if(write && synchronizeDB) { final VirtualTableColumn[] pk = getPrimaryKeyColumns(); if(pk.length == 0) { throw new VirtualTableException(this, "Cannot synchronize with database: No primary key defined"); } final WHERE where = new WHERE(); for(int i = 0; i < pk.length; i++) { where.and(pk[i].eq("?")); values.add(getValueAt(row,getColumnIndex(pk[i]))); } update.WHERE(where); final Object[] params = values.toArray(new Object[values.size()]); writeDB(connection,update,false,params); this.data.get(row).rowState = RowState.UNCHANGED; } } fireRowUpdated(this.data.get(row),row); } private void checkKeyValues(final KeyValues values) throws VirtualTableException { if(!equals(values.getVirtualTable())) { throw new IllegalArgumentException("KeyValues does not refer to this VirtualTable"); } if(values.isEmpty()) { throw new IllegalArgumentException("KeyValues are empty"); } } /** * Synchronizes all added, changed and removed rows with the current data * source. *

* This action is embedded in a transaction, meaning a rollback is performed * if an error occurs. * * @throws VirtualTableException * If a error in this VirtualTable occurs * @throws DBException * If an error occurs while writing the database * * @see #synchronizeChangedRows(DBConnection) * @see RowState * @see #getRemovedRowsKeyValues() */ public void synchronizeChangedRows() throws VirtualTableException, DBException { synchronizeChangedRows(null); } public void synchronizeChangedLockedRows(final List updateRows) throws VirtualTableException, DBException { synchronizeChangedRows(null,this.columns,true,true,true,updateRows); } /** * Synchronizes electively added, changed and removed rows with the current * data source. *

* This action is embedded in a transaction, meaning a rollback is performed * if an error occurs. * * @param doInserts * true if added rows should be synchronized * @param doUpdates * true if changed rows should be synchronized * @param doDeletes * true if removed rows should be synchronized * * @throws VirtualTableException * If a error in this VirtualTable occurs * @throws DBException * If an error occurs while writing the database * @throws IllegalArgumentException * If none of doInserts, doUpdates or * doDeletes is true * * @see #synchronizeChangedRows(DBConnection, boolean, boolean, boolean) * @see RowState * @see #getRemovedRowsKeyValues() * * @since 3.1 */ public void synchronizeChangedRows(final boolean doInserts, final boolean doUpdates, final boolean doDeletes) throws VirtualTableException, DBException, IllegalArgumentException { synchronizeChangedRows(null,this.columns,doInserts,doUpdates,doDeletes,null); } /** * Synchronizes all added, changed and removed rows via * connection. *

* If you want to embed this action in a transaction automatically, use * {@link #synchronizeChangedRows()}. * * @param connection * A connection to the underlying data source. * @throws VirtualTableException * If a error in this VirtualTable occurs * @throws DBException * If an error occurs while writing the database * * @see #synchronizeChangedRows() * @see RowState * @see #getRemovedRowsKeyValues() */ public void synchronizeChangedRows(final DBConnection connection) throws VirtualTableException, DBException { synchronizeChangedRows(connection,this.columns,true,true,true,null); } /** * Synchronizes electively added, changed and removed rows via * connection. *

* If you want to embed this action in a transaction automatically, use * {@link #synchronizeChangedRows()}. * * @param connection * A connection to the underlying data source. * @param doInserts * true if added rows should be synchronized * @param doUpdates * true if changed rows should be synchronized * @param doDeletes * true if removed rows should be synchronized * @throws VirtualTableException * If a error in this VirtualTable occurs * @throws DBException * If an error occurs while writing the database * @throws IllegalArgumentException * If none of doInserts, doUpdates or * doDeletes is true * * @see #synchronizeChangedRows() * @see RowState * @see #getRemovedRowsKeyValues() * * @since 3.1 */ public void synchronizeChangedRows(final DBConnection connection, final boolean doInserts, final boolean doUpdates, final boolean doDeletes) throws VirtualTableException, DBException, IllegalArgumentException { synchronizeChangedRows(connection,this.columns,doInserts,doUpdates,doDeletes,null); } private void synchronizeChangedRows(final DBConnection connection, VirtualTableColumn[] columns, final boolean doInserts, final boolean doUpdates, final boolean doDeletes, final List updateRows) throws VirtualTableException, DBException, IllegalArgumentException { if(!(doInserts || doUpdates || doDeletes)) { throw new IllegalArgumentException( "At least one of doInserts, doUpdates or doDeletes must be true"); } final VirtualTableColumn[] pk = getPrimaryKeyColumns(); if(pk.length == 0) { throw new VirtualTableException(this,"Cannot synchronize: No primary key defined"); } final int[] pkIndices = getColumnIndices(pk); // remove non persistant columns final List list = new ArrayList(); for(final VirtualTableColumn column : columns) { if(column.isPersistent()) { list.add(column); } } columns = new VirtualTableColumn[list.size()]; list.toArray(columns); final int[] colIndices = getColumnIndices(columns); final List updateStatements = new ArrayList(); final List insertStatements = new ArrayList(); final List deleteStatements = new ArrayList(); final IntList updateIndices = new IntList(); final IntList insertIndices = new IntList(); if(doUpdates || doInserts) { for(int i = 0; i < this.data.size(); i++) { final VirtualTableRow r = this.data.get(i); if(updateRows == null || updateRows.contains(i)) { if(r.rowState == RowState.UPDATED && doUpdates) { final UPDATE update = new UPDATE(this); final List values = new ArrayList(); for(int c = 0; c < columns.length; c++) { final VirtualTableColumn cd = columns[c]; // Don't update auto incremented primary key columns if(!(Arrays.binarySearch(pkIndices,c) >= 0 && cd.isAutoIncrement())) { update.SET(cd.toSqlField(),"?"); values.add(r.get(colIndices[c])); } } final WHERE where = new WHERE(); for(int pi = 0; pi < pk.length; pi++) { where.and(pk[pi].eq("?")); values.add(r.get(pkIndices[pi])); } update.WHERE(where); updateStatements.add(new WriteRequest(update,false,values.toArray())); updateIndices.add(i); } else if(r.rowState == RowState.ADDED && doInserts) { final INSERT insert = new INSERT().INTO(this); final List values = new ArrayList(); for(int c = 0; c < columns.length; c++) { final VirtualTableColumn cd = columns[c]; if(!cd.isAutoIncrement()) { insert.assign(cd,"?"); values.add(r.get(colIndices[c])); } } insertStatements.add( new WriteRequest(insert,getAutoValueColumns(),values.toArray())); insertIndices.add(i); } } } } if(doDeletes) { for(final KeyValues keyValues : this.removedRowKeys) { final DELETE delete = new DELETE().FROM(this); final List values = new ArrayList(); final WHERE where = new WHERE(); keyValues.appendCondition(where,values,this); delete.WHERE(where); deleteStatements.add(new WriteRequest(delete,false,values.toArray())); } } class Writer { void write(final DBConnection connection) throws DBException { int statementCount = updateStatements.size(); if(statementCount > 0) { final WriteRequest[] updates = updateStatements .toArray(new WriteRequest[statementCount]); writeDB(connection,updates); for(int i = 0; i < statementCount; i++) { VirtualTable.this.data .get(updateIndices.get(i)).rowState = RowState.UNCHANGED; } } statementCount = insertStatements.size(); if(statementCount > 0) { final WriteRequest[] inserts = insertStatements .toArray(new WriteRequest[statementCount]); int insertIndex = 0; for(final WriteResult result : writeDB(connection,inserts)) { if(result.hasGeneratedKeys()) { final Result rs = result.getGeneratedKeys(); final int columnCount = rs.getColumnCount(); try { while(rs.next()) { final int row = insertIndices.get(insertIndex++); for(int c = 0; c < columnCount; c++) { int col = getColumnIndex(rs.getMetadata(c).getName()); if(col == -1 && columnCount == 1) { col = getAutoValue(c); } if(col != -1) { setValueAt(rs.getObject(c),row,col,false,false,false); } } } } finally { result.close(); } } } for(int i = 0; i < statementCount; i++) { VirtualTable.this.data .get(insertIndices.get(i)).rowState = RowState.UNCHANGED; } } statementCount = deleteStatements.size(); if(statementCount > 0) { final WriteRequest[] deletes = deleteStatements .toArray(new WriteRequest[statementCount]); writeDB(connection,deletes); VirtualTable.this.removedRowKeys.clear(); } } } final Writer writer = new Writer(); if(connection != null) { writer.write(connection); } else { new Transaction(getDataSource()) { @Override protected void write(final DBConnection connection) throws DBException { writer.write(connection); } }.execute(); } } private WriteResult writeDB(DBConnection connection, final WritingQuery query, final String[] columnNames, final Object... params) throws DBException { boolean close = false; if(connection == null) { connection = getDataSource().openConnection(); close = true; } try { return connection.write(query,columnNames,params); } finally { if(close) { connection.close(); } } } private WriteResult writeDB(DBConnection connection, final WritingQuery query, final boolean returnGeneratedKeys, final Object... params) throws DBException { boolean close = false; if(connection == null) { connection = getDataSource().openConnection(); close = true; } try { return connection.write(query,returnGeneratedKeys,params); } finally { if(close) { connection.close(); } } } private WriteResult[] writeDB(final DBConnection connection, final WriteRequest... requests) throws DBException { final WriteResult[] results = new WriteResult[requests.length]; for(int i = 0; i < requests.length; i++) { results[i] = requests[i].execute(connection); } return results; } /** * Removes a {@link VirtualTableColumn} from this {@link VirtualTable}. * * @param columnName * the name of the column to be removed. * * @throws VirtualTableException * if no column with this name was found. */ public void removeColumn(final String columnName) throws VirtualTableException { final int col = getColumnIndex(columnName); if(col < 0) { throw new VirtualTableException(this,"Column '" + columnName + "' not found"); } removeColumn(col); } /** * Removes a {@link VirtualTableColumn} from this {@link VirtualTable}. * * @param col * the index of the column to be removed. * * @throws ArrayIndexOutOfBoundsException * if col < -1 or * col > getColumnCount-1 */ public void removeColumn(final int col) throws ArrayIndexOutOfBoundsException { final VirtualTableColumn vtCol = getColumnAt(col); vtCol.setVirtualTable(null); this.columnNameToIndex.clear(); this.columnSimpleNameToIndex.clear(); this.autoValueColumns = null; this.columns = ArrayUtils.remove(VirtualTableColumn.class,this.columns,col); // maxVal = ArrayUtils.remove(maxVal,col); this.columnFlags = ArrayUtils.remove(this.columnFlags,col); this.columnToIndex.remove(col); for(final VirtualTableIndex index : this.indices) { index.removeCol(vtCol); } this.data.removeColumn(col); fireStructureChanged(); } private String getKey(final String key, final Map map) { if(map != null) { for(final String retKey : map.keySet()) { String s = retKey; final int i = s.lastIndexOf('.'); if(i > 0) { s = s.substring(i + 1); } if(s.equalsIgnoreCase(key)) { return retKey; } } } return null; } /** * Returns the index of the nth column in this {@link VirtualTable} that is * auto incremented. *

* Defaults to the value of the first auto incremented column, if none war * found for nr. *

* * @param nr * the nr of the auto incremented column, which index should be * returned * @return a column index, or -1 if no auto incremented column was found. */ public int getAutoValue(final int nr) { int found = 0; for(int i = 0; i < this.columns.length; i++) { if(this.columns[i].isAutoIncrement()) { if(found == nr) { return i; } else { found++; } } } return getFirstAutoValue(); } /** * Returns the index of the first column in this {@link VirtualTable} that * is auto incremented. * * @return a column index, or -1 if no auto incremented column was found. */ public int getFirstAutoValue() { for(int i = 0; i < this.columns.length; i++) { if(this.columns[i].isAutoIncrement()) { return i; } } return -1; } /** * Returns an array of column names for all auto incremented columns. * * @return array of column names */ public String[] getAutoValueColumns() { if(this.autoValueColumns == null) { final List list = new ArrayList(); for(int i = 0; i < this.columns.length; i++) { if(this.columns[i].isAutoIncrement()) { list.add(this.columns[i].getName()); } } this.autoValueColumns = list.toArray(new String[list.size()]); } return this.autoValueColumns; } /** * Checks and returns a value. *

* Checks for: *

    *
  • null value for NotNull columns
  • *
  • correct type according to the specified column
  • *
*

*

* {@link Validator}s of the {@link VirtualTableColumn} at * columnIndex are invoked too. *

* * @param columnIndex * the index of the column for the value * * @param value * the value to be checked * * @return the checked value * * @throws VirtualTableException * if check failed or the value is null but the * column isn't nullable * * @see VirtualTableColumn#addValidator(Validator) */ public Object checkValue(final int columnIndex, Object value) throws VirtualTableException { value = checkValue0(columnIndex,value); /* * Added in 3.1, user defined validation */ this.columns[columnIndex].validate(value); return value; } private Object checkValue0(final int columnIndex, final Object value) throws VirtualTableException { final VirtualTableColumn vtColumn = this.columns[columnIndex]; if(value == null) { if(vtColumn.isNullable()) { return null; } else { /* * (22.02.2010 TM)NOTE: extended exception message * "null not allowed" with more details after request for more * detailed exception messages. */ throw new VirtualTableException(this,"null not allowed: column " + columnIndex + " (" + vtColumn.getName() + ") is not nullable but value is null."); } } else if(value.toString().length() == 0 && vtColumn.isNullable() && !vtColumn.getType().isString()) { return null; } return createValue(value,columnIndex); } private Object createValue(final Object value, final int col) throws VirtualTableException { final VirtualTableColumn cd = this.columns[col]; switch(cd.getType()) { case TINYINT: return createByte(col,value); case SMALLINT: return createShort(col,value); case INTEGER: return createInt(col,value); case BIGINT: return createLong(col,value); case REAL: return createFloat(col,value); case FLOAT: case DOUBLE: case NUMERIC: case DECIMAL: return createDouble(col,value); case CHAR: case VARCHAR: case LONGVARCHAR: return createString(col,value); case CLOB: { final XdevClob clob = createClob(col,value); try { clob.readFully(); } catch(final DBException e) { throw new VirtualTableException(this,e); } return clob; } case BINARY: case VARBINARY: case LONGVARBINARY: return createBytes(col,value); case BLOB: { final XdevBlob blob = createBlob(col,value); try { blob.readFully(); } catch(final DBException e) { throw new VirtualTableException(this,e); } return blob; } case DATE: case TIME: case TIMESTAMP: return createDate(col,value); case BOOLEAN: return createBoolean(col,value); } throw new VirtualTableException(this,"Unkown type"); } private Byte createByte(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toByte(value); } catch(final ObjectConversionException oce) { try { final Object o = this.columns[col].getTextFormat().getFormat() .parseObject(value.toString()); if(o instanceof Byte) { return (Byte)o; } else if(o instanceof Number) { return ((Number)o).byteValue(); } } catch(final ParseException pe) { } } throw new VirtualTableException(this, "Not a byte: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private Short createShort(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toShort(value); } catch(final ObjectConversionException oce) { try { final Object o = this.columns[col].getTextFormat().getFormat() .parseObject(value.toString()); if(o instanceof Short) { return (Short)o; } else if(o instanceof Number) { return ((Number)o).shortValue(); } } catch(final ParseException pe) { } } throw new VirtualTableException(this, "Not a short: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private Integer createInt(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toInteger(value); } catch(final ObjectConversionException oce) { try { final Object o = this.columns[col].getTextFormat().getFormat() .parseObject(value.toString()); if(o instanceof Integer) { return (Integer)o; } else if(o instanceof Number) { return ((Number)o).intValue(); } } catch(final ParseException pe) { } } throw new VirtualTableException(this, "Not an int: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private Long createLong(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toLong(value); } catch(final ObjectConversionException oce) { try { final Object o = this.columns[col].getTextFormat().getFormat() .parseObject(value.toString()); if(o instanceof Long) { return (Long)o; } else if(o instanceof Number) { return ((Number)o).longValue(); } } catch(final ParseException pe) { } } throw new VirtualTableException(this, "Not a long: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private Float createFloat(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toFloat(value); } catch(final ObjectConversionException oce) { try { final Object o = this.columns[col].getTextFormat().getFormat() .parseObject(value.toString()); if(o instanceof Float) { return (Float)o; } else if(o instanceof Number) { return ((Number)o).floatValue(); } } catch(final ParseException pe) { } } throw new VirtualTableException(this, "Not a float: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private Double createDouble(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toDouble(value); } catch(final ObjectConversionException oce) { try { final Object o = this.columns[col].getTextFormat().getFormat() .parseObject(value.toString()); if(o instanceof Double) { return (Double)o; } else if(o instanceof Number) { return ((Number)o).doubleValue(); } } catch(final ParseException pe) { } } throw new VirtualTableException(this,"Not a double: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private String createString(final int col, final Object value) throws VirtualTableException { // optimization if(value == null) { return null; } try { final String str = ConversionUtils.toString(value); final int length = this.columns[col].getLength(); int strLength; if(length > 0 && (strLength = str.length()) > length) { if(this.columns[col].isAutoTruncate()) { return str.substring(0,length); } else { throw new VirtualTableException(this, "String length out of bounds, " + strLength + " > " + length + ", in " + getName() + "." + this.columns[col].getName()); } } else { return str; } } catch(final ObjectConversionException e) { throw new VirtualTableException(this,"Not a string: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } } private XdevClob createClob(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toXdevClob(value); } catch(final ObjectConversionException e) { throw new VirtualTableException(this,"Not a CLOB: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } } private byte[] createBytes(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toBytes(value); } catch(final ObjectConversionException e) { throw new VirtualTableException(this,"No bytes: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } } private XdevBlob createBlob(final int col, final Object value) throws VirtualTableException { try { return ConversionUtils.toXdevBlob(value); } catch(final ObjectConversionException e) { throw new VirtualTableException(this,"Not a BLOB: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } } /** * Creates and returns a {@link Date} object, that fits the type of the * specified column. * * @param col * the {@link VirtualTableColumn} to create a date for * * @param value * the Object to create the new {@link Date} from * * @return a {@link Date} object * * @throws VirtualTableException * thrown if the creation of the {@link Date} failed. */ public Date createDate(final VirtualTableColumn col, final Object value) throws VirtualTableException { return createDate(getColumnIndex(col),value); } /** * Creates and returns a {@link Date} object, that fits the type of the * specified column. * * @param col * the index of the {@link VirtualTableColumn} to create a date * for * * @param value * the Object to create the new {@link Date} from * * @return a {@link Date} object * * @throws VirtualTableException * thrown if the creation of the {@link Date} failed. */ private Date createDate(final int col, final Object value) throws VirtualTableException { Date date = null; try { date = ConversionUtils.toDate(value); } catch(final ObjectConversionException oce) { try { date = this.columns[col].getTextFormat().parseDate(value.toString()); } catch(final ParseException pe) { } } if(date != null) { switch(this.columns[col].getType()) { case DATE: return new java.sql.Date(date.getTime()); case TIME: return new java.sql.Time(date.getTime()); case TIMESTAMP: return new java.sql.Timestamp(date.getTime()); } } throw new VirtualTableException(this, "Not a date: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } private Boolean createBoolean(final int col, final Object value) { try { return ConversionUtils.toBoolean(value); } catch(final ObjectConversionException e) { throw new VirtualTableException(this,"Not a boolean: '" + value + "' -> " + this.name + "." + this.columns[col].getName()); } } private class VirtualTableData implements Iterable, Serializable { private static final long serialVersionUID = 5587743513073942290L; VirtualTableRow[] data; int size; VirtualTableData(final int initialCapacity) { this.data = new VirtualTableRow[initialCapacity]; this.size = 0; } synchronized void add(final VirtualTableRow row, final boolean markAsAdded) { tryInsertInIndex(row); ensureCapacity(this.size + 1); row.index = this.size; this.data[this.size++] = row; if(markAsAdded) { row.rowState = RowState.ADDED; } } synchronized void insert(final VirtualTableRow row, final int index, final boolean markAsAdded) { tryInsertInIndex(row); ensureCapacity(this.size + 1); System.arraycopy(this.data,index,this.data,index + 1,this.size - index); this.data[index] = row; this.size++; for(int i = index; i < this.size; i++) { this.data[i].index = i; } if(markAsAdded) { row.rowState = RowState.ADDED; } } void tryInsertInIndex(final VirtualTableRow row) { for(final VirtualTableIndex vtIndex : VirtualTable.this.indices) { if(!vtIndex.put(row) && vtIndex.isUnique) { reportUniqueIndexDoubleValues(row,vtIndex); } } } void ensureCapacity(final int minCapacity) { final int oldCapacity = this.data.length; if(minCapacity > oldCapacity) { final Object oldData[] = this.data; int newCapacity = (oldCapacity * 3) / 2 + 1; if(newCapacity < minCapacity) { newCapacity = minCapacity; } this.data = new VirtualTableRow[newCapacity]; System.arraycopy(oldData,0,this.data,0,this.size); } } synchronized VirtualTableRow get(final int i) { return this.data[i]; } synchronized VirtualTableRow[] getAll() { final VirtualTableRow[] array = new VirtualTableRow[this.size]; System.arraycopy(this.data,0,array,0,this.size); return array; } synchronized VirtualTableRow[] get(final int startIndex, final int endIndex) { final VirtualTableRow[] array = new VirtualTableRow[endIndex - startIndex]; System.arraycopy(this.data,startIndex,array,0,endIndex - startIndex); return array; } synchronized int size() { return this.size; } synchronized VirtualTableRow remove(final int index) { final VirtualTableRow row = this.data[index]; for(final VirtualTableIndex vtIndex : VirtualTable.this.indices) { vtIndex.remove(row); } final int numMoved = this.size - index - 1; if(numMoved > 0) { System.arraycopy(this.data,index + 1,this.data,index,numMoved); } this.data[--this.size] = null; for(int i = index; i < this.size; i++) { this.data[i].index = i; } row.index = -1; fireRowDeleted(row,index); return row; } synchronized void removeColumn(final int col) { for(int i = 0; i < this.size; i++) { this.data[i].remove(col); } for(final VirtualTableIndex vtIndex : VirtualTable.this.indices) { vtIndex.clear(); for(int i = 0; i < this.size; i++) { vtIndex.put(this.data[i]); } } } synchronized void clear() { for(int i = 0; i < this.size; i++) { this.data[i].index = -1; this.data[i] = null; } this.size = 0; } /** * Returns an iterator over all rows of this VirtualTable in their * natural order. *

* Note: The returned iterator doesn't support {@link Iterator#remove()} * . *

* * * @return An interator over the rows */ @Override public Iterator iterator() { return ArrayUtils.getIterator(this.data,0,this.size); } synchronized void sortByCol(final int columnIndex, final boolean ascending) { final Comparator columnComparator = VirtualTable.this.columns[columnIndex] .getComparator(); final Comparator comparator = new Comparator() { @Override public int compare(final VirtualTableRow row1, final VirtualTableRow row2) { int value = columnComparator.compare(row1.data[columnIndex], row2.data[columnIndex]); if(!ascending) { value *= -1; } return value; } }; sortByCol(comparator); } synchronized void sortByCol(final Comparator comparator) { Arrays.sort(this.data,0,this.size,comparator); for(int i = 0; i < this.size; i++) { this.data[i].index = i; } fireDataChanged(); } @Override public String toString() { return Arrays.toString(this.data); } public String[][] putFormattedStrings(final String[][] s, int arrayOffset) { for(int row = 0; row < this.size && arrayOffset < s.length; row++) { s[arrayOffset++] = this.data[row].toFormattedStrings(); } return s; } } /** * State of the {@link VirtualTableRow} in comparison to the underlying * {@link DataSource}. *

* The {@link VirtualTable#synchronizeChangedRows()} methods use this flag * to decide which row has been added or modified. * * @see VirtualTableRow#getRowState() * @see VirtualTableRow#setRowState(RowState) * * @see VirtualTable#synchronizeChangedRows() * @see VirtualTable#getRemovedRowsKeyValues() */ public static enum RowState { /** * The row has the same values as in the underlying datasource */ UNCHANGED, /** * One or more values of the row has been changed */ UPDATED, /** * The row is new and doesn't exist in the underlying datasource */ ADDED } /** * Represents a single row within a {@link VirtualTable}. * * @author XDEV Software Corp. */ public class VirtualTableRow implements Serializable { private static final long serialVersionUID = 136292046887180787L; private Object[] data; private RowState rowState = RowState.UNCHANGED; private int index = -1; String primaryKeyHash; /** * used for cloning */ private VirtualTableRow(final VirtualTableRow original) { this(original.data.length); System.arraycopy(original.data,0,this.data,0,this.data.length); } private VirtualTableRow(final int initialCapacity) { this.data = new Object[initialCapacity]; } /** * Returns a {@link PessimisticLock} related to this row, with the given * locktime as lock duration. *

* This should be used to create a {@link PessimisticLock} instance with * the given lockTime. *

*

* To actually lock this {@link VirtualTableRow} invoke * {@link PessimisticLock#getLock()} *

* * @return a {@link PessimisticLock} instance for this particular row. * @param lockTime * the locks duration. * @throws LockingException * @since 4.0 */ public PessimisticLock getPessimisticLock(final long lockTime) throws LockingException { return LockFactory.getPessimisticLock(this,lockTime); } /** * Returns a {@link PessimisticLock} related to this row. If the lock * does not already exist, a new {@link PessimisticLock} instance with * its default timeout will be created in consequence. *

* To actually lock this {@link VirtualTableRow} invoke * {@link PessimisticLock#getLock()} *

* * @return a {@link PessimisticLock} instance for this particular row. * @throws LockingException * @since 4.0 */ public PessimisticLock getPessimisticLock() throws LockingException { return LockFactory.getPessimisticLock(this); } /** * Returns the surrounding {@link VirtualTable} of this * {@link VirtualTableRow}. * * @return a {@link VirtualTable} */ public VirtualTable getVirtualTable() { return VirtualTable.this; } /** * Sets a value for this {@link VirtualTableRow} at the specified column * index. * * @param columnName * the name of the column * @param value * the new value of the column * * @return the new value * * @throws VirtualTableException * if the value is not appropriate for the specified column * @throws IllegalArgumentException * if the column columnName does not exist in * this {@link VirtualTable} */ public synchronized Object set(final String columnName, final Object value) throws VirtualTableException, IllegalArgumentException { final int col = getColumnIndex(columnName); if(col == -1) { throw new IllegalArgumentException("column '" + columnName + "' not found"); } return set(col,value); } /** * Sets a value for this {@link VirtualTableRow} at the specified column * index. * * @param index * the index of the column to set * @param value * the new value of the column * * @return the new value * * @throws VirtualTableException * if the value is not appropriate for the specified column */ public synchronized Object set(final int index, final Object value) throws VirtualTableException { final int rowIndex = this.index; if(rowIndex >= 0) { return VirtualTable.this.setValueAt(value,rowIndex,index); } else { final Object old = this.data[index]; if(VirtualTable.equals(value,old,getColumnAt(index).getType())) { return old; } return this.data[index] = optConvertValue(checkValue(index,value)); } } private synchronized void set(final int columnIndex, Object value, final boolean changeToUpdated, final boolean updateIndex) throws VirtualTableException { value = optConvertValue(value); if(VirtualTable.equals(this.data[columnIndex],value,getColumnAt(columnIndex).getType())) { return; } /* * only update index if this has been added to VirtualTableData */ if(updateIndex && this.index != -1) { for(final VirtualTableIndex vtIndex : VirtualTable.this.indices) { if(vtIndex.hasCol(columnIndex)) { vtIndex.remove(this); final String newHash = vtIndex.computeHash(this,columnIndex,value); if(!vtIndex.put(newHash) && vtIndex.isUnique) { reportUniqueIndexDoubleValues(this,vtIndex); } if(vtIndex.type == IndexType.PRIMARY_KEY) { this.primaryKeyHash = newHash; } } } } this.data[columnIndex] = value; // if(columns[index].isAutoIncrement() && value != null && value // instanceof Number) // { // long l = ((Number)value).longValue(); // if(l > maxVal[index]) // { // maxVal[index] = l; // } // } if(changeToUpdated && this.rowState == RowState.UNCHANGED) { this.rowState = RowState.UPDATED; } } private Object optConvertValue(Object value) { if(value instanceof byte[]) { value = new XdevBlob((byte[])value); } else if(value instanceof char[]) { value = new XdevClob((char[])value); } if(value != null && value instanceof String && Settings.trimData()) { value = value.toString().trim(); } return value; } /** * Returns the value at the specified column index. * * @param i * the index of the column * @return a value */ public synchronized Object get(final int i) { return this.data[i]; } /** * Returns the value at the specified column. * * @param name * the name of the column * @return a value * @since 3.2 */ public synchronized Object get(final String name) { return this.data[getColumnIndex(name)]; } /** * Returns the value at the specified column. * * @param column * {@link VirtualTableColumn} * @return the value of the cell at the specified position. */ public synchronized T get(final VirtualTableColumn column) { return (T)this.data[getColumnIndex(column)]; } /** * Returns a formatted representation for the value at col. * The TextFormat of the VirtualTableColumn at index col is * used to format the value. * * * @param col * the index of the column (0-based) * * @return a formatted {@link String} representation of the specified * value. */ public synchronized String getFormattedValue(final int col) { return formatValue(this.data[col],col); } /** * Returns a formatted string, with this row used as * {@link ParameterProvider}. * * @param str * the {@link String} to identify the value's in the * {@link ParameterProvider} * * @return a formatted string * * @see StringUtils#format(String, ParameterProvider) * @see VirtualTable#formatValue(Object, int) */ public synchronized String format(final String str) { return StringUtils.format(str,new ParameterProvider() { @Override public String getValue(final String key) { final int index = getColumnIndex(key); if(index == -1) { return ""; } return formatValue(VirtualTableRow.this.data[index],index); } }); } /** * Removes a column from this {@link VirtualTableColumn}. *

* Note: The value at this position is lost. *

* * @param i * the index of the column to be removed */ private synchronized void remove(final int i) { this.data = ArrayUtils.remove(Object.class,this.data,i); } /** * Returns the number of columns of this {@link VirtualTableRow}. * * @return the number of columns */ public int size() { return this.data.length; } /** * Checks if this {@link VirtualTableRow} exists in the * {@link VirtualTable}. * * @return false if this {@link VirtualTableRow} exists in * the {@link VirtualTable}, true otherwise */ public boolean isNew() { return this.index == -1; } /** * Saves a {@link VirtualTableRow}. *

* If the {@link VirtualTableRow} is already part of a * {@link VirtualTable}, an update is performed. Otherwise an insert * will be done. *

* * @param synchronizeDB * if set to true, changes will be propagated to * the underlying data source. * @throws VirtualTableException * @throws DBException * thrown if database access failed. * @since 3.1 */ public void save(final boolean synchronizeDB) throws VirtualTableException, DBException { save(synchronizeDB,null); } /** * Saves a {@link VirtualTableRow}. *

* If the {@link VirtualTableRow} is already part of a * {@link VirtualTable}, an update is performed. Otherwise an insert * will be done. *

* * @param synchronizeDB * if set to true, changes will be propagated to * the underlying data source. * @param connection * An already open connection, e.g. in a transaction, or * null * @throws VirtualTableException * @throws DBException * thrown if database access failed. * @since 3.1 */ public void save(final boolean synchronizeDB, final DBConnection connection) throws VirtualTableException, DBException { if(isNew()) { insert(null,synchronizeDB,connection); } else if(synchronizeDB) { if(this.rowState == RowState.ADDED) { // row is in vt, but not in db -> INSERT final INSERT insert = new INSERT().INTO(VirtualTable.this); final List values = new ArrayList(); for(int c = 0; c < VirtualTable.this.columns.length; c++) { final VirtualTableColumn cd = VirtualTable.this.columns[c]; if(cd.isPersistent() && !cd.isAutoIncrement()) { insert.assign(cd,"?"); values.add(get(c)); } } final Object[] params = values.toArray(new Object[values.size()]); final WriteResult result = writeDB(connection,insert,getAutoValueColumns(), params); try { if(result.hasGeneratedKeys()) { updateKeys(result.getGeneratedKeys()); } } finally { result.close(); } this.rowState = RowState.UNCHANGED; } else { final UPDATE update = new UPDATE(VirtualTable.this); final List values = new ArrayList(); for(int c = 0; c < VirtualTable.this.columns.length; c++) { final VirtualTableColumn cd = VirtualTable.this.columns[c]; if(cd.isPersistent() && !cd.isAutoIncrement()) { update.SET(cd.toSqlField(),"?"); values.add(get(c)); } } final VirtualTableColumn[] pk = getPrimaryKeyColumns(); if(pk.length == 0) { throw new VirtualTableException(VirtualTable.this, "Cannot synchronize with database: No primary key defined"); } final WHERE where = new WHERE(); for(int i = 0; i < pk.length; i++) { where.and(pk[i].eq("?")); values.add(get(getColumnIndex(pk[i]))); } update.WHERE(where); final Object[] params = values.toArray(new Object[values.size()]); writeDB(connection,update,false,params); this.rowState = RowState.UNCHANGED; } } } /** * Saves a {@link VirtualTableRow}. *

* If the {@link VirtualTableRow} is already part of a * {@link VirtualTable}, an update is performed. Otherwise an insert * will be done. *

* * @param data * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the * row. * @param synchronizeDB * if set to true, changes will be propagated to * the underlying data source. * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void save(final Map data, final boolean synchronizeDB) throws VirtualTableException, DBException { save(data,synchronizeDB,null); } /** * Saves a {@link VirtualTableRow}. *

* If the {@link VirtualTableRow} is already part of a * {@link VirtualTable}, an update is performed. Otherwise an insert * will be done. *

* * @param data * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the * row. * @param synchronizeDB * if set to true, changes will be propagated to * the underlying data source. * @param connection * An already open connection, e.g. in a transaction, or * null * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void save(final Map data, final boolean synchronizeDB, final DBConnection connection) throws VirtualTableException, DBException { if(isNew()) { insert(data,synchronizeDB,connection); } else { update(data,synchronizeDB,connection); } } /** * Updates a {@link VirtualTableRow}. * * @param data * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the * row. * @param synchronizeDB * if set to true, changes will be propagated to * database. * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void update(final Map data, final boolean synchronizeDB) throws VirtualTableException, DBException { update(data,synchronizeDB,null); } /** * Updates a {@link VirtualTableRow}. * * @param data * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the * row. * @param synchronizeDB * if set to true, changes will be propagated to * database. * @param connection * An already open connection, e.g. in a transaction, or * null * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void update(final Map data, final boolean synchronizeDB, final DBConnection connection) throws VirtualTableException, DBException { updateRow(data,this.index,synchronizeDB,connection); } /** * Inserts a {@link VirtualTableRow} into its {@link VirtualTable}. * * @param data * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the * row. * @param synchronizeDB * if set to true, changes will be propagated to * database. * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void insert(final Map data, final boolean synchronizeDB) throws VirtualTableException, DBException { insert(data,synchronizeDB,null); } /** * Inserts a {@link VirtualTableRow} into its {@link VirtualTable}. * * @param data * a {@link Map} of {@link String} keys and {@link Object} as * values, containing the column names and values for the * row. * @param synchronizeDB * if set to true, changes will be propagated to * database. * @param connection * An already open connection, e.g. in a transaction, or * null * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void insert(final Map data, final boolean synchronizeDB, final DBConnection connection) throws VirtualTableException, DBException { addRow(this,data,synchronizeDB,false,connection); } /** * Deletes a {@link VirtualTableRow} from its {@link VirtualTable}. * * @param synchronizeDB * if set to true, changes will be propagated to * database. * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void delete(final boolean synchronizeDB) throws VirtualTableException, DBException { removeRow(this.index,synchronizeDB); } /** * Reloads the contents of this {@link VirtualTableRow} from the * database. *

* This is an alias for {@link #reload(DBDataSource)}. *

* * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void reload() throws VirtualTableException, DBException { reload(getDataSource()); } /** * Reloads the contents of this {@link VirtualTableRow} from the * database. * * @param dataSource * the {@link DBDataSource} to be used to do the reloads * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void reload(final DBDataSource dataSource) throws VirtualTableException, DBException { reloadImpl(dataSource,true); } /** * Reloads the contents of this {@link VirtualTableRow} from the * database with the default query. *

* This is an alias for {@link #reloadDefault(DBDataSource)}. *

* * @param dataSource * the {@link DBDataSource} to be used to do the reloads * @throws VirtualTableException * @throws DBException * thrown if database access failed. * @since 5.0 */ public void reloadDefault() throws VirtualTableException, DBException { reloadDefault(getDataSource()); } /** * Reloads the contents of this {@link VirtualTableRow} from the * database with the default query. * * @throws VirtualTableException * @throws DBException * thrown if database access failed. * @since 5.0 */ public void reloadDefault(final DBDataSource dataSource) throws VirtualTableException, DBException { reloadImpl(dataSource,false); } private void reloadImpl(final DBDataSource dataSource, final boolean useLastQuery) throws VirtualTableException, DBException { final KeyValues keyValues = new KeyValues(this); SELECT select; List values; if(useLastQuery && VirtualTable.this.lastQuery != null) { select = VirtualTable.this.lastQuery.getSelect().clone(); values = new XdevList(VirtualTable.this.lastQuery.getParameters()); } else { select = getSelect(); values = new XdevList(); } WHERE where = select.getWhere(); if(where == null) { where = new WHERE(); select.WHERE(where); } keyValues.appendCondition(where,values); final DBConnection connection = dataSource.openConnection(); try { final Result result = connection.query(select,values.toArray()); try { if(!result.next()) { throw new VirtualTableRowDeletedInDbException(this); } for(final VirtualTableIndex vtIndex : VirtualTable.this.indices) { vtIndex.remove(this); } final int[] colIndices = getColumnIndices(result,false); for(int i = 0; i < colIndices.length; i++) { if(colIndices[i] != -1) { final Object val = result.getObject(i); set(colIndices[i],checkValue(colIndices[i],val),false,false); } } for(final VirtualTableIndex vtIndex : VirtualTable.this.indices) { vtIndex.put(this); } } finally { result.close(); } } finally { connection.close(); } } /** * Custom toString Implementation for {@link VirtualTableRow}. *

* The String contains all elements of the VirtualTableRow in a comma * separated list *

* * @return a {@link String} representation of this * {@link VirtualTableRow}. */ @Override public String toString() { return Arrays.toString(this.data); } /** * Returns an array of formatted {@link String} representations for this * {@link VirtualTableRow}. * * @return an array of Strings */ public String[] toFormattedStrings() { final int len = this.data.length; final String[] s = new String[len]; for(int col = 0; col < len; col++) { s[col] = formatValue(this.data[col],col); } return s; } /** * Returns all values of this row as an {@link Map}. The keys of the map * are the column names, the entries are the values. * * @return all values of this row as an {@link Map}. */ public Map toMap() { final Map map = new HashMap( VirtualTable.this.columns.length); for(int col = 0; col < VirtualTable.this.columns.length; col++) { map.put(VirtualTable.this.columns[col].getName(),this.data[col]); } return map; } /** * Returns all values of this row as a {@link XdevList}. * * @return all values of this row as a {@link XdevList}. */ public XdevList toList() { final int c = VirtualTable.this.columns.length; final XdevList list = new XdevList(c); for(int i = 0; i < c; i++) { list.add(this.data[i]); } return list; } /** * Returns all values of this row as an array. * * @return all values of this row as an array. */ public Object[] getValues() { final Object[] values = new Object[VirtualTable.this.columns.length]; System.arraycopy(this.data,0,values,0,VirtualTable.this.columns.length); return values; } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { if(obj == this) { return true; } if(obj instanceof VirtualTableRow) { /* * TODO: Check if comparing primary keys is sufficient */ final VirtualTableRow other = (VirtualTableRow)obj; return getVirtualTable().equals(other.getVirtualTable()) && equalsValues(other); } return false; } /** * Checks if the values of this row are the same as the values of an * other row. * * @param other * The row to compare * @return true if the values of both rows are equal, * false otherwise * @see VirtualTable#equals(Object, Object) */ public boolean equalsValues(final VirtualTableRow other) { final Object[] data1 = this.data; final Object[] data2 = other.data; final int length = data1.length; if(length != data2.length) { return false; } for(int i = 0; i < length; i++) { if(!VirtualTable.equals(data1[i],data2[i],getColumnAt(i).getType())) { return false; } } return true; } /** * Updates the auto value columns with values from * generatedKeys. *

* This method gets called automatically after a row is inserted into * the database. * * @param generatedKeys * the generated key values * @throws DBException * if an database error occurs */ public void updateKeys(final Result generatedKeys) throws DBException { final int columnCount = generatedKeys.getColumnCount(); try { if(generatedKeys.next()) { for(int c = 0; c < columnCount; c++) { int col = getColumnIndex(generatedKeys.getMetadata(c).getName()); if(col == -1 && columnCount == 1) { col = getAutoValue(c); } if(col != -1) { Object generatedKey = generatedKeys.getObject(c); generatedKey = checkValue(col,generatedKey); set(col,generatedKey,false,false); } } } } finally { generatedKeys.close(); } } /** * Returns the state of this row * * @return the state of this row * * @since 3.1 */ public RowState getRowState() { return this.rowState; } /** * Updates the state of this row. * * @param rowState * the new state * * @since 3.1 */ public void setRowState(final RowState rowState) { this.rowState = rowState; } } /** * Hash values are only computed and stored if the index is unique. */ private class VirtualTableIndex implements Serializable { private static final long serialVersionUID = 7320439821022328063L; final Index _originalIndex; final String name; final IndexType type; VirtualTableColumn[] columns; int[] columnIndices; final boolean isUnique; HashSet set; transient Object hashLock; transient LinkedHashMap hashMap; VirtualTableIndex(final Index index) { this._originalIndex = index; this.name = index.getName(); this.type = index.getType(); final String[] columnNames = index.getColumns(); final List columns = new ArrayList(columnNames.length); for(int i = 0; i < columnNames.length; i++) { final VirtualTableColumn col = getColumn(columnNames[i]); if(col != null) { columns.add(col); } } boolean onlyAutoValues = true; final int cc = columns.size(); this.columns = new VirtualTableColumn[cc]; this.columnIndices = new int[cc]; for(int i = 0; i < cc; i++) { this.columns[i] = columns.get(i); this.columnIndices[i] = getColumnIndex(this.columns[i]); if(!this.columns[i].isAutoIncrement()) { onlyAutoValues = false; } } boolean unique = false; if(index.isUnique()) { this.set = new HashSet(); if(!onlyAutoValues) { unique = true; } } this.isUnique = unique; } void removeCol(final VirtualTableColumn column) { final int index = ArrayUtils.indexOf(this.columns,column); if(index >= 0) { this.columns = ArrayUtils.remove(VirtualTableColumn.class,this.columns,index); this.columnIndices = ArrayUtils.remove(this.columnIndices,index); } else // update column indices { for(int i = 0; i < this.columns.length; i++) { this.columnIndices[i] = getColumnIndex(this.columns[i]); } } } boolean hasCol(final int index) { final int[] columnIndices = this.columnIndices; for(int i = 0; i < columnIndices.length; i++) { if(columnIndices[i] == index) { return true; } } return false; } String computeHash(final VirtualTableRow row) { synchronized(getHashLock()) { if(this.hashMap == null) { this.hashMap = new LinkedHashMap<>(); } else { this.hashMap.clear(); } for(int i = 0, c = this.columns.length; i < c; i++) { this.hashMap.put(this.columns[i].getName(),row.data[this.columnIndices[i]]); } final String hash = HashComputer.computeHash(this.hashMap, getAllowMultipleNullsInUniqueIndices()); if(this.type == IndexType.PRIMARY_KEY) { row.primaryKeyHash = hash; } return hash; } } String computeHash(final VirtualTableRow row, final int replacementIndex, final Object replacement) { synchronized(getHashLock()) { if(this.hashMap == null) { this.hashMap = new LinkedHashMap<>(); } else { this.hashMap.clear(); } for(int i = 0, c = this.columns.length; i < c; i++) { final int columnIndex = this.columnIndices[i]; final Object value = columnIndex == replacementIndex ? replacement : row.data[columnIndex]; this.hashMap.put(this.columns[i].getName(),value); } return HashComputer.computeHash(this.hashMap, getAllowMultipleNullsInUniqueIndices()); } } private Object getHashLock() { if(this.hashLock == null) { this.hashLock = new Object(); } return this.hashLock; } /** * @return true if row is new, false otherwise */ boolean put(final VirtualTableRow row) { if(this.set != null) { final String hash = computeHash(row); if(hash == HashComputer.EMPTY_HASH) { return true; } return this.set.add(hash); } return true; } /** * * @param hash * @return true if hash is new, false * otherwise */ boolean put(final String hash) { if(this.set != null) { if(hash == HashComputer.EMPTY_HASH) { return true; } return this.set.add(hash); } return true; } void remove(final VirtualTableRow row) { if(this.set != null) { this.set.remove(computeHash(row)); } } synchronized void clear() { if(this.set != null) { this.set.clear(); } } } /** * @return if multiple null values are allowed in unique * indices * @since 4.1 */ public boolean getAllowMultipleNullsInUniqueIndices() { return this.allowMultipleNullsInUniqueIndices; } /** * Sets if multiple null values are allowed in unique indices. *
* The default is false. * * @param allowMultipleNullsInUniqueIndices * true if multipe nulls are allowed in unique * indices * @since 4.1 */ public void setAllowMultipleNullsInUniqueIndices( final boolean allowMultipleNullsInUniqueIndices) { this.allowMultipleNullsInUniqueIndices = allowMultipleNullsInUniqueIndices; } /** * Returns if double values in unique indices are checked. * * @return true if double values in unique indices are checked, * false otherwise. */ public boolean getCheckUniqueIndexDoubleValues() { return this.checkUniqueIndexDoubleValues; } /** * Sets if double values in unique indices should be checked, meaning an * exception is thrown on an attempt to insert a duplicate value. * * @param checkUniqueIndexDoubleValues */ public void setCheckUniqueIndexDoubleValues(final boolean checkUniqueIndexDoubleValues) { this.checkUniqueIndexDoubleValues = checkUniqueIndexDoubleValues; } private void reportUniqueIndexDoubleValues(final VirtualTableRow row, final VirtualTableIndex index) throws UniqueIndexDuplicateValuesException { if(this.checkUniqueIndexDoubleValues) { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getName()); sb.append(": Duplicate values in unique index "); sb.append(index.name); for(final int columnIndex : index.columnIndices) { sb.append(", "); sb.append(this.columns[columnIndex].getName()); sb.append(" = "); sb.append(row.getFormattedValue(columnIndex)); } throw new UniqueIndexDuplicateValuesException(this,sb.toString()); } } /** * Returns if missing columns in fill actions are allowed. * * @return true if missing columns in a fill action are * allowed, false otherwise. * @since 3.1 */ public boolean getAllowMissingFillColumns() { return this.allowMissingFillColumns; } /** * Sets if missing columns in fill actions are allowed, if not an exception * is thrown. * * @param allowMissingFillColumns * true if missing fill columns in fill action * should be allowed, false otherwise * @since 3.1 */ public void setAllowMissingFillColumns(final boolean allowMissingFillColumns) { this.allowMissingFillColumns = allowMissingFillColumns; } /** * Returns if superfluous columns in fill actions are allowed. * * @return true if superfluous columns in a fill action are * allowed, false otherwise. * @since 3.1 */ public boolean getAllowSuperfluousFillColumns() { return this.allowSuperfluousFillColumns; } /** * Sets if superfluous columns in fill actions are allowed, if not an * exception is thrown. * * @param allowSuperfluousFillColumns * true if superfluous fill columns in fill action * should be allowed, false otherwise * @since 3.1 */ public void setAllowSuperfluousFillColumns(final boolean allowSuperfluousFillColumns) { this.allowSuperfluousFillColumns = allowSuperfluousFillColumns; } private int[] getColumnIndices(final Result result, final boolean checkMissingOrSuperfluousColumns) throws VirtualTableException { final int cc = result.getColumnCount(); final int[] colIndices = new int[cc]; for(int col = 0; col < cc; col++) { String colName = result.getMetadata(col).getCaption(); colIndices[col] = getColumnIndex(colName); if(colIndices[col] == -1) { colName = result.getMetadata(col).getName(); if(colName != null && colName.length() > 0) { colIndices[col] = getColumnIndex(colName); } } } if(checkMissingOrSuperfluousColumns) { if(!this.allowMissingFillColumns) { final List missingColumns = getColumnNames(); for(int col = 0; col < cc; col++) { if(colIndices[col] != -1) { missingColumns.remove(this.columns[colIndices[col]].getName()); } } if(missingColumns.size() > 0) { throw new VirtualTableException(this,"Missing columns in result: " + StringUtils.concat(", ",missingColumns)); } } if(!this.allowSuperfluousFillColumns) { final List superfluousColumns = new ArrayList(); for(int col = 0; col < cc; col++) { if(colIndices[col] == -1) { String colName = result.getMetadata(col).getCaption(); if(colName == null || colName.length() == 0) { colName = result.getMetadata(col).getName(); } superfluousColumns.add(colName); } } if(superfluousColumns.size() > 0) { throw new VirtualTableException(this,"Superfluous columns in result: " + StringUtils.concat(", ",superfluousColumns)); } } } return colIndices; } /** * Returns a {@link Table} for this {@link VirtualTable}. * * @return a {@link Table} * @deprecated the VirtualTable is as SQL Table object itself since 3.2 */ @Deprecated public Table toSqlTable() { return this; } /** * Shortcut to create a new select with optional columns. * *

	 * new SELECT().FROM(VT).columns(...)
	 * ->
	 * VT.SELECT(...)
	 * 
* * @param columns * @return a new {@link SELECT} object * @since 3.2 */ public SELECT SELECT(final Object... columns) { return new SELECT().FROM(this).columns(columns); } /** * Stores the {@link QueryInfo} of the last executed query. * * @param lastQuery * a {@link QueryInfo} */ public void setLastQuery(final QueryInfo lastQuery) { this.lastQuery = lastQuery; } /** * Returns the last executed query. * * @return a {@link QueryInfo} */ public QueryInfo getLastQuery() { if(this.lastQuery != null) { return this.lastQuery.clone(); } return null; } /** * Creates the default {@link SELECT} for this {@link VirtualTable}. * *
	 * SELECT [all columns...] FROM[tableName]
	 * 
* * @return the created {@link SELECT} */ public SELECT getDefaultSelect() { return getDefaultSelect(this.columns); } public SELECT getDefaultSelect(final VirtualTableColumn[] columns) { final SELECT select = new SELECT().FROM(this); for(final VirtualTableColumn col : columns) { if(col.isPersistent()) { select.columns(col); } } return select; } /** * Creates the extended {@link SELECT} for this {@link VirtualTable} with * the joined, non persistent columns. *

* Alias for getExtendedSelect(JoinType.LEFT_JOIN). *

* * @return the created {@link SELECT} * * @throws VirtualTableException * * @see #getExtendedSelect(JoinType) */ public SELECT getExtendedSelect() throws VirtualTableException { return getExtendedSelect(JoinType.LEFT_JOIN); } public SELECT getExtendedSelect(final VirtualTableColumn[] columns) throws VirtualTableException { return getExtendedSelect(JoinType.LEFT_JOIN,columns); } /** * Creates the extended {@link SELECT} for this {@link VirtualTable} with * the joined, non persistent columns. * * @param joinType * the way linked tables should be joined * * @return the created {@link SELECT} */ public SELECT getExtendedSelect(final JoinType joinType) throws VirtualTableException { return getExtendedSelect(joinType,this.columns); } public SELECT getExtendedSelect(final JoinType joinType, final VirtualTableColumn[] columns) throws VirtualTableException { final SELECT select = new SELECT().FROM(this); SqlGenerationContext context = null; for(final VirtualTableColumn column : columns) { final TableColumnLink link = column.getTableColumnLink(); if(link != null) { if(context == null) { context = new SqlGenerationContext(); } link.addColumn(select,this,column,joinType,context); } else if(column.isPersistent()) { select.columns(column); } } return select; } /** * Sets a user-defined {@link SELECT} statement which should be used to fill * this Virtual Table. * * @param select * the user defined select which should be used * * @see #getSelect() * * @since 4.0 */ public void setSelect(final SELECT select) { this.userDefinedSelect = select; } /** * Returns the default {@link SELECT} statement which is used to query the * underlying datasource to fill this VT. *

* If a user-defined statement is set via {@link #setSelect(SELECT)}, this * one is used. *

*

* Otherwise, depending if this {@link VirtualTable} has non persistent * columns, {@link #getExtendedSelect()} respectively * {@link #getDefaultSelect()} is returned. *

* * @return The best fitting select to query data for this VirtualTable * @see #hasNonPersistentColumns() * @see #setSelect(SELECT) */ public SELECT getSelect() { return getSelect(this.columns); } public SELECT getSelect(final VirtualTableColumn[] columns) { if(this.userDefinedSelect != null) { return this.userDefinedSelect; } return hasNonPersistentColumns() ? getExtendedSelect(columns) : getDefaultSelect(columns); } /** * Reloads the contents of this {@link VirtualTable} applying the search * conditions of the last executed query. * * @throws VirtualTableException * @throws DBException * thrown if database access fails */ public void reload() throws VirtualTableException, DBException { if(this.lastQuery != null) { queryAndFill(getDataSource(),this.lastQueryIndices,this.lastQuery.getSelect(), this.lastQuery.getParameters()); } } /** * Executes the default select for this {@link VirtualTable}. *

* This is an alias for * {@link #queryAndFill(DBDataSource, SELECT, Object...)}. *

* * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void queryAndFill() throws VirtualTableException, DBException { queryAndFill(getDataSource(),getSelect()); } /** * Executes the default select for this {@link VirtualTable}. *

* This is an alias for * {@link #queryAndFill(DBDataSource, SELECT, Object...)}. *

* * @param dataSource * the {@link DBDataSource} used for querying the database * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void queryAndFill(final DBDataSource dataSource) throws VirtualTableException, DBException { queryAndFill(dataSource,getSelect()); } /** * Executes the default select for this {@link VirtualTable}. *

* This is an alias for * {@link #queryAndFill(DBDataSource, SELECT, Object...)}. *

* * @param select * the SELECT to be executed for querying the database * @param params * dynamic parameters, that can be used optionally * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void queryAndFill(final SELECT select, final Object... params) throws VirtualTableException, DBException { queryAndFill(getDataSource(),select,params); } /** * Executes the default select for this {@link VirtualTable}. * * @param dataSource * the {@link DBDataSource} used for querying the database * @param select * the SELECT to be executed for querying the database * @param params * dynamic parameters, that can be used optionally * @throws VirtualTableException * @throws DBException * thrown if database access failed. */ public void queryAndFill(final DBDataSource dataSource, final SELECT select, final Object... params) throws VirtualTableException, DBException { queryAndFill(dataSource,null,select,params); } private void queryAndFill(final DBDataSource dataSource, final int[] columnIndices, final SELECT select, final Object... params) throws VirtualTableException, DBException { final DBConnection connection = dataSource.openConnection(); try { final Result result = connection.query(select,params); try { addData(result,columnIndices,VirtualTableFillMethod.OVERWRITE); } finally { result.close(); } } finally { connection.close(); } } /** * Executes a query and appends its results at the end of this * {@link VirtualTable}. *

* This is an alias for {@link #queryAndAppend(DBDataSource, KeyValues)}. *

* * @param pkValue * the {@link KeyValues} for specifying the filter condition * * @return true if values were appended, false * otherwise. * * @throws VirtualTableException * * @throws DBException * thrown if database access failed. */ public boolean queryAndAppend(final KeyValues pkValue) throws VirtualTableException, DBException { return queryAndAppend(getDataSource(),pkValue); } /** * Executes a query and appends its results at the end of this * {@link VirtualTable}. * * @param dataSource * the {@link DBDataSource} used for querying the database * * @param pkValue * the {@link KeyValues} for specifying the filter condition * * @return true if values were appended, false * otherwise. * * @throws VirtualTableException * * @throws DBException * thrown if database access failed. */ public boolean queryAndAppend(final DBDataSource dataSource, final KeyValues pkValue) throws VirtualTableException, DBException { SELECT select; List values; if(this.lastQuery != null) { select = this.lastQuery.getSelect().clone(); values = new XdevList(this.lastQuery.getParameters()); } else { select = getSelect(); values = new XdevList(); } WHERE where = select.getWhere(); if(where == null) { where = new WHERE(); select.WHERE(where); } pkValue.appendCondition(where,values,this); boolean appended = false; final DBConnection connection = dataSource.openConnection(); try { final Result result = connection.query(select,values.toArray()); try { if(result.next()) { final VirtualTableRow row = new VirtualTableRow(this.columns.length); final int[] colIndices = getColumnIndices(result,false); for(int i = 0; i < colIndices.length; i++) { if(colIndices[i] != -1) { final Object val = result.getObject(i); row.set(colIndices[i],checkValue(colIndices[i],val),false,false); } } this.data.add(row,false); appended = true; } } finally { result.close(); } } finally { connection.close(); } if(appended) { fireDataChanged(); } return appended; } /** * Compares two values with each other. * * @param value1 * first value to be compared * @param value2 * second value to be compared * @return true, if both values are equal */ public static boolean equals(final Object value1, final Object value2) { return equals(value1,value2,null); } /** * Compares two values with each other. * * @param value1 * first value to be compared * @param value2 * second value to be compared * @param type * which should be considered for comparison, can be * null * @return true, if both values are equal * @since 4.1.2 */ public static boolean equals(final Object value1, final Object value2, final DataType type) { if(value1 == value2) { return true; } if(value1 == null || value2 == null) { return false; } if(value1.equals(value2)) { return true; } if(value1 instanceof ByteHolder || value1 instanceof CharHolder || value2 instanceof ByteHolder || value2 instanceof CharHolder) { // don't perform further checks on byte or char holders return false; } if(value1 instanceof Number && value2 instanceof Number) { return ((Number)value1).doubleValue() == ((Number)value2).doubleValue(); } else if(value1.toString().equals(value2.toString())) { return true; } if(type != null) { if(type.isNumeric() || type == DataType.BOOLEAN) { try { final Object o1 = ConversionUtils.convert(value1,type.getJavaClass()); final Object o2 = ConversionUtils.convert(value2,type.getJavaClass()); if(o1.equals(o2)) { return true; } } catch(final ObjectConversionException e) { } } } return false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy