Java tutorial
package org.sigmah.client.ui.view.project.logframe.grid; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.allen_sauer.gwt.log.client.Log; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; /** * Maintains a group of rows in a flex table. The group of rows is shown with a rowspan on the first column of the * table. * * @author tmi (v1.3) * @author HUZHE (v1.3) * @author Denis Colliot (dcolliot@ideia.fr) (v2.0) */ public class FlexTableView { /** * Listen to view events. * * @author tmi (v1.3) * @author Denis Colliot (dcolliot@ideia.fr) (v2.0) */ public static interface FlexTableViewListener { /** * Method called when a group is added to this view. * * @param group * The new group. */ void groupAdded(RowsGroup<?> group); /** * Method called when a row is added to a group. * * @param group * The group. * @param row * The new row. */ void rowAdded(RowsGroup<?> group, Row<?> row); /** * Method called when a row is removed from a group. * * @param group * The group. * @param row * The old row. */ void rowRemoved(RowsGroup<?> group, Row<?> row); } /** * CSS style name for the entire view. */ private static final String CSS_FLEX_TABLE_VIEW_STYLE_NAME = "flextable-view"; /** * CSS style name for the cells which display groups. */ private static final String CSS_GROUP_CELL_STYLE_NAME = CSS_FLEX_TABLE_VIEW_STYLE_NAME + "-group-cell"; /** * CSS style name for the labels which display groups. */ private static final String CSS_GROUP_LABEL_STYLE_NAME = CSS_FLEX_TABLE_VIEW_STYLE_NAME + "-group-label"; /** * CSS style name for the merged rows. */ private static final String CSS_MERGED_ROWS_STYLE_NAME = CSS_FLEX_TABLE_VIEW_STYLE_NAME + "-merged-row"; /** * The parent flex table. */ private final FlexTable table; /** * The other views (lower in the same table) which depend on this view. When this view adds or removes rows, its * dependent views will be incremented or decremented to keep a consistent index. */ private final ArrayList<FlexTableView> dependencies; /** * The difference between the first row index of this view and the first row index of the entire table. This value * should be incremented by others views if there are displayed above. */ private int shift; /** * The column to show the group of rows. */ private final int groupColumnIndex; /** * The number of fillable columns. */ private final int columnsCount; /** * An ordered list of the current displayed groups. */ private final ArrayList<RowsGroup<?>> groupsOrderedList; /** * The key of this map is the unique integer which identifies a group of rows. */ private final HashMap<Integer, RowsGroup<?>> groupsCodesMap; /** * Listeners. */ private final ArrayList<FlexTableViewListener> listeners; /** * Initializes this group of rows. * * @param table * The parent flex table. * @param columnsCount * The total columns count. * @param row * The row index. */ public FlexTableView(FlexTable table, int columnsCount, int row) { // Checks if the table isn't null. if (table == null) { throw new NullPointerException("table must not be null"); } // Checks if the row indexes an existing row. if (row < 0 || row >= table.getRowCount()) { throw new IllegalArgumentException("the flex table does not have a row at index #" + row + "."); } // Checks if the table contains enough columns. if (columnsCount < 2) { throw new IllegalArgumentException("the flex table does not contains enought columns (min 2)."); } // Sets the table. this.table = table; // Initializes the local lists. groupsOrderedList = new ArrayList<RowsGroup<?>>(); groupsCodesMap = new HashMap<Integer, RowsGroup<?>>(); dependencies = new ArrayList<FlexTableView>(); listeners = new ArrayList<FlexTableViewListener>(); // At the beginning, the shift count is equals to the row index. shift = row; // The first column is used to show the group of rows (rowspan). groupColumnIndex = 0; // The number of the others columns with can contains widgets. this.columnsCount = columnsCount - 1; } // ------------------------------------------------------------------------ // -- LISTENERS // ------------------------------------------------------------------------ /** * Adds a listener. * * @param l * The new listener. */ public void addFlexTableViewListener(FlexTableViewListener l) { this.listeners.add(l); } /** * Removes a listener. * * @param l * The old listener. */ public void removeFlexTableViewListener(FlexTableViewListener l) { this.listeners.remove(l); } /** * Method called when a group is added to this view. * * @param group * The new group. */ protected void fireGroupAdded(final RowsGroup<?> group) { for (final FlexTableViewListener l : listeners) { l.groupAdded(group); } } /** * Method called when a row is added to a group. * * @param group * The group. * @param row * The new row. */ protected void fireRowAdded(RowsGroup<?> group, Row<?> row) { for (final FlexTableViewListener l : listeners) { l.rowAdded(group, row); } } /** * Method called when a row is removed from a group. * * @param group * The group. * @param row * The old row. */ protected void fireRowRemoved(RowsGroup<?> group, Row<?> row) { for (final FlexTableViewListener l : listeners) { l.rowRemoved(group, row); } } // ------------------------------------------------------------------------ // -- DEPENDENCIES // ------------------------------------------------------------------------ /** * Adds a dependency to this view. * * @param other * The other view. */ public void addDependency(FlexTableView other) { // Checks if the other view is correct. if (other == null) { throw new NullPointerException("other must not be null"); } // Checks if the two views share the same table. if (table != other.table) { throw new IllegalArgumentException("the other view doesn't share the same table as the current view"); } dependencies.add(other); } /** * Increments the shift count of each dependency. */ private void incrementDependencies() { for (final FlexTableView dependency : dependencies) { dependency.shift++; } } /** * Decrements the shift count of each dependency. */ private void decrementDependencies() { for (final FlexTableView dependency : dependencies) { dependency.shift--; } } // ------------------------------------------------------------------------ // -- FLEX TABLE // ------------------------------------------------------------------------ /** * Inserts a row in the table. This method considers the shift. * * @param beforeRow * The index before which the new row will be inserted. * @return The new row index. */ protected int insertTableRow(int beforeRow) { // Inserts the new row. int row = table.insertRow(beforeRow + shift); // Adjusts the row span. table.getFlexCellFormatter().setRowSpan(shift, groupColumnIndex, table.getFlexCellFormatter().getRowSpan(shift, groupColumnIndex) + 1); // Applies the row styles. HTMLTableUtils.applyRowStyles(table, row); // Impacts the adding of row on dependencies. incrementDependencies(); return row; } /** * Removes a row from the table. This method considers the shift. * * @param row * The index of the row to remove. */ protected void removeTableRow(int row) { // Removes the row. table.removeRow(row + shift); // Adjusts the row span. table.getFlexCellFormatter().setRowSpan(shift, groupColumnIndex, table.getFlexCellFormatter().getRowSpan(shift, groupColumnIndex) - 1); // Impacts the removing of row on dependencies. decrementDependencies(); } // ------------------------------------------------------------------------ // -- GROUPS // ------------------------------------------------------------------------ /** * Inserts a new group of rows at the last position. * * @param group * The group. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If a group with the same id already exists. */ public void addGroup(final RowsGroup<?> group) { insertGroup(groupsOrderedList.size() + 1, group); } /** * Remove a log group when it is empty (should be verified before this action). * * @param group * The group to remove. */ public void removeGroup(final RowsGroup<?> group) { // Get the index of this group in the ordered group list int groupIndexInGroups = groupsOrderedList.indexOf(group); // Get the index in the view, the shift is not considered int groupIndexInView = computeGroupIndex(groupIndexInGroups + 1); // Remove the row from the table,the shift is considered in the removeTableRow method this.removeTableRow(groupIndexInView); // Remove the group from ordered list and the map groupsOrderedList.remove(group); groupsCodesMap.remove(group.getId()); } /** * Inserts a new group of rows at the given position. * * @param position * The position at which the group will be inserted among the groups list (for example, a index equals to * <code>2</code> means that the group will be the second one). * If this index is lower or equal than <code>0</code>, the group will be the first one. An index greater * than the number of group will insert the group at the last position. * @param group * The group. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If a group with the same id already exists. */ public void insertGroup(int position, final RowsGroup<?> group) { // Checks if the group is valid. if (group == null) { throw new NullPointerException("The group must not be null."); } final int id = group.getId(); // Checks if the group doesn't exist already. if (groupsCodesMap.get(id) != null) { throw new IllegalArgumentException("The group with id #" + id + " already exists."); } // Re-adjusts the position to avoid out of bounds errors. if (position <= 0 || groupsOrderedList.isEmpty()) { position = 1; } else if (position > groupsOrderedList.size()) { position = groupsOrderedList.size() + 1; } if (Log.isDebugEnabled()) { Log.debug("[insertGroup] Inserts the new group #" + id + " at position # " + position + "."); } // Computes new group indexes. int row = computeGroupIndex(position); row = insertTableRow(row); int column = 0; // Builds group's widget. final Widget widget = group.getWidget(); // Adds widget and sets row. table.setWidget(row, column, widget); table.getFlexCellFormatter().setColSpan(row, column, columnsCount + 2); HTMLTableUtils.applyCellStyles(table, row, column, false, true); table.getFlexCellFormatter().addStyleName(row, column, CSS_GROUP_CELL_STYLE_NAME); widget.addStyleName(CSS_GROUP_LABEL_STYLE_NAME); // Adds the group locally at the correct position. groupsOrderedList.add(position - 1, group); groupsCodesMap.put(group.getId(), group); // Hides the group header if needed. if (!group.isVisible()) { widget.setVisible(false); } fireGroupAdded(group); } /** * Gets the group with the given id if it exists. Returns <code>null</code> otherwise. * * @param groupId * The group id. * @return The group with this id, <code>null</code> otherwise. */ public RowsGroup<?> getGroup(int groupId) { return groupsCodesMap.get(groupId); } /** * Gets the index at which a group must be inserted to be at the given position. This index <strong>does'nt</strong> * consider the shift. * Use the {@link FlexTableView#insertTableRow(int)} method to insert a row considering the shift. * * @param position * The position at which the group will be inserted among the groups list (for example, a index equals to * <code>2</code> means that the group will be the second one). * If this index is lower or equal than <code>0</code>, the group will be the first one. An index greater * than the number of group will insert the group at the last position. * @see FlexTableView#insertTableRow(int) */ protected int computeGroupIndex(int position) { // Default index (no group already displayed). int index = 1; // Browses the list of existing groups until the desired position is reached. RowsGroup<?> group; for (int i = 0; i < position - 1 && i < groupsOrderedList.size(); i++) { // For each group, increments the index with the number of elements it contains. group = groupsOrderedList.get(i); index += 1 + group.getRowsCount(); } return index; } /** * Gets the position of a group. The position is included in the interval [1;GROUPS_COUNT]. If the group doesn't * exist, an exception is thrown. * * @param group * The group. * @return The group position in the interval [1;GROUPS_COUNT]. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If the group doesn't exist. */ protected int getGroupPosition(final RowsGroup<?> group) { // Checks if the group is valid. if (group == null) { throw new NullPointerException("The group must not be null."); } // Gets the group index in the ordered list. final int position = groupsOrderedList.indexOf(group); // The group doesn't exist. if (position == -1) { throw new IllegalArgumentException("The group with id #" + group.getId() + " doesn't exist."); } return position + 1; } /** * Gets the row index of a group. If the group doesn't exist, an exception is thrown. * * @param group * The group. * @return The row index of this group. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If the group doesn't exist. */ protected int getGroupRowIndex(final RowsGroup<?> group) { // Checks if the group is valid. if (group == null) { throw new NullPointerException("The group must not be null."); } // Default index (no group already displayed). int index = 0; // Browses the list of existing groups until the searched group is reached. for (final RowsGroup<?> g : groupsOrderedList) { index++; if (g.equals(group)) { return index; } // For each group, increments the index with the number of elements it contains. index += g.getRowsCount(); } // The group hasn't been found. throw new IllegalArgumentException("The group with id #" + group.getId() + " doesn't exist."); } /** * Gets the number of groups in this view. * * @return The number of groups in this view. */ public int getGroupsCount() { return groupsOrderedList.size(); } /** * Refreshes the group widget. * * @param group * The group. */ public void refreshGroupWidget(final RowsGroup<?> group) { // Checks if the group is valid. if (group == null) { throw new NullPointerException("The group must not be null."); } if (Log.isDebugEnabled()) { Log.debug("[refreshGroupWidget] Refreshes the group #" + group.getId() + "."); } // Computes new group indexes. int row = getGroupRowIndex(group) + shift; int column = 0; // Builds group's widget. final Widget widget = group.getWidget(); // Sets the new widget. table.setWidget(row, column, widget); // Applies style names. HTMLTableUtils.applyCellStyles(table, row, column, false, true); widget.addStyleName(CSS_GROUP_LABEL_STYLE_NAME); } // ------------------------------------------------------------------------ // -- ROWS (IN GROUP) // ------------------------------------------------------------------------ /** * Inserts a row in the given group at the last position. * * @param <T> * The type of the user object contained in this row. * @param groupId * The id of the group in which the row will be inserted. * @param row * The row. * @throws NullPointerException * If the row is <code>null</code>. * @throws IllegalArgumentException * If there isn't a group with the given id. */ public <T> void addRow(final int groupId, final Row<T> row) { // By default the row is inserted at the end of the group. // (sets the position to the infinite value to avoid group searching which is done by the sub method). insertRow(Integer.MAX_VALUE, groupId, row); } /** * Inserts a row in the given group at the given position. * * @param <T> * The type of the user object contained in this row. * @param position * The row position in its group (for example, a index equals to <code>2</code> means that the row will be * the second one in its group). * If this index is lower or equal than <code>0</code>, the row will be the first one. An index greater than * the number of rows in this group will insert the row at the last position. * @param groupId * The id of the group in which the row will be inserted. * @param row * The row. * @throws NullPointerException * If the row is <code>null</code>. * @throws IllegalArgumentException * If there isn't a group with the given id. */ @SuppressWarnings("unchecked") public <T> void insertRow(int position, final int groupId, final Row<T> row) { // Checks if the row is valid. if (row == null) { throw new NullPointerException("The row must not be null."); } // Checks if the group code is valid. final RowsGroup<?> group; if ((group = groupsCodesMap.get(groupId)) == null) { throw new IllegalArgumentException("The group #" + groupId + " does'nt exist."); } // Re-adjusts the position to avoid out of bounds errors. if (position <= 0 || group.getRowsCount() == 0) { position = 1; } else if (position > group.getRowsCount()) { position = group.getRowsCount() + 1; } if (Log.isDebugEnabled()) { Log.debug("[insertRow] Inserts the new row #" + row.getId() + " in group #" + groupId + " at position #" + position + "."); } // Computes new row indexes. int rowIndex = computeRowIndex(group, position); rowIndex = insertTableRow(rowIndex); // Indexes of the columns which manage merging. final List<Integer> merge = new ArrayList<Integer>(); for (int index : group.getMergedColumnIndexes()) { merge.add(index); } int column = 0; int colSpan = 0; // Adds each column widget. for (int j = 0; j < columnsCount; j++) { // Gets the widget at this column index. final Widget w = row.getWidgetAt(j); if (w == null) { colSpan++; column--; } else { table.setWidget(rowIndex, column, w); // If there is any col span to perform. if (colSpan != 0) { table.getFlexCellFormatter().setColSpan(rowIndex, column, colSpan + 1); } HTMLTableUtils.applyCellStyles(table, rowIndex, column, false, false); // Reinit the col span. colSpan = 0; } // Checks if this column can be merged. if (merge.contains(j)) { // Gets the top row if any. final Row<?> topRow; if ((topRow = group.getRowAtPosition(position - 1)) != null) { // If the rows properties are similar, removes the widget. if (row.isSimilar(j, row.getUserObject(), ((Row<T>) topRow).getUserObject())) { table.setWidget(rowIndex, column, new Label("")); table.getFlexCellFormatter().addStyleName(rowIndex, column, CSS_MERGED_ROWS_STYLE_NAME); } } } column++; } // Adds the row locally. group.addRow(row, position); try { // Refreshes the direct bottom row of the just inserted row. refreshMergedRow(group, position + 1); } // The row doesn't exist, nothing to do. catch (IndexOutOfBoundsException e) { // Digests exception. } fireRowAdded(group, row); } /** * Removes the given row from the given group. * * @param group * The group in which the row is inserted. * @param rowId * The row id. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If the row doesn't exist. */ public void removeRow(final RowsGroup<?> group, final int rowId) { // Checks if the group is valid. if (group == null) { throw new IllegalArgumentException("The group must not be null."); } // Checks if the row exists in this group. final Row<?> row; if ((row = group.getRow(rowId)) == null) { throw new IllegalArgumentException( "The row with id #" + rowId + " does'nt exist in group #" + group.getId() + "."); } // Saves the old position of the removed row. final int oldPosition = group.getRowPosition(row); // Gets the row index. final int index = getRowIndex(row); // Removes the row in the table. removeTableRow(index); // Removes the row locally. group.removeRow(row); try { // Refreshes the direct bottom row of the just removed row. refreshMergedRow(group, oldPosition); } // The row doesn't exist, nothing to do. catch (IndexOutOfBoundsException e) { // Digests exception. } fireRowRemoved(group, row); } /** * Refreshes a row styles and widgets considering the merged columns indexes. * * @param group * The group in which the row is inserted. * @param position * The position of the row to refresh. * @throws IndexOutOfBoundsException * If there is no row at the given position. */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void refreshMergedRow(RowsGroup<?> group, int position) throws IndexOutOfBoundsException { // If the row was the last one, nothing to do. if (group.getRowsCount() == 0) { return; } // Gets the row to refresh. final Row row = group.getRowAtPosition(position); // Computes this row index. int rowIndex = computeRowIndex(group, position) + shift; // Indexes of the columns which manage merging. final List<Integer> merge = new ArrayList<Integer>(); for (int index : group.getMergedColumnIndexes()) { merge.add(index); } int column = 0; // Adds each column widget. for (int j = 0; j < columnsCount; j++) { // Gets the widget at this column index. final Widget w = row.getWidgetAt(j); if (w == null) { column--; } // Checks if this column can be merged. if (merge.contains(j)) { // Gets the top row if any. final Row topRow; if ((topRow = group.getRowAtPosition(position - 1)) != null) { // If the rows properties are similar, removes the widget. if (row.isSimilar(j, row.getUserObject(), topRow.getUserObject())) { table.setWidget(rowIndex, column, new Label("")); table.getFlexCellFormatter().addStyleName(rowIndex, column, CSS_MERGED_ROWS_STYLE_NAME); } else { table.setWidget(rowIndex, column, w); HTMLTableUtils.applyCellStyles(table, rowIndex, column, false, false); table.getFlexCellFormatter().removeStyleName(rowIndex, column, CSS_MERGED_ROWS_STYLE_NAME); } } else { table.setWidget(rowIndex, column, w); HTMLTableUtils.applyCellStyles(table, rowIndex, column, false, false); table.getFlexCellFormatter().removeStyleName(rowIndex, column, CSS_MERGED_ROWS_STYLE_NAME); } } column++; } } /** * Moves a row inside its group. * * @param group * The group in which the row is inserted. * @param rowId * The id of the row to move. * @param move * The number of moves to execute. If this count is higher than the available moves inside the row's group, * the excess is ignored. * A null integer has no effect. * A positive integer will move the row upward. * A negative integer will move the row downward. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If the row doesn't exist. */ public void moveRow(final RowsGroup<?> group, final int rowId, int move) { // No move. if (move == 0) { return; } // Checks if the group is valid. if (group == null) { throw new IllegalArgumentException("The group must not be null."); } // The group contains no or only one row, nothing to do. final int rowsCount = group.getRowsCount(); if (group.getRowsCount() <= 1) { return; } // Checks if the row exists in this group. final Row<?> row; if ((row = group.getRow(rowId)) == null) { throw new IllegalArgumentException( "The row #" + rowId + " does'nt exist in group #" + group.getId() + "."); } // Gets the row position in its group. final int rowPosition = row.getParent().getRowPosition(row); // Checks if the row can be moved. if (move > 0) { // The row is already the first one, nothing to do. if (rowPosition == 1) { return; } } else { // The row is already the last one, nothing to do. if (rowPosition == rowsCount) { return; } } // Re-adjusts the moves count to avoid out of bounds errors. if (move > 0) { final int avalaibleMovesCount = rowPosition - 1; if (move > avalaibleMovesCount) { move = avalaibleMovesCount; } } else { final int avalaibleMovesCount = rowsCount - rowPosition; if (Math.abs(move) > avalaibleMovesCount) { move = -avalaibleMovesCount; } } // Removes the row. removeRow(group, rowId); // Re-inserts it at its new position. insertRow(rowPosition - move, group.getId(), row); } /** * Returns if a row can be moved for the given moves count. * * @param group * The group in which the row is inserted. * @param rowId * The id of the row to move. * @param move * The number of moves to execute. If this count is higher than the available moves inside the row's group, * the excess is ignored. * A null integer has no effect. * A positive integer will move the row upward. * A negative integer will move the row downward. * @return If the row can be moved for the given moves count. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If the row doesn't exist. */ public boolean canBeMoved(final RowsGroup<?> group, final int rowId, int move) { // No move. if (move == 0) { return false; } // Checks if the group is valid. if (group == null) { throw new IllegalArgumentException("The group must not be null."); } // The group contains no or only one row, nothing to do. final int rowsCount = group.getRowsCount(); if (group.getRowsCount() <= 1) { return false; } // Checks if the row exists in this group. final Row<?> row; if ((row = group.getRow(rowId)) == null) { throw new IllegalArgumentException( "The row #" + rowId + " does'nt exist in group #" + group.getId() + "."); } // Gets the row position in its group. final int rowPosition = row.getParent().getRowPosition(row); // Checks if the row can be moved. if (move > 0) { // The row is already the first one, nothing to do. if (rowPosition == 1) { return false; } } else { // The row is already the last one, nothing to do. if (rowPosition == rowsCount) { return false; } } return true; } /** * Gets the index at which a row must be inserted to be contained in the given group at the given position. This index * <strong>does'nt</strong> consider the shift. * Use the {@link FlexTableView#insertTableRow(int)} method to insert a row considering the shift. * * @param group * The group in which the row will be inserted. * @param position * The row position in its group (for example, a index equals to <code>2</code> means that the row will be * the second one in its group). * If this index is lower or equal than <code>0</code>, the row will be the first one. An index greater than * the number of rows in this group will insert the row at the last position. * @throws NullPointerException * If the group is <code>null</code>. * @throws IllegalArgumentException * If the group doesn't exist. * @see FlexTableView#insertTableRow(int) */ protected int computeRowIndex(final RowsGroup<?> group, int position) { // Computes group row index. final int groupRowIndex = getGroupRowIndex(group); // Re-adjusts the position to avoid out of bounds errors. if (position <= 0 || group.getRowsCount() == 0) { position = 1; } else if (position > group.getRowsCount()) { position = group.getRowsCount() + 1; } // The row position is added to the group row index. return groupRowIndex + position; } /** * Gets the row index of a row. * * @param row * The row. * @throws NullPointerException * If the row is <code>null</code>. * @throws IllegalArgumentException * If this row doesn't exist. */ protected int getRowIndex(final Row<?> row) { // Checks if the row is valid. if (row == null) { throw new NullPointerException("The row must not be null."); } // Gets the row group. final RowsGroup<?> parent = row.getParent(); // Computes group row index. final int groupRowIndex = getGroupRowIndex(parent); // Gets the row position. final int rowPosition = parent.getRowPosition(row); return groupRowIndex + rowPosition; } /** * Gets the number of rows in this view. * * @return The number of rows in this view. */ public int getRowsCount() { int count = 0; for (final RowsGroup<?> group : groupsOrderedList) { count += group.getRowsCount(); } return count; } }