Made facets deal with java.util.Collection rather than just Object[].

Documented the browsing.* packages.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@330 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-03-21 07:14:39 +00:00
parent d90e75dff1
commit 7648126a5e
17 changed files with 284 additions and 99 deletions

View File

@ -7,6 +7,10 @@ import com.metaweb.gridworks.browsing.filters.RowFilter;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Encapsulate logic for visiting rows that match all give row filters. Also visit
* context rows and dependent rows if configured so.
*/
public class ConjunctiveFilteredRows implements FilteredRows { public class ConjunctiveFilteredRows implements FilteredRows {
final protected List<RowFilter> _rowFilters = new LinkedList<RowFilter>(); final protected List<RowFilter> _rowFilters = new LinkedList<RowFilter>();
final protected boolean _includeContextual; final protected boolean _includeContextual;
@ -22,48 +26,57 @@ public class ConjunctiveFilteredRows implements FilteredRows {
} }
public void accept(Project project, RowVisitor visitor) { public void accept(Project project, RowVisitor visitor) {
int lastVisitedRow = -1; int lastVisitedRowRowIndex = -1;
int lastRecordRowAccepted = -1; int lastRecordRowAcceptedRowIndex = -1;
int c = project.rows.size(); int c = project.rows.size();
for (int i = 0; i < c; i++) { for (int rowIndex = 0; rowIndex < c; rowIndex++) {
Row row = project.rows.get(i); Row row = project.rows.get(rowIndex);
if (checkRow(project, i, row)) { if (matchRow(project, rowIndex, row)) {
if (row.recordIndex >= 0) { if (row.recordIndex >= 0) {
// this is a record row itself lastRecordRowAcceptedRowIndex = rowIndex; // this is a record row itself
lastRecordRowAccepted = i;
} }
visitRow(project, visitor, i, row, lastVisitedRow); visitRow(project, visitor, rowIndex, row, lastVisitedRowRowIndex);
lastVisitedRow = i; lastVisitedRowRowIndex = rowIndex;
} else if ( } else if (
// this row doesn't match by itself but ...
// we want to include dependent rows
_includeDependent && _includeDependent &&
// and this row is a dependent row since it's not a record row
row.recordIndex < 0 && row.recordIndex < 0 &&
row.contextRows != null && row.contextRows != null &&
row.contextRows.size() > 0) { row.contextRows.size() > 0
) {
if (row.contextRows.get(0) == lastRecordRowAccepted) { if (row.contextRows.get(0) == lastRecordRowAcceptedRowIndex) {
visitor.visit(project, i, row, false, true); // this row depends on the last previously matched record row,
lastVisitedRow = i; // so we visit it as well as a dependent row
visitor.visit(project, rowIndex, row, false, true);
lastVisitedRowRowIndex = rowIndex;
} }
} }
} }
} }
protected void visitRow(Project project, RowVisitor visitor, int rowIndex, Row row, int lastVisitedRow) { protected void visitRow(Project project, RowVisitor visitor, int rowIndex, Row row, int lastVisitedRow) {
if (_includeContextual) { if (_includeContextual && // we need to include any context row and
if (row.contextRows != null && lastVisitedRow < rowIndex - 1) { row.contextRows != null && // this row itself isn't a context row and
lastVisitedRow < rowIndex - 1 // there is definitely some rows before this row
// that we haven't visited yet
) {
for (int contextRowIndex : row.contextRows) { for (int contextRowIndex : row.contextRows) {
if (contextRowIndex > lastVisitedRow) { if (contextRowIndex > lastVisitedRow) {
visitor.visit( visitor.visit(
project, project,
contextRowIndex, contextRowIndex,
project.rows.get(contextRowIndex), project.rows.get(contextRowIndex),
true, true, // is visited as a context row
false false // is not visited as a dependent row
); );
lastVisitedRow = contextRowIndex; lastVisitedRow = contextRowIndex;
} }
@ -71,12 +84,9 @@ public class ConjunctiveFilteredRows implements FilteredRows {
} }
visitor.visit(project, rowIndex, row, false, false); visitor.visit(project, rowIndex, row, false, false);
} else {
visitor.visit(project, rowIndex, row, false, false);
}
} }
protected boolean checkRow(Project project, int rowIndex, Row row) { protected boolean matchRow(Project project, int rowIndex, Row row) {
for (RowFilter rowFilter : _rowFilters) { for (RowFilter rowFilter : _rowFilters) {
if (!rowFilter.filterRow(project, rowIndex, row)) { if (!rowFilter.filterRow(project, rowIndex, row)) {
return false; return false;

View File

@ -7,6 +7,13 @@ import org.json.JSONWriter;
import com.metaweb.gridworks.Jsonizable; import com.metaweb.gridworks.Jsonizable;
/**
* Store a value and its text label, in case the value is not a string itself.
* For instance, if a value is a date, then its label can be one particular
* rendering of that date.
*
* Facet choices that are presented to the user as text are stored as decorated values.
*/
public class DecoratedValue implements Jsonizable { public class DecoratedValue implements Jsonizable {
final public Object value; final public Object value;
final public String label; final public String label;

View File

@ -17,6 +17,9 @@ import com.metaweb.gridworks.browsing.facets.TextSearchFacet;
import com.metaweb.gridworks.browsing.filters.RowFilter; import com.metaweb.gridworks.browsing.filters.RowFilter;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
/**
* Faceted browsing engine.
*/
public class Engine implements Jsonizable { public class Engine implements Jsonizable {
protected Project _project; protected Project _project;
protected List<Facet> _facets = new LinkedList<Facet>(); protected List<Facet> _facets = new LinkedList<Facet>();

View File

@ -2,6 +2,18 @@ package com.metaweb.gridworks.browsing;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
/**
* Interface for anything that can decide which rows match and which rows don't match
* based on some particular criteria.
*/
public interface FilteredRows { public interface FilteredRows {
/**
* Go through the rows of the given project, determine which match and which don't,
* and call visitor.visit() on those that match, and possibly their context and
* dependent rows.
*
* @param project
* @param visitor
*/
public void accept(Project project, RowVisitor visitor); public void accept(Project project, RowVisitor visitor);
} }

View File

@ -3,12 +3,20 @@ package com.metaweb.gridworks.browsing;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Interface for visiting rows one by one. The rows visited are only those that match some
* particular criteria, such as facets' constraints, or those that are related to the matched
* rows. The related rows can be those that the matched rows depend on, or those that depend
* on the matched rows.
*/
public interface RowVisitor { public interface RowVisitor {
public boolean visit( public boolean visit(
Project project, Project project,
int rowIndex, // zero-based row index int rowIndex, // zero-based row index
Row row, Row row,
boolean contextual, // true if this row is included because it's the context row of an included row boolean contextual, // true if this row is included because it's the context row
boolean dependent // true if this row is included because it depends on an included row // of a matched row, that is, a matched row depends on it
boolean dependent // true if this row is included because it depends on a matched row,
// that is, it depends on a matched row
); );
} }

View File

@ -1,5 +1,6 @@
package com.metaweb.gridworks.browsing.facets; package com.metaweb.gridworks.browsing.facets;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
@ -12,10 +13,20 @@ import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Visit matched rows and group them into facet choices based on the values computed
* from a given expression.
*/
public class ExpressionNominalRowGrouper implements RowVisitor { public class ExpressionNominalRowGrouper implements RowVisitor {
/*
* Configuration
*/
final protected Evaluable _evaluable; final protected Evaluable _evaluable;
final protected int _cellIndex; final protected int _cellIndex;
/*
* Computed results
*/
final public Map<Object, NominalFacetChoice> choices = new HashMap<Object, NominalFacetChoice>(); final public Map<Object, NominalFacetChoice> choices = new HashMap<Object, NominalFacetChoice>();
public int blankCount = 0; public int blankCount = 0;
public int errorCount = 0; public int errorCount = 0;
@ -32,15 +43,23 @@ public class ExpressionNominalRowGrouper implements RowVisitor {
ExpressionUtils.bind(bindings, row, rowIndex, cell); ExpressionUtils.bind(bindings, row, rowIndex, cell);
Object value = _evaluable.evaluate(bindings); Object value = _evaluable.evaluate(bindings);
if (value != null && value.getClass().isArray()) { if (value != null) {
if (value.getClass().isArray()) {
Object[] a = (Object[]) value; Object[] a = (Object[]) value;
for (Object v : a) { for (Object v : a) {
processValue(v); processValue(v);
} }
} else { return false;
processValue(value); } else if (value instanceof Collection<?>) {
for (Object v : ExpressionUtils.toObjectCollection(value)) {
processValue(v);
} }
return false; return false;
} // else, fall through
}
processValue(value);
return false;
} }
protected void processValue(Object value) { protected void processValue(Object value) {

View File

@ -1,5 +1,6 @@
package com.metaweb.gridworks.browsing.facets; package com.metaweb.gridworks.browsing.facets;
import java.util.Collection;
import java.util.Properties; import java.util.Properties;
import com.metaweb.gridworks.browsing.RowVisitor; import com.metaweb.gridworks.browsing.RowVisitor;
@ -9,13 +10,22 @@ import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Visit matched rows and slot them into bins based on the numbers computed
* from a given expression.
*/
public class ExpressionNumericRowBinner implements RowVisitor { public class ExpressionNumericRowBinner implements RowVisitor {
/*
* Configuration
*/
final protected Evaluable _evaluable; final protected Evaluable _evaluable;
final protected int _cellIndex; final protected int _cellIndex;
final protected NumericBinIndex _index; final protected NumericBinIndex _index; // base bins
/*
* Computed results
*/
final public int[] bins; final public int[] bins;
public int numericCount; public int numericCount;
public int nonNumericCount; public int nonNumericCount;
public int blankCount; public int blankCount;
@ -35,15 +45,23 @@ public class ExpressionNumericRowBinner implements RowVisitor {
ExpressionUtils.bind(bindings, row, rowIndex, cell); ExpressionUtils.bind(bindings, row, rowIndex, cell);
Object value = _evaluable.evaluate(bindings); Object value = _evaluable.evaluate(bindings);
if (value != null && value.getClass().isArray()) { if (value != null) {
if (value.getClass().isArray()) {
Object[] a = (Object[]) value; Object[] a = (Object[]) value;
for (Object v : a) { for (Object v : a) {
processValue(v); processValue(v);
} }
} else { return false;
processValue(value); } else if (value instanceof Collection<?>) {
for (Object v : ExpressionUtils.toObjectCollection(value)) {
processValue(v);
} }
return false; return false;
} // else, fall through
}
processValue(value);
return false;
} }
protected void processValue(Object value) { protected void processValue(Object value) {

View File

@ -7,6 +7,9 @@ import com.metaweb.gridworks.browsing.FilteredRows;
import com.metaweb.gridworks.browsing.filters.RowFilter; import com.metaweb.gridworks.browsing.filters.RowFilter;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
/**
* Interface of facets.
*/
public interface Facet extends Jsonizable { public interface Facet extends Jsonizable {
public RowFilter getRowFilter(); public RowFilter getRowFilter();

View File

@ -21,24 +21,31 @@ import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.util.JSONUtilities; import com.metaweb.gridworks.util.JSONUtilities;
public class ListFacet implements Facet { public class ListFacet implements Facet {
protected List<NominalFacetChoice> _selection = new LinkedList<NominalFacetChoice>(); /*
* Configuration
*/
protected String _name;
protected String _expression;
protected String _columnName;
// If true, then facet won't show the blank and error choices // If true, then facet won't show the blank and error choices
protected boolean _omitBlank; protected boolean _omitBlank;
protected boolean _omitError; protected boolean _omitError;
protected List<NominalFacetChoice> _selection = new LinkedList<NominalFacetChoice>();
protected boolean _selectBlank; protected boolean _selectBlank;
protected boolean _selectError; protected boolean _selectError;
protected String _name; /*
protected String _expression; * Derived configuration
protected String _columnName; */
protected int _cellIndex; protected int _cellIndex;
protected Evaluable _eval; protected Evaluable _eval;
protected String _errorMessage; protected String _errorMessage;
// computed /*
* Computed results
*/
protected List<NominalFacetChoice> _choices = new LinkedList<NominalFacetChoice>(); protected List<NominalFacetChoice> _choices = new LinkedList<NominalFacetChoice>();
protected int _blankCount; protected int _blankCount;
protected int _errorCount; protected int _errorCount;
@ -157,9 +164,20 @@ public class ListFacet implements Facet {
for (NominalFacetChoice choice : _selection) { for (NominalFacetChoice choice : _selection) {
String valueString = choice.decoratedValue.value.toString(); String valueString = choice.decoratedValue.value.toString();
if (grouper.choices.containsKey(valueString)) { if (grouper.choices.containsKey(valueString)) {
grouper.choices.get(valueString).selected = true; grouper.choices.get(valueString).selected = true;
} else { } else {
/*
* A selected choice can have zero count if it is selected together
* with other choices, and some other facets' constraints eliminate
* all rows projected to this choice altogether. For example, if you
* select both "car" and "bicycle" in the "type of vehicle" facet, and
* then constrain the "wheels" facet to more than 2, then the "bicycle"
* choice now has zero count even if it's still selected. The grouper
* won't be able to detect the "bicycle" choice, so we need to inject
* that choice into the choice list ourselves.
*/
choice.count = 0; choice.count = 0;
_choices.add(choice); _choices.add(choice);
} }

View File

@ -8,6 +8,10 @@ import org.json.JSONWriter;
import com.metaweb.gridworks.Jsonizable; import com.metaweb.gridworks.Jsonizable;
import com.metaweb.gridworks.browsing.DecoratedValue; import com.metaweb.gridworks.browsing.DecoratedValue;
/**
* Store a facet choice that has a decorated value, a count of matched rows,
* and a flag of whether it has been selected.
*/
public class NominalFacetChoice implements Jsonizable { public class NominalFacetChoice implements Jsonizable {
final public DecoratedValue decoratedValue; final public DecoratedValue decoratedValue;
public int count; public int count;

View File

@ -1,6 +1,7 @@
package com.metaweb.gridworks.browsing.facets; package com.metaweb.gridworks.browsing.facets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
@ -10,6 +11,16 @@ import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* A utility class for computing the base bins that form the base histograms of
* numeric range facets. It evaluates an expression on all the rows of a project to
* get numeric values, determines how many bins to distribute those values in, and
* bins the rows accordingly.
*
* This class processes all rows rather than just the filtered rows because it
* needs to compute the base bins of a numeric range facet, which remain unchanged
* as the user interacts with the facet.
*/
public class NumericBinIndex { public class NumericBinIndex {
private double _min; private double _min;
private double _max; private double _max;
@ -38,6 +49,12 @@ public class NumericBinIndex {
processValue(((Number) v).doubleValue(), allValues); processValue(((Number) v).doubleValue(), allValues);
} }
} }
} else if (value instanceof Collection<?>) {
for (Object v : ExpressionUtils.toObjectCollection(value)) {
if (v instanceof Number) {
processValue(((Number) v).doubleValue(), allValues);
}
}
} else if (value instanceof Number) { } else if (value instanceof Number) {
processValue(((Number) value).doubleValue(), allValues); processValue(((Number) value).doubleValue(), allValues);
} }

View File

@ -17,33 +17,45 @@ import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.util.JSONUtilities; import com.metaweb.gridworks.util.JSONUtilities;
public class RangeFacet implements Facet { public class RangeFacet implements Facet {
protected String _name; /*
protected String _expression; * Configuration, from the client side
protected String _columnName; */
protected String _name; // name of facet
protected String _expression; // expression to compute numeric value(s) per row
protected String _columnName; // column to base expression on, if any
protected String _mode; // "range", "min", "max"
protected double _from; // the numeric selection
protected double _to;
protected boolean _selectNumeric; // whether the numeric selection applies, default true
protected boolean _selectNonNumeric;
protected boolean _selectBlank;
protected boolean _selectError;
/*
* Derived configuration data
*/
protected int _cellIndex; protected int _cellIndex;
protected Evaluable _eval; protected Evaluable _eval;
protected String _errorMessage; protected String _errorMessage;
protected boolean _selected; // false if we're certain that all rows will match
// and there isn't any filtering to do
protected String _mode; /*
* Computed data, to return to the client side
*/
protected double _min; protected double _min;
protected double _max; protected double _max;
protected double _step; protected double _step;
protected int[] _baseBins; protected int[] _baseBins;
protected int[] _bins; protected int[] _bins;
protected int _numericCount; protected int _numericCount;
protected int _nonNumericCount; protected int _nonNumericCount;
protected int _blankCount; protected int _blankCount;
protected int _errorCount; protected int _errorCount;
protected double _from;
protected double _to;
protected boolean _selected;
protected boolean _selectNumeric;
protected boolean _selectNonNumeric;
protected boolean _selectBlank;
protected boolean _selectError;
public RangeFacet() { public RangeFacet() {
} }

View File

@ -15,15 +15,21 @@ import com.metaweb.gridworks.gel.ast.VariableExpr;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
public class TextSearchFacet implements Facet { public class TextSearchFacet implements Facet {
/*
* Configuration
*/
protected String _name; protected String _name;
protected String _columnName; protected String _columnName;
protected int _cellIndex;
protected String _query; protected String _query;
protected Pattern _pattern;
protected String _mode; protected String _mode;
protected boolean _caseSensitive; protected boolean _caseSensitive;
/*
* Derived configuration
*/
protected int _cellIndex;
protected Pattern _pattern;
public TextSearchFacet() { public TextSearchFacet() {
} }
@ -42,6 +48,7 @@ public class TextSearchFacet implements Facet {
public void initializeFromJSON(Project project, JSONObject o) throws Exception { public void initializeFromJSON(Project project, JSONObject o) throws Exception {
_name = o.getString("name"); _name = o.getString("name");
_columnName = o.getString("columnName"); _columnName = o.getString("columnName");
_cellIndex = project.columnModel.getColumnByName(_columnName).getCellIndex(); _cellIndex = project.columnModel.getColumnByName(_columnName).getCellIndex();
if (!o.isNull("query")) { if (!o.isNull("query")) {

View File

@ -1,5 +1,6 @@
package com.metaweb.gridworks.browsing.filters; package com.metaweb.gridworks.browsing.filters;
import java.util.Collection;
import java.util.Properties; import java.util.Properties;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
@ -8,14 +9,28 @@ import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Judge if a row matches by evaluating a given expression on the row, based on a particular
* column, and checking the result. It's a match if the result is any one of a given list of
* values, or if the result is blank or error and we want blank or error values.
*/
public class ExpressionEqualRowFilter implements RowFilter { public class ExpressionEqualRowFilter implements RowFilter {
final protected Evaluable _evaluable; final protected Evaluable _evaluable; // the expression to evaluate
final protected int _cellIndex; final protected int _cellIndex; // the expression is based on this column;
// -1 if based on no column in particular,
// for expression such as "row.starred".
final protected Object[] _matches; final protected Object[] _matches;
final protected boolean _selectBlank; final protected boolean _selectBlank;
final protected boolean _selectError; final protected boolean _selectError;
public ExpressionEqualRowFilter(Evaluable evaluable, int cellIndex, Object[] matches, boolean selectBlank, boolean selectError) { public ExpressionEqualRowFilter(
Evaluable evaluable,
int cellIndex,
Object[] matches,
boolean selectBlank,
boolean selectError
) {
_evaluable = evaluable; _evaluable = evaluable;
_cellIndex = cellIndex; _cellIndex = cellIndex;
_matches = matches; _matches = matches;
@ -30,17 +45,26 @@ public class ExpressionEqualRowFilter implements RowFilter {
ExpressionUtils.bind(bindings, row, rowIndex, cell); ExpressionUtils.bind(bindings, row, rowIndex, cell);
Object value = _evaluable.evaluate(bindings); Object value = _evaluable.evaluate(bindings);
if (value != null && value.getClass().isArray()) { if (value != null) {
if (value.getClass().isArray()) {
Object[] a = (Object[]) value; Object[] a = (Object[]) value;
for (Object v : a) { for (Object v : a) {
if (testValue(v)) { if (testValue(v)) {
return true; return true;
} }
} }
} else { return false;
return testValue(value); } else if (value instanceof Collection<?>) {
for (Object v : ExpressionUtils.toObjectCollection(value)) {
if (testValue(v)) {
return true;
}
} }
return false; return false;
} // else, fall through
}
return testValue(value);
} }
protected boolean testValue(Object v) { protected boolean testValue(Object v) {

View File

@ -1,5 +1,6 @@
package com.metaweb.gridworks.browsing.filters; package com.metaweb.gridworks.browsing.filters;
import java.util.Collection;
import java.util.Properties; import java.util.Properties;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
@ -8,6 +9,12 @@ import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Judge if a row matches by evaluating a given expression on the row, based on a particular
* column, and checking the result. It's a match if the result satisfies some numeric comparisons,
* or if the result is non-numeric or blank or error and we want non-numeric or blank or error
* values.
*/
abstract public class ExpressionNumberComparisonRowFilter implements RowFilter { abstract public class ExpressionNumberComparisonRowFilter implements RowFilter {
final protected Evaluable _evaluable; final protected Evaluable _evaluable;
final protected int _cellIndex; final protected int _cellIndex;
@ -33,25 +40,32 @@ abstract public class ExpressionNumberComparisonRowFilter implements RowFilter {
} }
public boolean filterRow(Project project, int rowIndex, Row row) { public boolean filterRow(Project project, int rowIndex, Row row) {
Cell cell = row.getCell(_cellIndex); Cell cell = _cellIndex < 0 ? null : row.getCell(_cellIndex);
Properties bindings = ExpressionUtils.createBindings(project); Properties bindings = ExpressionUtils.createBindings(project);
ExpressionUtils.bind(bindings, row, rowIndex, cell); ExpressionUtils.bind(bindings, row, rowIndex, cell);
Object value = _evaluable.evaluate(bindings); Object value = _evaluable.evaluate(bindings);
if (value != null && value.getClass().isArray()) { if (value != null) {
if (value.getClass().isArray()) {
Object[] a = (Object[]) value; Object[] a = (Object[]) value;
for (Object v : a) { for (Object v : a) {
if (checkValue(v)) { if (checkValue(v)) {
return true; return true;
} }
} }
} else { return false;
if (checkValue(value)) { } else if (value instanceof Collection<?>) {
for (Object v : ExpressionUtils.toObjectCollection(value)) {
if (checkValue(v)) {
return true; return true;
} }
} }
return false; return false;
} // else, fall through
}
return checkValue(value);
} }
protected boolean checkValue(Object v) { protected boolean checkValue(Object v) {

View File

@ -8,6 +8,10 @@ import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Judge if a row matches by evaluating a given expression on the row, based on a particular
* column, and checking the result. It's a match if the result satisfies some string comparisons.
*/
abstract public class ExpressionStringComparisonRowFilter implements RowFilter { abstract public class ExpressionStringComparisonRowFilter implements RowFilter {
final protected Evaluable _evaluable; final protected Evaluable _evaluable;
final protected int _cellIndex; final protected int _cellIndex;
@ -18,7 +22,8 @@ abstract public class ExpressionStringComparisonRowFilter implements RowFilter {
} }
public boolean filterRow(Project project, int rowIndex, Row row) { public boolean filterRow(Project project, int rowIndex, Row row) {
Cell cell = row.getCell(_cellIndex); Cell cell = _cellIndex < 0 ? null : row.getCell(_cellIndex);
Properties bindings = ExpressionUtils.createBindings(project); Properties bindings = ExpressionUtils.createBindings(project);
ExpressionUtils.bind(bindings, row, rowIndex, cell); ExpressionUtils.bind(bindings, row, rowIndex, cell);

View File

@ -3,6 +3,10 @@ package com.metaweb.gridworks.browsing.filters;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
/**
* Interface for judging if a particular row matches or doesn't match some
* particular criterion, such as a facet constraint.
*/
public interface RowFilter { public interface RowFilter {
public boolean filterRow(Project project, int rowIndex, Row row); public boolean filterRow(Project project, int rowIndex, Row row);
} }