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 @Override
public void accept(Project project, RowVisitor visitor) { 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; boolean ok = true;
for (RowFilter rowFilter : _rowFilters) { for (RowFilter rowFilter : _rowFilters) {
if (!rowFilter.filterRow(row)) { if (!rowFilter.filterRow(row)) {
@ -26,7 +28,7 @@ public class ConjunctiveFilteredRows implements FilteredRows {
} }
if (ok) { 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.Facet;
import com.metaweb.gridlock.browsing.facets.ListFacet; import com.metaweb.gridlock.browsing.facets.ListFacet;
import com.metaweb.gridlock.model.Project;
public class Engine { 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() { public FilteredRows getAllFilteredRows() {
return getFilteredRows(null); return getFilteredRows(null);
@ -21,7 +27,7 @@ public class Engine {
public FilteredRows getFilteredRows(Facet except) { public FilteredRows getFilteredRows(Facet except) {
ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(); ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows();
for (Facet facet : facets) { for (Facet facet : _facets) {
if (facet != except) { if (facet != except) {
cfr.add(facet.getRowFilter()); cfr.add(facet.getRowFilter());
} }
@ -32,8 +38,8 @@ public class Engine {
public JSONObject getJSON(Properties options) throws JSONException { public JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
List<JSONObject> a = new ArrayList<JSONObject>(facets.size()); List<JSONObject> a = new ArrayList<JSONObject>(_facets.size());
for (Facet facet : facets) { for (Facet facet : _facets) {
a.add(facet.getJSON(options)); a.add(facet.getJSON(options));
} }
o.put("facets", a); o.put("facets", a);
@ -41,7 +47,7 @@ public class Engine {
return o; return o;
} }
public void initializeFromJSON(JSONObject o) throws JSONException { public void initializeFromJSON(JSONObject o) throws Exception {
JSONArray a = o.getJSONArray("facets"); JSONArray a = o.getJSONArray("facets");
int length = a.length(); int length = a.length();
@ -56,16 +62,16 @@ public class Engine {
if (facet != null) { if (facet != null) {
facet.initializeFromJSON(fo); facet.initializeFromJSON(fo);
facets.add(facet); _facets.add(facet);
} }
} }
} }
public void computeFacets() throws JSONException { public void computeFacets() throws JSONException {
for (Facet facet : facets) { for (Facet facet : _facets) {
FilteredRows filteredRows = getFilteredRows(facet); 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; import com.metaweb.gridlock.model.Row;
public interface RowVisitor { 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; package com.metaweb.gridlock.browsing.accessors;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
public class DecoratedValue { public class DecoratedValue {
final public Object value; final public Object value;
final public String label; final public String label;
@ -8,4 +13,13 @@ public class DecoratedValue {
this.value = value; this.value = value;
this.label = label; 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 @Override
public void visit(Row row) { public void visit(int rowIndex, Row row) {
if (_cellIndex < row.cells.size()) { if (_cellIndex < row.cells.size()) {
Cell cell = row.cells.get(_cellIndex); Cell cell = row.cells.get(_cellIndex);
if (cell != null) { if (cell != null) {
@ -35,10 +35,10 @@ public class CellAccessorNominalRowGrouper implements RowVisitor {
new DecoratedValue(value, value.toString()); new DecoratedValue(value, value.toString());
Object v = dValue.value; Object v = dValue.value;
if (choices.containsKey(value)) { if (choices.containsKey(v)) {
choices.get(value).count++; choices.get(v).count++;
} else { } else {
NominalFacetChoice choice = new NominalFacetChoice(dValue, v); NominalFacetChoice choice = new NominalFacetChoice(dValue);
choice.count = 1; choice.count = 1;
choices.put(v, choice); 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.FilteredRows;
import com.metaweb.gridlock.browsing.filters.RowFilter; import com.metaweb.gridlock.browsing.filters.RowFilter;
import com.metaweb.gridlock.model.Project;
public interface Facet { public interface Facet {
public RowFilter getRowFilter(); public RowFilter getRowFilter();
public void computeChoices(FilteredRows filteredRows); public void computeChoices(Project project, FilteredRows filteredRows);
public JSONObject getJSON(Properties options) throws JSONException; 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; package com.metaweb.gridlock.browsing.facets;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
@ -9,37 +10,91 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import com.metaweb.gridlock.browsing.FilteredRows; 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.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 { public class ListFacet implements Facet {
final protected List<Object> _choices = new LinkedList<Object>(); protected List<NominalFacetChoice> _selection = new LinkedList<NominalFacetChoice>();
protected List<NominalFacetChoice> _choices = new LinkedList<NominalFacetChoice>();
@Override protected String _name;
public JSONObject getJSON(Properties options) throws JSONException { protected String _expression;
// TODO Auto-generated method stub protected int _cellIndex;
return null; protected Evaluable _eval;
public ListFacet() {
} }
@Override @Override
public void initializeFromJSON(JSONObject o) throws JSONException { public JSONObject getJSON(Properties options) throws JSONException {
JSONArray a = o.getJSONArray("choices"); 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(); int length = a.length();
for (int i = 0; i < length; i++) { 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 @Override
public RowFilter getRowFilter() { public RowFilter getRowFilter() {
// TODO Auto-generated method stub return new ExpressionEqualRowFilter(_eval, _cellIndex, createMatches());
return null;
} }
@Override @Override
public void computeChoices(FilteredRows filteredRows) { public void computeChoices(Project project, FilteredRows filteredRows) {
// TODO Auto-generated method stub 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; 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; import com.metaweb.gridlock.browsing.accessors.DecoratedValue;
public class NominalFacetChoice { public class NominalFacetChoice {
final public DecoratedValue decoratedValue; final public DecoratedValue decoratedValue;
final public Object value;
public int count; public int count;
public boolean selected;
public NominalFacetChoice(DecoratedValue decoratedValue, Object value) { public NominalFacetChoice(DecoratedValue decoratedValue) {
this.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 org.json.JSONTokener;
import com.metaweb.gridlock.ProjectManager; import com.metaweb.gridlock.ProjectManager;
import com.metaweb.gridlock.browsing.Engine;
import com.metaweb.gridlock.model.Project; import com.metaweb.gridlock.model.Project;
import com.oreilly.servlet.multipart.FilePart; import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser; import com.oreilly.servlet.multipart.MultipartParser;
@ -150,4 +151,18 @@ public abstract class Command {
JSONObject o = (JSONObject) t.nextValue(); JSONObject o = (JSONObject) t.nextValue();
return o; 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.JSONException;
import org.json.JSONObject; 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.Project;
import com.metaweb.gridlock.model.Row; import com.metaweb.gridlock.model.Row;
@ -20,21 +23,41 @@ public class GetRowsCommand extends Command {
public void doGet(HttpServletRequest request, HttpServletResponse response) public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
try {
Project project = getProject(request); Project project = getProject(request);
Engine engine = getEngine(request, project);
int start = Math.min(project.rows.size(), Math.max(0, getIntegerParameter(request, "start", 0))); 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))); int limit = Math.min(project.rows.size() - start, Math.max(0, getIntegerParameter(request, "limit", 20)));
Properties options = new Properties(); Properties options = new Properties();
try {
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
List<JSONObject> a = new ArrayList<JSONObject>(limit); List<JSONObject> a = new ArrayList<JSONObject>(limit);
try { try {
for (int r = start; r < start + limit && r < project.rows.size(); r++) { FilteredRows filteredRows = engine.getAllFilteredRows();
Row row = project.rows.get(r); 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) { } catch (JSONException e) {
respondException(response, e); respondException(response, e);
} }
@ -44,8 +67,31 @@ public class GetRowsCommand extends Command {
o.put("total", project.rows.size()); o.put("total", project.rows.size());
respondJSON(response, o); respondJSON(response, o);
} catch (JSONException e) { } catch (Exception e) {
respondException(response, 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) {
}
}
} }