From e0365f45c8a38c115ed20c0adc186fce0e1d2371 Mon Sep 17 00:00:00 2001 From: David Huynh Date: Thu, 28 Jan 2010 01:43:09 +0000 Subject: [PATCH] More server-side scaffolding for faceted browsing. git-svn-id: http://google-refine.googlecode.com/svn/trunk@11 7d457c2a-affb-35e4-300a-418c747d4874 --- .../browsing/ConjunctiveFilteredRows.java | 6 +- .../com/metaweb/gridlock/browsing/Engine.java | 22 +++-- .../metaweb/gridlock/browsing/RowVisitor.java | 2 +- .../browsing/accessors/DecoratedValue.java | 14 ++++ .../facets/CellAccessorNominalRowGrouper.java | 8 +- .../facets/ExpressionNominalRowGrouper.java | 50 ++++++++++++ .../gridlock/browsing/facets/Facet.java | 5 +- .../gridlock/browsing/facets/ListFacet.java | 81 ++++++++++++++++--- .../browsing/facets/NominalFacetChoice.java | 20 ++++- .../filters/ExpressionEqualRowFilter.java | 42 ++++++++++ .../metaweb/gridlock/commands/Command.java | 15 ++++ .../gridlock/commands/GetRowsCommand.java | 68 +++++++++++++--- 12 files changed, 289 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java create mode 100644 src/main/java/com/metaweb/gridlock/browsing/filters/ExpressionEqualRowFilter.java diff --git a/src/main/java/com/metaweb/gridlock/browsing/ConjunctiveFilteredRows.java b/src/main/java/com/metaweb/gridlock/browsing/ConjunctiveFilteredRows.java index b2ebdb77d..1baef4a74 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/ConjunctiveFilteredRows.java +++ b/src/main/java/com/metaweb/gridlock/browsing/ConjunctiveFilteredRows.java @@ -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); } } } diff --git a/src/main/java/com/metaweb/gridlock/browsing/Engine.java b/src/main/java/com/metaweb/gridlock/browsing/Engine.java index 6a2604218..4652cf2e3 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/Engine.java +++ b/src/main/java/com/metaweb/gridlock/browsing/Engine.java @@ -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 facets = new LinkedList(); + protected Project _project; + protected List _facets = new LinkedList(); + + 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 a = new ArrayList(facets.size()); - for (Facet facet : facets) { + List a = new ArrayList(_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); } } } diff --git a/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java b/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java index e69b881ac..d7c2bb1b5 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java +++ b/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java @@ -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); } diff --git a/src/main/java/com/metaweb/gridlock/browsing/accessors/DecoratedValue.java b/src/main/java/com/metaweb/gridlock/browsing/accessors/DecoratedValue.java index d39f50bc6..bfbb52738 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/accessors/DecoratedValue.java +++ b/src/main/java/com/metaweb/gridlock/browsing/accessors/DecoratedValue.java @@ -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; + } } diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java b/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java index 5e0d3bc76..bf7c68294 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java @@ -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); diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java b/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java new file mode 100644 index 000000000..7b26a2756 --- /dev/null +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java @@ -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 choices = new HashMap(); + + 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); + } + } + } + } + } +} diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/Facet.java b/src/main/java/com/metaweb/gridlock/browsing/facets/Facet.java index d786dc1c9..23a58ecad 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/Facet.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/Facet.java @@ -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; } diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java b/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java index ff16cf258..a31ea6b42 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java @@ -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 _choices = new LinkedList(); - - @Override - public JSONObject getJSON(Properties options) throws JSONException { - // TODO Auto-generated method stub - return null; + protected List _selection = new LinkedList(); + protected List _choices = new LinkedList(); + + 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 a = new ArrayList(_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; } - } diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/NominalFacetChoice.java b/src/main/java/com/metaweb/gridlock/browsing/facets/NominalFacetChoice.java index 4d804316d..5d56f6420 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/NominalFacetChoice.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/NominalFacetChoice.java @@ -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; } } diff --git a/src/main/java/com/metaweb/gridlock/browsing/filters/ExpressionEqualRowFilter.java b/src/main/java/com/metaweb/gridlock/browsing/filters/ExpressionEqualRowFilter.java new file mode 100644 index 000000000..2fddb248d --- /dev/null +++ b/src/main/java/com/metaweb/gridlock/browsing/filters/ExpressionEqualRowFilter.java @@ -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; + } +} diff --git a/src/main/java/com/metaweb/gridlock/commands/Command.java b/src/main/java/com/metaweb/gridlock/commands/Command.java index d79e66ceb..50223246b 100644 --- a/src/main/java/com/metaweb/gridlock/commands/Command.java +++ b/src/main/java/com/metaweb/gridlock/commands/Command.java @@ -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; + } } diff --git a/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java b/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java index a72011c56..de0df5345 100644 --- a/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java +++ b/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java @@ -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 a = new ArrayList(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 list; + Properties options; - a.add(row.getJSON(options)); - } + public RowAccumulator init(List 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) { + } + } }