Created
November 27, 2009 03:08
-
-
Save keithrbennett/243802 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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