Skip to content

Instantly share code, notes, and snippets.

@keithrbennett
Created November 27, 2009 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save keithrbennett/243802 to your computer and use it in GitHub Desktop.
Save keithrbennett/243802 to your computer and use it in GitHub Desktop.
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.model.IModel;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Collections2;
/**
* Utilities for navigating Wicket tables.
*
* @author Keith Bennett
* Copyright Computer Sciences Corporation, 2009
*/
public class TableNavigationUtils {
// TODO: Some of these methods are not table-specific and should
// probably be in a differently named class.
/**
* Gets a list of component id's from the specified component
* up its ancestry to the furthest ancestor (one level below
* the page).
*/
public static List<String> getComponentIdList(Component component) {
String path = component.getPageRelativePath();
return Arrays.asList(StringUtils.split(path, ":"));
}
/**
* Gets a list of components from the specified component
* up its ancestry to the furthest ancestor (one level below
* the page).
*/
public static List<Component> getComponentAncestorList(final Component component) {
List<String> ids = getComponentIdList(component);
List<Component> components = new ArrayList<Component>();
for (int i = 0; i < ids.size(); i++) {
List<String> idsToBuildPathWith = ids.subList(0, i);
String path = StringUtils.join(idsToBuildPathWith, ":");
Component c = component.getPage().get(path);
components.add(c);
}
return components;
}
/**
* Gets a list of all descendants of the specified component.
*/
public static List<Component> getComponentDescendantList(Component component) {
return component instanceof MarkupContainer
? getDescendantComponents((MarkupContainer) component)
: null;
}
/**
* Gets the first table found navigating up the specified component's ancestry.
*/
public static DataTable getTable(Component component) {
DataTable table;
if (component instanceof DataTable) {
table = (DataTable) component;
} else {
List<Component> components = getComponentAncestorList(component);
Collection<Component> componentCollection =
Collections2.filter(components, new InstanceOfPredicate(DataTable.class));
List<? extends Component> componentList = Lists.newArrayList(componentCollection);
List<? extends DataTable> tables = (List<? extends DataTable>) componentList;
table = tables.isEmpty()
? null
: tables.get(tables.size() - 1); // get last table
}
return table;
}
/**
* Gets the column number (1 offset) of the column (implementing ColumnWithKey)
* containing the specified key value.
*
* @param seedComponent any component in the table's hierarchy, or the table itself
*/
public static int getColumnNumber(Component seedComponent, String columnKey) {
DataTable table = getTable(seedComponent);
IColumn [] columns = table.getColumns();
for (int i = 0; i < columns.length; i++) {
IColumn column = columns[i];
if (column instanceof ColumnWithKey) {
String key = ((ColumnWithKey) column).getColumnKey();
if (key != null && key.equals(columnKey)) {
return i + 1;
}
}
}
return -1;
}
/**
* Gets the key of the column (implementing ColumnWithKey) with
* the specified column number (1 offset).
*
* @param seedComponent any component in the table's hierarchy,
* or the table itself
*/
public static String getColumnKey(Component seedComponent, int colnum) {
DataTable table = getTable(seedComponent);
IColumn column = table.getColumns()[colnum - 1];
return column instanceof ColumnWithKey
? ((ColumnWithKey) column).getColumnKey()
: null;
}
/**
* Gets the component in seedComponent's enclosing table with the specified row number
* and column key.
*
* @param seedComponent any component in the table's hierarchy,
* or the table itself
*/
public static Component getComponent(Component seedComponent, int rowNumber, String columnKey) {
DataTable table = getTable(seedComponent);
int columnNumber = getColumnNumber(table, columnKey);
return getComponentAt(table, rowNumber, columnNumber);
}
/**
* Gets the component in seedComponent's enclosing table with the specified row number
* and column number.
*
* @param seedComponent any component in the table's hierarchy, or the table itself
*/
public static Component getComponentAt(Component seedComponent, int rowNumber, int colNumber) {
DataTable table = getTable(seedComponent);
int adjustedRowNumber = rowNumber + getRowNumberOffset(table);
String path = buildCellId(table, adjustedRowNumber, colNumber);
return table.getPage().get(path);
}
/**
* Predicate that returns whether or not the object passed to apply()
* is an instance of the class passed to the predicate's constructor.
*/
public static class InstanceOfPredicate implements Predicate<Object> {
Class<?> clazz;
public InstanceOfPredicate(Class<?> clazz) {
this.clazz = clazz;
}
public boolean apply(Object o) {
return clazz.isAssignableFrom(o.getClass());
}
}
/**
* Gets the row number of the specified component.
*/
public static int getRowNumber(Component component) {
int index = getColumnNumberIndex(component);
return indexOk(index)
? getNumberBetweenIds(component, "rows", "cells") - getRowNumberOffset(component)
: -1;
}
/**
* Gets the column number (1 offset) of the specified component.
*/
public static int getColumnNumber(Component component) {
int index = getColumnNumberIndex(component);
return indexOk(index)
? getNumberBetweenIds(component, "cells", "cell")
: -1;
}
/**
* Builds a component path for the cell at the specified row and column.
* Note that seedComponent is used only to infer the table, and is not
* related with the component expressed in the component path.
*/
public static String buildCellId(Component seedComponent, int rownum, int colnum) {
return buildRowId(seedComponent, rownum)
+ ":cells:"
+ colnum
+ ":cell";
}
/**
* Builds a component path for the row at the specified row number.
* Note that seedComponent is used only to infer the table, and is not
* related with the component expressed in the component path.
*
* @rownum the *real* row number, that is, the actual number that
* needs to be inserted into the path. This number may reflect
* an offset other than 1 (see getRowNumberOffset).
*/
public static String buildRowId(Component seedComponent, int rownum) {
DataTable table = getTable(seedComponent);
return
table.getPageRelativePath()
+ ":rows:"
+ rownum;
}
/**
* Gets the row model of the specified row number.
*
* @param seedComponent any component in the table's hierarchy, or the table itself
*/
public static IModel getRowModel(Component seedComponent, int rownumOneOffset) {
DataTable table = getTable(seedComponent);
int adjustedRowNumber = rownumOneOffset + getRowNumberOffset(table);
String id = table.getPageRelativePath() + ":rows:" + adjustedRowNumber;
Component c = table.getPage().get(id);
return c instanceof Item
? ((Item) c).getModel()
: null;
}
/**
* Gets the index of the element between beforeId and afterId, for example:
* "form:table:rows:1:cells:1:cell"
* For beforeId = "cells", and afterId = "cell", 5 would be returned.
*/
public static int getIndexBetweenIds(Component component, String beforeId, String afterId) {
List<String> ids = getComponentIdList(component);
int indexBefore = ids.indexOf(beforeId);
int indexAfter = ids.indexOf(afterId);
boolean ok = indexOk(indexBefore) && indexOk(indexAfter);
return ok ? (indexBefore + 1) : -1;
}
/**
* Gets a number between 2 component id's in a component path, for example:
* "form:table:rows:1:cells:3:cell"
* For beforeId = "cells", and afterId = "cell", 3 would be returned.
*/
private static int getNumberBetweenIds(Component component, String beforeId, String afterId) {
int number = -1; // default to error value
int index = getIndexBetweenIds(component, beforeId, afterId);
if (indexOk(index)) {
String numAsString = getComponentIdList(component).get(index);
number = Integer.parseInt(numAsString);
}
return number;
}
/**
* Gets a list of descendant components in this container. For example:
*
* @param markupContainer the container (e.g. Panel)
* @return list of components (goes all levels down the hierarchy)
*/
public static List<Component> getDescendantComponents(MarkupContainer markupContainer) {
DescendantCollector descendantCollector = new DescendantCollector();
markupContainer.visitChildren(descendantCollector);
return descendantCollector.getDescendants();
}
/**
* Uses the visitor pattern to accumulate a list of all child
* components of a MarkupContainer.
*
* Used by getDescendantComponents().
*/
private static class DescendantCollector implements Component.IVisitor<Component> {
private List<Component> components = new ArrayList<Component>();
public Object component(Component component) {
components.add(component);
return CONTINUE_TRAVERSAL;
}
public List<Component> getDescendants() {
return components;
}
}
public static String getTableDump(Component seedComponent) {
DataTable table = getTable(seedComponent);
int numColumns = table.getColumns().length;
int numRows = table.getRowCount();
int firstRowNum = getRowNumberOffset(table);
StringBuilder sb = new StringBuilder();
sb.append("Table '").append(table.getId()).append(":\n\n");
sb.append("# Rows: ").append(numRows).append("\n");
sb.append("First Row #: ").append(firstRowNum).append("\n");
sb.append("# Columns: ").append(numColumns).append("\n");
sb.append("Current page #: ").append(table.getCurrentPage()).append("\n\n");
for (int rowNum = 0; rowNum < numRows; rowNum++) {
sb.append("Row #").append(rowNum).append(" (").append(rowNum + firstRowNum).append("): \n");
for (int j = 0; j < numColumns; j++) {
int col = j + 1;
Component c = getComponentAt(table, rowNum, col);
String classname = c != null ? c.getClass().getName() : "(none)";
sb.append(" ").append(col).append(": ").append(classname).append("\n");
}
}
sb.append("\nTable descendant components:\n\n");
for (Component c : getDescendantComponents(table)) {
sb.append(c.getPageRelativePath()).append(": ").append(c.getClass()).append("\n");
}
return sb.toString();
}
private static boolean indexOk(int index) {
return index >= 0;
}
/**
* Gets the index in the component's path of the column number, e.g., in
* "form:table:rows:1:cells:1:cell"
* the number 5 would be returned.
*/
private static int getColumnNumberIndex(Component component) {
return getIndexBetweenIds(component, "cells", "cell");
}
/**
* Gets the index in the component's path of the row number, e.g., in
* "form:table:rows:1:cells:1:cell"
* the number 3 would be returned.
*/
private static int getRowNumberIndex(Component component) {
DataTable table = getTable(component);
return StringUtils.countMatches(table.getPageRelativePath(), ":") + 2;
}
/**
* When Wicket recreates table elements, it does not reuse Item's,
* and also does not reuse row numbers. Thus, when a table is
* first created, the lowest row number (aka row offset) is 1,
* but when that table is refreshed, the row numbers will increase.
* For example, a five row table might start out containing 5 rows
* numbered 1-5, but after a refresh would contain 5 rows
* numbered 6-10.
*
* @param seedComponent any component in the table's hierarchy, or the table itself
* @return row offset (i.e. lowest row number in component path
*/
private static int getRowNumberOffset(Component seedComponent) {
DataTable table = getTable(seedComponent);
Integer minRowNumber = Integer.MAX_VALUE;
Collection<Component> rowNumberComponents = Collections2.filter(
getDescendantComponents(table),
new IsRowNumberComponentPredicate());
int rowNumberIndex = getRowNumberIndex(table);
for (Component component : rowNumberComponents) {
String rowNumberAsString = getComponentIdList(component).get(rowNumberIndex);
int rowNumber = Integer.parseInt(rowNumberAsString);
minRowNumber = Math.min(minRowNumber, rowNumber);
}
return minRowNumber;
}
public static class IsRowNumberComponentPredicate implements Predicate<Component> {
@Override
public boolean apply(Component component) {
boolean isRowNumberItem = false; // default
if (component instanceof Item) {
String path = component.getPageRelativePath();
isRowNumberItem = path.contains(":rows:") && (! path.contains(":cells:"));
}
return isRowNumberItem;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment