More server-side scaffolding for faceted browsing.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@11 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-01-28 01:43:09 +00:00
parent 23b9e313b8
commit e0365f45c8
12 changed files with 289 additions and 44 deletions

View File

@ -16,7 +16,9 @@ public class ConjunctiveFilteredRows implements FilteredRows {
@Override
public void accept(Project project, RowVisitor visitor) {
for (Row row : project.rows) {
for (int i = 0; i < project.rows.size(); i++) {
Row row = project.rows.get(i);
boolean ok = true;
for (RowFilter rowFilter : _rowFilters) {
if (!rowFilter.filterRow(row)) {
@ -26,7 +28,7 @@ public class ConjunctiveFilteredRows implements FilteredRows {
}
if (ok) {
visitor.visit(row);
visitor.visit(i, row);
}
}
}

View File

@ -11,9 +11,15 @@ import org.json.JSONObject;
import com.metaweb.gridlock.browsing.facets.Facet;
import com.metaweb.gridlock.browsing.facets.ListFacet;
import com.metaweb.gridlock.model.Project;
public class Engine {
protected List<Facet> facets = new LinkedList<Facet>();
protected Project _project;
protected List<Facet> _facets = new LinkedList<Facet>();
public Engine(Project project) {
_project = project;
}
public FilteredRows getAllFilteredRows() {
return getFilteredRows(null);
@ -21,7 +27,7 @@ public class Engine {
public FilteredRows getFilteredRows(Facet except) {
ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows();
for (Facet facet : facets) {
for (Facet facet : _facets) {
if (facet != except) {
cfr.add(facet.getRowFilter());
}
@ -32,8 +38,8 @@ public class Engine {
public JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
List<JSONObject> a = new ArrayList<JSONObject>(facets.size());
for (Facet facet : facets) {
List<JSONObject> a = new ArrayList<JSONObject>(_facets.size());
for (Facet facet : _facets) {
a.add(facet.getJSON(options));
}
o.put("facets", a);
@ -41,7 +47,7 @@ public class Engine {
return o;
}
public void initializeFromJSON(JSONObject o) throws JSONException {
public void initializeFromJSON(JSONObject o) throws Exception {
JSONArray a = o.getJSONArray("facets");
int length = a.length();
@ -56,16 +62,16 @@ public class Engine {
if (facet != null) {
facet.initializeFromJSON(fo);
facets.add(facet);
_facets.add(facet);
}
}
}
public void computeFacets() throws JSONException {
for (Facet facet : facets) {
for (Facet facet : _facets) {
FilteredRows filteredRows = getFilteredRows(facet);
facet.computeChoices(filteredRows);
facet.computeChoices(_project, filteredRows);
}
}
}

View File

@ -3,6 +3,6 @@ package com.metaweb.gridlock.browsing;
import com.metaweb.gridlock.model.Row;
public interface RowVisitor {
public void visit(Row row);
public void visit(int rowIndex, Row row);
}

View File

@ -1,5 +1,10 @@
package com.metaweb.gridlock.browsing.accessors;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
public class DecoratedValue {
final public Object value;
final public String label;
@ -8,4 +13,13 @@ public class DecoratedValue {
this.value = value;
this.label = label;
}
public JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
o.put("v", value);
o.put("l", label);
return o;
}
}

View File

@ -21,7 +21,7 @@ public class CellAccessorNominalRowGrouper implements RowVisitor {
}
@Override
public void visit(Row row) {
public void visit(int rowIndex, Row row) {
if (_cellIndex < row.cells.size()) {
Cell cell = row.cells.get(_cellIndex);
if (cell != null) {
@ -35,10 +35,10 @@ public class CellAccessorNominalRowGrouper implements RowVisitor {
new DecoratedValue(value, value.toString());
Object v = dValue.value;
if (choices.containsKey(value)) {
choices.get(value).count++;
if (choices.containsKey(v)) {
choices.get(v).count++;
} else {
NominalFacetChoice choice = new NominalFacetChoice(dValue, v);
NominalFacetChoice choice = new NominalFacetChoice(dValue);
choice.count = 1;
choices.put(v, choice);

View File

@ -0,0 +1,50 @@
package com.metaweb.gridlock.browsing.facets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import com.metaweb.gridlock.browsing.RowVisitor;
import com.metaweb.gridlock.browsing.accessors.DecoratedValue;
import com.metaweb.gridlock.expr.Evaluable;
import com.metaweb.gridlock.model.Cell;
import com.metaweb.gridlock.model.Row;
public class ExpressionNominalRowGrouper implements RowVisitor {
final protected Evaluable _evaluable;
final protected int _cellIndex;
final public Map<Object, NominalFacetChoice> choices = new HashMap<Object, NominalFacetChoice>();
public ExpressionNominalRowGrouper(Evaluable evaluable, int cellIndex) {
_evaluable = evaluable;
_cellIndex = cellIndex;
}
@Override
public void visit(int rowIndex, Row row) {
if (_cellIndex < row.cells.size()) {
Cell cell = row.cells.get(_cellIndex);
if (cell != null) {
Properties bindings = new Properties();
bindings.put("this", cell);
bindings.put("value", cell.value);
Object value = _evaluable.evaluate(bindings);
if (value != null) {
DecoratedValue dValue = new DecoratedValue(value, value.toString());
if (choices.containsKey(value)) {
choices.get(value).count++;
} else {
NominalFacetChoice choice = new NominalFacetChoice(dValue);
choice.count = 1;
choices.put(value, choice);
}
}
}
}
}
}

View File

@ -7,13 +7,14 @@ import org.json.JSONObject;
import com.metaweb.gridlock.browsing.FilteredRows;
import com.metaweb.gridlock.browsing.filters.RowFilter;
import com.metaweb.gridlock.model.Project;
public interface Facet {
public RowFilter getRowFilter();
public void computeChoices(FilteredRows filteredRows);
public void computeChoices(Project project, FilteredRows filteredRows);
public JSONObject getJSON(Properties options) throws JSONException;
public void initializeFromJSON(JSONObject o) throws JSONException;
public void initializeFromJSON(JSONObject o) throws Exception;
}

View File

@ -1,5 +1,6 @@
package com.metaweb.gridlock.browsing.facets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
@ -9,37 +10,91 @@ import org.json.JSONException;
import org.json.JSONObject;
import com.metaweb.gridlock.browsing.FilteredRows;
import com.metaweb.gridlock.browsing.accessors.DecoratedValue;
import com.metaweb.gridlock.browsing.filters.ExpressionEqualRowFilter;
import com.metaweb.gridlock.browsing.filters.RowFilter;
import com.metaweb.gridlock.expr.Evaluable;
import com.metaweb.gridlock.expr.Parser;
import com.metaweb.gridlock.model.Project;
public class ListFacet implements Facet {
final protected List<Object> _choices = new LinkedList<Object>();
@Override
public JSONObject getJSON(Properties options) throws JSONException {
// TODO Auto-generated method stub
return null;
protected List<NominalFacetChoice> _selection = new LinkedList<NominalFacetChoice>();
protected List<NominalFacetChoice> _choices = new LinkedList<NominalFacetChoice>();
protected String _name;
protected String _expression;
protected int _cellIndex;
protected Evaluable _eval;
public ListFacet() {
}
@Override
public void initializeFromJSON(JSONObject o) throws JSONException {
JSONArray a = o.getJSONArray("choices");
public JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
o.put("name", _name);
o.put("expression", _expression);
o.put("cellIndex", _cellIndex);
List<JSONObject> a = new ArrayList<JSONObject>(_choices.size());
for (NominalFacetChoice choice : _choices) {
a.add(choice.getJSON(options));
}
o.put("choices", a);
return o;
}
@Override
public void initializeFromJSON(JSONObject o) throws Exception {
_name = o.getString("name");
_expression = o.getString("expression");
_cellIndex = o.getInt("cellIndex");
_eval = new Parser(_expression).getExpression();
_selection.clear();
JSONArray a = o.getJSONArray("selection");
int length = a.length();
for (int i = 0; i < length; i++) {
JSONObject oc = a.getJSONObject(i);
JSONObject ocv = oc.getJSONObject("v");
DecoratedValue decoratedValue = new DecoratedValue(
ocv.get("v"), ocv.getString("l"));
NominalFacetChoice nominalFacetChoice = new NominalFacetChoice(decoratedValue);
nominalFacetChoice.count = oc.getInt("c");
nominalFacetChoice.selected = oc.getBoolean("s");
_selection.add(nominalFacetChoice);
}
}
@Override
public RowFilter getRowFilter() {
// TODO Auto-generated method stub
return null;
return new ExpressionEqualRowFilter(_eval, _cellIndex, createMatches());
}
@Override
public void computeChoices(FilteredRows filteredRows) {
// TODO Auto-generated method stub
public void computeChoices(Project project, FilteredRows filteredRows) {
ExpressionNominalRowGrouper grouper =
new ExpressionNominalRowGrouper(_eval, _cellIndex);
filteredRows.accept(project, grouper);
_choices.clear();
_choices.addAll(grouper.choices.values());
}
protected Object[] createMatches() {
Object[] a = new Object[_choices.size()];
for (int i = 0; i < a.length; i++) {
a[i] = _choices.get(i).decoratedValue.value;
}
return a;
}
}

View File

@ -1,14 +1,28 @@
package com.metaweb.gridlock.browsing.facets;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
import com.metaweb.gridlock.browsing.accessors.DecoratedValue;
public class NominalFacetChoice {
final public DecoratedValue decoratedValue;
final public Object value;
public int count;
public boolean selected;
public NominalFacetChoice(DecoratedValue decoratedValue, Object value) {
public NominalFacetChoice(DecoratedValue decoratedValue) {
this.decoratedValue = decoratedValue;
this.value = value;
}
public JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
o.put("v", decoratedValue.getJSON(options));
o.put("c", count);
o.put("s", selected);
return o;
}
}

View File

@ -0,0 +1,42 @@
package com.metaweb.gridlock.browsing.filters;
import java.util.Properties;
import com.metaweb.gridlock.expr.Evaluable;
import com.metaweb.gridlock.model.Cell;
import com.metaweb.gridlock.model.Row;
public class ExpressionEqualRowFilter implements RowFilter {
final protected Evaluable _evaluable;
final protected int _cellIndex;
final protected Object[] _matches;
public ExpressionEqualRowFilter(Evaluable evaluable, int cellIndex, Object[] matches) {
_evaluable = evaluable;
_cellIndex = cellIndex;
_matches = matches;
}
@Override
public boolean filterRow(Row row) {
if (_cellIndex < row.cells.size()) {
Cell cell = row.cells.get(_cellIndex);
if (cell != null) {
Properties bindings = new Properties();
bindings.put("this", cell);
bindings.put("value", cell.value);
Object value = _evaluable.evaluate(bindings);
if (value != null) {
for (Object match : _matches) {
if (match.equals(value)) {
return true;
}
}
}
}
}
return false;
}
}

View File

@ -20,6 +20,7 @@ import org.json.JSONObject;
import org.json.JSONTokener;
import com.metaweb.gridlock.ProjectManager;
import com.metaweb.gridlock.browsing.Engine;
import com.metaweb.gridlock.model.Project;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser;
@ -150,4 +151,18 @@ public abstract class Command {
JSONObject o = (JSONObject) t.nextValue();
return o;
}
protected Engine getEngine(HttpServletRequest request, Project project) throws Exception {
Properties properties = new Properties();
readFileUpload(request, properties);
Engine engine = new Engine(project);
if (properties.containsKey("engine")) {
String json = properties.getProperty("engine");
JSONObject o = jsonStringToObject(json);
engine.initializeFromJSON(o);
}
return engine;
}
}

View File

@ -12,6 +12,9 @@ import javax.servlet.http.HttpServletResponse;
import org.json.JSONException;
import org.json.JSONObject;
import com.metaweb.gridlock.browsing.Engine;
import com.metaweb.gridlock.browsing.FilteredRows;
import com.metaweb.gridlock.browsing.RowVisitor;
import com.metaweb.gridlock.model.Project;
import com.metaweb.gridlock.model.Row;
@ -20,21 +23,41 @@ public class GetRowsCommand extends Command {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Project project = getProject(request);
int start = Math.min(project.rows.size(), Math.max(0, getIntegerParameter(request, "start", 0)));
int limit = Math.min(project.rows.size() - start, Math.max(0, getIntegerParameter(request, "limit", 20)));
Properties options = new Properties();
try {
Project project = getProject(request);
Engine engine = getEngine(request, project);
int start = Math.min(project.rows.size(), Math.max(0, getIntegerParameter(request, "start", 0)));
int limit = Math.min(project.rows.size() - start, Math.max(0, getIntegerParameter(request, "limit", 20)));
Properties options = new Properties();
JSONObject o = new JSONObject();
List<JSONObject> a = new ArrayList<JSONObject>(limit);
try {
for (int r = start; r < start + limit && r < project.rows.size(); r++) {
Row row = project.rows.get(r);
FilteredRows filteredRows = engine.getAllFilteredRows();
RowAccumulator acc = new RowAccumulator(start, limit) {
List<JSONObject> list;
Properties options;
a.add(row.getJSON(options));
}
public RowAccumulator init(List<JSONObject> list, Properties options) {
this.list = list;
this.options = options;
return this;
}
@Override
public void internalVisit(Row row) {
try {
list.add(row.getJSON(options));
} catch (JSONException e) {
}
}
}.init(a, options);
filteredRows.accept(project, acc);
o.put("filtered", acc.total);
} catch (JSONException e) {
respondException(response, e);
}
@ -44,8 +67,31 @@ public class GetRowsCommand extends Command {
o.put("total", project.rows.size());
respondJSON(response, o);
} catch (JSONException e) {
} catch (Exception e) {
respondException(response, e);
}
}
static protected class RowAccumulator implements RowVisitor {
final public int start;
final public int limit;
public int total;
public RowAccumulator(int start, int limit) {
this.start = start;
this.limit = limit;
}
@Override
public void visit(int rowIndex, Row row) {
if (total >= start && total < start + limit) {
internalVisit(row);
}
total++;
}
protected void internalVisit(Row row) {
}
}
}