/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* TableValueEditor.java
* Created: Feb 28, 2001
* By: Michael Cheng
*/
package org.openquark.gems.client.valueentry;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.valuenode.ValueNode;
/**
* This is a StructuredValueEditor that uses a table to visually represent and manipulate the data.
*
* This editor uses a ListSelectionListener that will edit the currently selected cell.
* If a cell is selected and a new cell becomes selected, then the value for the previously selected
* cell will be committed and the new cell will start editing.
*
* This class behaves somewhat differently from a standard JTable in that the selected cell is always being
* edited. There are a number of odd looking pieces of code that attempt to ensure this is always true
* including the ListSelectionListener which will ensure that we are editing the correct cell any time the
* selection changes.
* @author Michael Cheng
* @author Frank Worsley
*/
abstract public class TableValueEditor extends StructuredValueEditor {
/**
* If cell selection is changed this listener will commit the value currently being edited
* and then start editing the value in the newly selected cell (if any is selected).
* @author Frank Worsley
*/
private class TableListSelectionListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
// This has to be invoked later, otherwise the newly selected cell will not always
// end up being edited. One way to reproduce: create a list of 4 Integers. Edit the
// first cell and enter a new value. Press the down arrow key. Notice that the value
// was committed and the next cell is selected, but it is not being edited.
// Don't proceed if the value is still adjusting (the check is duplicated again later, but there
// is no point adding the event to the event queue if it will just be rejected later)
if (!e.getValueIsAdjusting()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (table.getSelectionModel().getValueIsAdjusting() ||
table.getColumnModel().getSelectionModel().getValueIsAdjusting()) {
// Don't do anything if the value is still adjusting. Otherwise this can cause
// exceptions if the user clicks on a cell and drags the mouse. The BasicTableUI
// will try to repost mouse events to the cell editor, which may not be visible
// on the screen as we change cell editing here. This results in an
// IllegalStateException and then a NullPointerException.
return;
}
int selectedRow = table.getSelectedRow();
int selectedColumn = table.getSelectedColumn();
// Ensure that the row and column we are editing is valid
if (selectedRow != -1 && selectedColumn != -1 &&
valueEditorHierarchyManager.existsInHierarchy(TableValueEditor.this)) {
scrollToSelectedRow();
// If the user has selected a new cell and this editor still exists in the editor
// hierarchy, then start editing the newly selected cell. It's possible that we
// are already editing this cell so don't re-edit the same cell (which can do
// strange things to the UI by redrawing the same component multiple times)
if (selectedRow != table.getEditingRow() || selectedColumn != table.getEditingColumn()) {
table.editCellAt(selectedRow, selectedColumn);
handleCellActivated();
table.revalidate();
}
}
}
});
}
}
}
/**
* Will edit the previous or next cell if the user presses the up or down arrow keys.
* @author Frank Worsley
*/
private class UpDownTabKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
int editRow = getSelectedRow();
int editColumn = getSelectedColumn();
int nRows = table.getRowCount();
if (keyCode == KeyEvent.VK_UP && editRow > 0) {
selectCell(editRow - 1, editColumn);
e.consume();
} else if (keyCode == KeyEvent.VK_DOWN && editRow < nRows - 1) {
selectCell(editRow + 1, editColumn);
e.consume();
}
}
}
/** The table used by this editor. */
protected final JTable table;
/** The table cell editor used by the table. */
private final ValueEditor tableCellEditor;
/** The scroll pane the table is placed into. */
protected final JScrollPane tableScrollPane;
/** The model used by the table. */
protected ValueEditorTableModel tableModel;
/**
* TableValueEditor constructor comment.
* @param valueEditorHierarchyManager
*/
protected TableValueEditor(ValueEditorHierarchyManager valueEditorHierarchyManager) {
super(valueEditorHierarchyManager);
// Ensure that the table rows are the correct size. I think this is bad, but PANEL_HEIGHT is
// supposed to include the size of the border so we may need to subtract the size of the border
// if it won't be drawn.
Border border = valueEditorManager.getValueEditorBorder(this);
if (valueEditorManager.useValueEntryPanelBorders() || border == null) {
table = createTable(ValueEntryPanel.PANEL_HEIGHT);
} else {
Insets insets = border.getBorderInsets(this);
table = createTable(ValueEntryPanel.PANEL_HEIGHT - insets.top - insets.bottom);
}
tableScrollPane = new JScrollPane(table);
tableScrollPane.setCursor(Cursor.getDefaultCursor());
tableCellEditor = createTableCellEditor();
tableCellEditor.setEditable(isEditable());
table.setDefaultEditor(ValueNode.class, (TableCellEditor)tableCellEditor);
ListSelectionListener selectionListener = createListSelectionListener();
table.getSelectionModel().addListSelectionListener(selectionListener);
table.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener);
table.addKeyListener(new UpDownTabKeyListener());
tableCellEditor.setContext(new ValueEditorContext() {
public TypeExpr getLeastConstrainedTypeExpr() {
ValueEditor parentEditor = tableCellEditor.getParentValueEditor();
TypeExpr leastConstrainedParentType = parentEditor.getContext().getLeastConstrainedTypeExpr();
// The parent editor's editing (non-owner) value node is the owner value node for the child (ie. the table).
TypeExpr parentType = parentEditor.getValueNode().getTypeExpr();
TypeExpr childTypeToFind = tableCellEditor.getOwnerValueNode().getTypeExpr();
TypeExpr resultType = parentType.getCorrespondingTypeExpr(leastConstrainedParentType, childTypeToFind);
return (resultType == null) ? TypeExpr.makeParametricType() : resultType;
}
});
// Ensure that commits in the child are reflected in the table.
tableCellEditor.addValueEditorListener(new ValueEditorAdapter() {
@Override
public void valueCommitted(ValueEditorEvent evt) {
int editRow = table.getEditingRow();
int editColumn = table.getEditingColumn();
if (editRow == -1 || editColumn == -1) {
return;
}
// Only commit the child value if there really is a cell at this location.
// If the user presses the remove button multiple times it can happen that the cell is gone
// from the model, but we still try to commit its value.
if (tableModel.getRowCount() > editRow && tableModel.getColumnCount() > editColumn) {
ValueNode oldValueNode = (ValueNode) tableModel.getValueAt(editRow, editColumn);
ValueNode newValueNode = tableCellEditor.getValueNode();
if (oldValueNode.sameValue(newValueNode)) {
return;
}
saveSize();
commitChildChanges(oldValueNode, newValueNode);
// Refresh the display to account for changes in the child value.
refreshDisplay();
restoreSavedSize();
}
}
});
}
/**
* Creates the JTable that will be used by the value editor.
* @return JTable
*/
private static JTable createTable(int rowHeight) {
JTable table = new JTable();
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
table.setRowMargin(0);
table.setRowHeight(rowHeight);
JTableHeader tableHeader = table.getTableHeader();
tableHeader.setReorderingAllowed(false);
return table;
}
/**
* Creates the table model to use by this table editor. Subclasses implement this to return
* the proper table model they want to use.
* @param valueNode the value node to create the table model for
* @return ValueEditorTableModel
*/
protected abstract ValueEditorTableModel createTableModel(ValueNode valueNode);
/**
* Creates the cell editor for this table editor. Subclasses can override this to return
* a customized cell editor. Although not part of the type declaration it is required that the return
* type implement the TableCellEditor interface. Failure to do so will result in class cast exceptions.
* It would be nice if this was enforced by the type, but that is difficult to do because of the structure
* of the value editor heirarchy
* @return a ValueEditor that implements the TableCellEditor interface
*/
protected ValueEditor createTableCellEditor() {
return new ValueEditorTableCellEditor(this, valueEditorHierarchyManager);
}
/**
* Creates the table header renderer with the given icon.
* @param typeIcon the icon for the header renderer
* @return TableCellRenderer
*/
protected static TableCellRenderer createTableHeaderRenderer(ImageIcon typeIcon) {
DefaultTableCellRenderer label = new DefaultTableCellRenderer() {
private static final long serialVersionUID = 6442249477330477442L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (table != null) {
JTableHeader header = table.getTableHeader();
if (header != null) {
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());
}
}
setText((value == null) ? "" : value.toString());
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
return this;
}
};
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setIcon(typeIcon);
return label;
}
/**
* Creates the list selection listener to add to the table. By default this creates a
* listener that will edit whatever cell is currently selected. If no cell is selected
* it will stop editing. Override this to return a custom selection listener.
* @return ListSelectionListener
*/
private ListSelectionListener createListSelectionListener() {
return new TableListSelectionListener();
}
/**
* Stops editing the current cell if one is being edited. Does nothing if no cell
* is currently being edited.
* @param commit whether or not to commit the value
*/
private void stopEditing(boolean commit) {
TableCellEditor cellEditor = table.getCellEditor();
if (cellEditor != null) {
if (commit) {
cellEditor.stopCellEditing();
} else {
cellEditor.cancelCellEditing();
}
table.removeEditor();
}
}
/**
* @return the table header used by the table
*/
protected final JTableHeader getTableHeader() {
return table.getTableHeader();
}
/**
* @return the currently selected row which is also the row being edited.
*/
protected int getSelectedRow() {
return table.getSelectedRow();
}
/**
* @return the currently selected column which is also the column being edited.
*/
protected int getSelectedColumn() {
return table.getSelectedColumn();
}
/**
* @return the number of rows in the table
*/
protected final int getRowCount() {
return table.getRowCount();
}
/**
* @return the number of columns in the table
*/
protected final int getColumnCount() {
return table.getColumnCount();
}
/**
* Call this method when selected row changes.
* If the selected row is still in view, then do nothing.
* Else, if the selected row is no longer in view, then scroll to it.
*/
protected final void scrollToSelectedRow() {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
Rectangle rect = table.getCellRect(selectedRow, 0, true);
JViewport viewport = tableScrollPane.getViewport();
int viewportTop = viewport.getViewPosition().y;
int viewportHeight = viewport.getExtentSize().height;
int viewportBottom = viewportHeight + viewportTop;
// Determine if the selected row is in view, and if not, then make sure viewport shows it.
if (rect.getY() < viewportTop) {
Point pointSelected = new Point((int) rect.getX(), (int) rect.getY());
viewport.setViewPosition(pointSelected);
} else if (rect.getY() > viewportBottom - table.getRowHeight()) {
Point pointSelected = new Point((int) rect.getX(), (int) rect.getY() - viewportHeight + table.getRowHeight());
viewport.setViewPosition(pointSelected);
}
}
}
/**
* Convenience method to select the cell at the given row and column.
* This will also result in the selected cell to become the edited cell
* and the previously edited value to be committed.
* @param rowNum the row number
* @param colNum the column number
*/
protected final void selectCell(int rowNum, int colNum) {
// A concrete cell has to be selected, you cannot just select
// a row or a column. Therefore, if one of them is -1, default it to
// the first row or column.
if (colNum == -1) {
colNum = 0;
}
if (rowNum == -1) {
rowNum = 0;
}
ListSelectionModel rowModel = table.getSelectionModel();
ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
colModel.setSelectionInterval(colNum, colNum);
rowModel.setSelectionInterval(rowNum, rowNum);
}
/**
* Clears the selection and causes editing to stop.
* @param commit whether or not to commit the current value
*/
protected final void clearSelection(boolean commit) {
stopEditing(commit);
table.clearSelection();
}
/**
* {@inheritDoc}
*/
@Override
protected void commitValue() {
stopEditing(true);
saveSize();
notifyValueCommitted();
}
/**
* {@inheritDoc}
*/
@Override
protected void cancelValue() {
stopEditing(false);
notifyValueCanceled();
}
/**
* Commits the child's changes to the parent.
* This method is only called if the value of oldChild is different from newChild.
* For important implementation notes, see the comment at the top of the TableValueEditor class.
* @param oldChild the old child value
* @param newChild the new child value
*/
public abstract void commitChildChanges(ValueNode oldChild, ValueNode newChild);
/**
* When a cell has been activated, there might be set-up issues that need to be done.
* By default this method makes the editor resizable and enables the scrollbars.
* Subclasses should override this to perform any additional setup needed.
*/
public void handleCellActivated() {
setResizable(true);
tableScrollPane.getVerticalScrollBar().setEnabled(true);
tableScrollPane.getHorizontalScrollBar().setEnabled(true);
}
/**
* Performs any setup needed when a child editor is launched.
* By default this disables resizability and the scrollbars.
* Subclasses should override this to perform any additional setup needed.
*/
@Override
public void handleElementLaunchingEditor() {
setResizable(false);
tableScrollPane.getVerticalScrollBar().setEnabled(false);
tableScrollPane.getHorizontalScrollBar().setEnabled(false);
}
/**
* Will give each column a ValueEditorTableCellRenderer set to the correct type.
* Note: Call this method only after initializing the table, elementTypeArray, and typeColourManager.
*/
protected void initializeTableCellRenderers() {
TypeExpr typeExpr = getValueNode().getTypeExpr();
TableColumnModel tableColumnModel = table.getColumnModel();
boolean displayElementNumber = typeExpr.isListType();
for (int i = 0, columnCount = tableColumnModel.getColumnCount(); i < columnCount; i++) {
TypeExpr elementTypeExpr = tableModel.getElementType(i);
ValueEditorTableCellRenderer cellRenderer =
new ValueEditorTableCellRenderer(displayElementNumber,
elementTypeExpr,
valueEditorManager);
cellRenderer.setEditable(isEditable());
// Give the column headers an icon of the type that they represent.
String iconName = valueEditorManager.getTypeIconName(elementTypeExpr);
ImageIcon typeIcon = showTypeIconInColumnHeading(i) ? new ImageIcon(TableValueEditor.class.getResource(iconName)) : null;
TableCellRenderer headerRenderer = createTableHeaderRenderer(typeIcon);
TableColumn tableColumn = tableColumnModel.getColumn(i);
tableColumn.setCellRenderer(cellRenderer);
tableColumn.setHeaderRenderer(headerRenderer);
}
}
/**
* Initialized the size of this value editor. If there is a saved size, then that is used.
* Otherwise the preferred size of the editor is used and all table columns are sized according to
* the preferred width of the type they represent. If the passed in maximum size is null or the width/height
* contains negative values, then the preferred size will be used as the maximum size for that value.
* The same goes for the minimum size.
* @param minSize the desired minimum size of the editor
* @param maxSize the desired maximum size of the editor
*/
protected final void initSize(Dimension minSize, Dimension maxSize) {
if (!restoreSavedSize()) {
// If there is no saved size, then use the preferred size and give the columns preferred widths.
for (int i = 0, nElements = tableModel.getNElements(); i < nElements; ++i) {
TypeExpr innerTypeExpr = tableModel.getElementType(i);
int colWidth;
if (tableCellEditor instanceof ValueEntryPanel) {
colWidth = valueEditorManager.getTypePreferredWidth(innerTypeExpr, ((ValueEntryPanel)tableCellEditor).getValueFieldFontMetrics());
// The type width doesn't include the width of the VEP button/icon/border.
// Therefore we add 55 to correct for it.
colWidth += 55;
} else {
colWidth = (int)tableCellEditor.getPreferredSize().getWidth();
}
TableColumn tableColumn = table.getColumnModel().getColumn(i);
tableColumn.setPreferredWidth(colWidth);
}
Dimension tableSize = table.getPreferredSize();
table.setPreferredScrollableViewportSize(new Dimension(tableSize.width, tableSize.height + 1));
validate();
Dimension prefSize = getPreferredSize();
// For some reason header size is not included if parent is null.
if (getParent() == null && table.getTableHeader() != null) {
prefSize.height += table.getTableHeader().getPreferredSize().height;
}
setSize(prefSize);
}
// Now setup the maximum/minimum size.
Dimension prefSize = getPreferredSize();
// For some reason header size is not included if parent is null.
if (getParent() == null && table.getTableHeader() != null) {
prefSize.height += table.getTableHeader().getPreferredSize().height;
}
Dimension realMinSize = new Dimension(0, 0);
realMinSize.width = minSize != null && minSize.width > -1 ? minSize.width : prefSize.width;
realMinSize.height = minSize != null && minSize.height > -1 ? minSize.height : prefSize.height;
Dimension realMaxSize = new Dimension(0, 0);
realMaxSize.width = maxSize != null && maxSize.width > -1 ? maxSize.width : prefSize.width;
realMaxSize.height = maxSize != null && maxSize.height > -1 ? maxSize.height : prefSize.height;
setMaxResizeDimension(realMaxSize);
setMinResizeDimension(realMinSize);
Dimension currentSize = getSize();
currentSize.width = ValueEditorManager.clamp(realMinSize.width, currentSize.width, realMaxSize.width);
currentSize.height = ValueEditorManager.clamp(realMinSize.height, currentSize.height, realMaxSize.height);
setSize(currentSize);
revalidate();
repaint();
}
/**
* Loads the specified saved size by resizing UI components with the
* dimensions stored in the size information.
* @param sizeInfo structure containing size information
*/
protected void loadSavedSize(ValueEditor.Info sizeInfo) {
// Restore the saved size.
List<Integer> columnWidthList = sizeInfo.getComponentWidths();
Dimension savedSize = sizeInfo.getEditorSize();
// Set sizes for the columns
TableColumnModel columnModel = table.getColumnModel();
for (int i = 0, listCount = columnWidthList.size(), tableColumnCount = table.getColumnCount();
i < listCount && i < tableColumnCount;
i++) {
Integer colWidth = columnWidthList.get(i);
TableColumn column = columnModel.getColumn(i);
column.setPreferredWidth(colWidth.intValue());
}
Dimension tableSize = table.getPreferredSize();
table.setPreferredScrollableViewportSize(new Dimension(tableSize.width, tableSize.height + 1));
setSize(savedSize);
}
/**
* Restores the saved size, if there is any saved size.
* @return true if saved size was restored, false if there was no saved size
*/
private boolean restoreSavedSize() {
ValueEditor.Info info = valueEditorManager.getInfo(getOwnerValueNode());
if (info == null) {
return false;
}
loadSavedSize(info);
return true;
}
/**
* Retrieve a size information structure describing the current size of the
* editor and its columns.
* @return ValueEditor.Info
*/
protected ValueEditor.Info getSaveSize() {
// Going with our set default when table empty (no row) policy.
if (table.getRowCount() == 0) {
return null;
}
List<Integer> columnWidthList = new ArrayList<Integer>();
TableColumnModel columnModel = table.getColumnModel();
for (int i = 0, colCount = columnModel.getColumnCount(); i < colCount; i++) {
TableColumn column = columnModel.getColumn(i);
Integer width = Integer.valueOf(column.getWidth());
columnWidthList.add(width);
}
return new ValueEditor.Info(getSize(), columnWidthList);
}
/**
* Saves the current size information of the value editor and associates it with the
* owner value node. This includes the overall editor size and the widths of the
* individual columns.
*/
private void saveSize() {
valueEditorManager.associateInfo(getOwnerValueNode(), getSaveSize());
}
/**
* Returns whether a type icon should be displayed in the specified column heading of the table.
* By default this returns true for all columns.
* @param columnN the column number
* @return whether the type icon should be displayed for the given column
*/
protected boolean showTypeIconInColumnHeading(int columnN) {
return true;
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#setEditable(boolean)
*/
@Override
public void setEditable(boolean b) {
super.setEditable(b);
tableCellEditor.setEditable(b);
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#replaceValueNode(org.openquark.cal.valuenode.ValueNode, boolean)
*/
@Override
public void replaceValueNode(ValueNode newValueNode, boolean preserveInfo) {
super.replaceValueNode(newValueNode, preserveInfo);
// Get the existing table selection so we can preserve it
int selectedRow = table.getSelectedRow();
int selectedCol = table.getSelectedColumn();
// Update the model
setTableModel(createTableModel(newValueNode));
// Restore the selection
ListSelectionModel rowModel = table.getSelectionModel();
rowModel.setSelectionInterval(selectedRow, selectedRow);
ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
colModel.setSelectionInterval(selectedCol, selectedCol);
initializeTableCellRenderers();
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#setOwnerValueNode(org.openquark.cal.valuenode.ValueNode)
*/
@Override
public void setOwnerValueNode(ValueNode newValueNode) {
super.setOwnerValueNode(newValueNode);
setTableModel(createTableModel(getValueNode()));
initializeTableCellRenderers();
}
/**
* Set the table model used by this editor's table
* @param tableModel
*/
protected void setTableModel(ValueEditorTableModel tableModel) {
this.tableModel = tableModel;
table.setModel(tableModel);
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#getDefaultFocusComponent()
*/
@Override
public Component getDefaultFocusComponent() {
if (table.getCellEditor() != null) {
return tableCellEditor;
} else if (tableModel.getNElements() > 0) {
return table;
} else {
return this;
}
}
}