diff --git a/main/src/com/google/gridworks/protograph/BooleanColumnCondition.java b/main/src/com/google/gridworks/protograph/BooleanColumnCondition.java new file mode 100644 index 000000000..d97508508 --- /dev/null +++ b/main/src/com/google/gridworks/protograph/BooleanColumnCondition.java @@ -0,0 +1,31 @@ +package com.google.gridworks.protograph; + +import com.google.gridworks.model.Column; +import com.google.gridworks.model.Project; +import com.google.gridworks.model.Row; + + +public class BooleanColumnCondition implements Condition { + final public String columnName; + + public BooleanColumnCondition(String columnName) { + this.columnName = columnName; + } + + @Override + public boolean test(Project project, int rowIndex, Row row) { + Column column = project.columnModel.getColumnByName(columnName); + if (column != null) { + Object o = row.getCellValue(column.getCellIndex()); + if (o != null) { + if (o instanceof Boolean) { + return ((Boolean) o).booleanValue(); + } else { + return Boolean.parseBoolean(o.toString()); + } + } + } + return false; + } + +} diff --git a/main/src/com/google/gridworks/protograph/CellKeyNode.java b/main/src/com/google/gridworks/protograph/CellKeyNode.java index 29ced3a29..e6b5ddf05 100644 --- a/main/src/com/google/gridworks/protograph/CellKeyNode.java +++ b/main/src/com/google/gridworks/protograph/CellKeyNode.java @@ -6,14 +6,11 @@ import org.json.JSONException; import org.json.JSONWriter; public class CellKeyNode extends CellNode { - final public FreebaseTopic namespace; + final public FreebaseTopic namespace; public CellKeyNode( - String columnName, - FreebaseTopic namespace + FreebaseTopic namespace ) { - super(columnName); - this.namespace = namespace; } @@ -22,7 +19,14 @@ public class CellKeyNode extends CellNode { writer.object(); writer.key("nodeType"); writer.value("cell-as-key"); - writer.key("columnName"); writer.value(columnName); + + writer.key("columnNames"); + writer.array(); + for (String name : columnNames) { + writer.value(name); + } + writer.endArray(); + writer.key("namespace"); namespace.write(writer, options); writer.endObject(); } diff --git a/main/src/com/google/gridworks/protograph/CellNode.java b/main/src/com/google/gridworks/protograph/CellNode.java index 8a2336d90..7284ada77 100644 --- a/main/src/com/google/gridworks/protograph/CellNode.java +++ b/main/src/com/google/gridworks/protograph/CellNode.java @@ -1,11 +1,8 @@ package com.google.gridworks.protograph; +import java.util.LinkedList; +import java.util.List; + abstract public class CellNode implements Node { - final public String columnName; - - public CellNode( - String columnName - ) { - this.columnName = columnName; - } + final public List columnNames = new LinkedList(); } diff --git a/main/src/com/google/gridworks/protograph/CellTopicNode.java b/main/src/com/google/gridworks/protograph/CellTopicNode.java index 5f743547b..e2b4cdc45 100644 --- a/main/src/com/google/gridworks/protograph/CellTopicNode.java +++ b/main/src/com/google/gridworks/protograph/CellTopicNode.java @@ -12,11 +12,8 @@ public class CellTopicNode extends CellNode implements NodeWithLinks { final public List links = new LinkedList(); public CellTopicNode( - String columnName, - FreebaseType type + FreebaseType type ) { - super(columnName); - this.type = type; } @@ -25,7 +22,12 @@ public class CellTopicNode extends CellNode implements NodeWithLinks { writer.object(); writer.key("nodeType"); writer.value("cell-as-topic"); - writer.key("columnName"); writer.value(columnName); + writer.key("columnNames"); + writer.array(); + for (String name : columnNames) { + writer.value(name); + } + writer.endArray(); if (type != null) { writer.key("type"); type.write(writer, options); } diff --git a/main/src/com/google/gridworks/protograph/CellValueNode.java b/main/src/com/google/gridworks/protograph/CellValueNode.java index 26a04357e..c57fade38 100644 --- a/main/src/com/google/gridworks/protograph/CellValueNode.java +++ b/main/src/com/google/gridworks/protograph/CellValueNode.java @@ -10,12 +10,9 @@ public class CellValueNode extends CellNode { final public String lang; public CellValueNode( - String columnName, - String valueType, + String valueType, String lang ) { - super(columnName); - this.valueType = valueType; this.lang = lang; } @@ -25,7 +22,12 @@ public class CellValueNode extends CellNode { writer.object(); writer.key("nodeType"); writer.value("cell-as-value"); - writer.key("columnName"); writer.value(columnName); + writer.key("columnNames"); + writer.array(); + for (String name : columnNames) { + writer.value(name); + } + writer.endArray(); writer.key("valueType"); writer.value(valueType); writer.key("lang"); writer.value(lang); writer.endObject(); diff --git a/main/src/com/google/gridworks/protograph/Condition.java b/main/src/com/google/gridworks/protograph/Condition.java new file mode 100644 index 000000000..b406a10f6 --- /dev/null +++ b/main/src/com/google/gridworks/protograph/Condition.java @@ -0,0 +1,8 @@ +package com.google.gridworks.protograph; + +import com.google.gridworks.model.Project; +import com.google.gridworks.model.Row; + +public interface Condition { + public boolean test(Project project, int rowIndex, Row row); +} diff --git a/main/src/com/google/gridworks/protograph/Link.java b/main/src/com/google/gridworks/protograph/Link.java index df6bf70e4..19843c08e 100644 --- a/main/src/com/google/gridworks/protograph/Link.java +++ b/main/src/com/google/gridworks/protograph/Link.java @@ -10,11 +10,13 @@ import com.google.gridworks.Jsonizable; public class Link implements Jsonizable { final public FreebaseProperty property; final public Node target; + final public Condition condition; final public boolean load; - public Link(FreebaseProperty property, Node target, boolean load) { + public Link(FreebaseProperty property, Node target, Condition condition, boolean load) { this.property = property; this.target = target; + this.condition = condition; this.load = load; } diff --git a/main/src/com/google/gridworks/protograph/Protograph.java b/main/src/com/google/gridworks/protograph/Protograph.java index 0347d03e2..527d71a7e 100644 --- a/main/src/com/google/gridworks/protograph/Protograph.java +++ b/main/src/com/google/gridworks/protograph/Protograph.java @@ -45,27 +45,34 @@ public class Protograph implements OverlayModel { String nodeType = o.getString("nodeType"); if (nodeType.startsWith("cell-as-")) { - String columnName = o.getString("columnName"); - if ("cell-as-topic".equals(nodeType)) { if (o.has("type")) { node = new CellTopicNode( - columnName, reconstructType(o.getJSONObject("type")) ); } } else if ("cell-as-value".equals(nodeType)) { node = new CellValueNode( - columnName, o.getString("valueType"), o.getString("lang") ); } else if ("cell-as-key".equals(nodeType)) { node = new CellKeyNode( - columnName, reconstructTopic(o.getJSONObject("namespace")) ); } + + if (o.has("columnName") && !o.isNull("columnName")) { + ((CellNode) node).columnNames.add(o.getString("columnName")); + } + if (o.has("columnNames") && !o.isNull("columnNames")) { + JSONArray columnNames = o.getJSONArray("columnNames"); + int count = columnNames.length(); + + for (int c = 0; c < count; c++) { + ((CellNode) node).columnNames.add(columnNames.getString(c)); + } + } } else if ("topic".equals(nodeType)) { node = new FreebaseTopicNode(reconstructTopic(o.getJSONObject("topic"))); } else if ("value".equals(nodeType)) { @@ -86,11 +93,20 @@ public class Protograph implements OverlayModel { for (int j = 0; j < linkCount; j++) { JSONObject oLink = links.getJSONObject(j); + Condition condition = null; + + if (oLink.has("condition") && !oLink.isNull("condition")) { + JSONObject oCondition = oLink.getJSONObject("condition"); + if (oCondition.has("columnName") && !oCondition.isNull("columnName")) { + condition = new BooleanColumnCondition(oCondition.getString("columnName")); + } + } node2.addLink(new Link( reconstructProperty(oLink.getJSONObject("property")), oLink.has("target") && !oLink.isNull("target") ? reconstructNode(oLink.getJSONObject("target")) : null, + condition, oLink.has("load") && !oLink.isNull("load") ? oLink.getBoolean("load") : true )); diff --git a/main/src/com/google/gridworks/protograph/transpose/MqlwriteLikeTransposedNodeFactory.java b/main/src/com/google/gridworks/protograph/transpose/MqlwriteLikeTransposedNodeFactory.java index e9807c01d..3aa3c5fad 100644 --- a/main/src/com/google/gridworks/protograph/transpose/MqlwriteLikeTransposedNodeFactory.java +++ b/main/src/com/google/gridworks/protograph/transpose/MqlwriteLikeTransposedNodeFactory.java @@ -261,7 +261,9 @@ public class MqlwriteLikeTransposedNodeFactory implements TransposedNodeFactory TransposedNode parentNode, Link link, CellNode node, - int rowIndex, Cell cell) { + int rowIndex, + int cellIndex, + Cell cell) { JsonTransposedNode tnode = null; if (node instanceof CellTopicNode) { diff --git a/main/src/com/google/gridworks/protograph/transpose/TransposedNodeFactory.java b/main/src/com/google/gridworks/protograph/transpose/TransposedNodeFactory.java index 2470f3761..2f9b37d80 100644 --- a/main/src/com/google/gridworks/protograph/transpose/TransposedNodeFactory.java +++ b/main/src/com/google/gridworks/protograph/transpose/TransposedNodeFactory.java @@ -21,6 +21,7 @@ public interface TransposedNodeFactory { Link link, CellNode node, int rowIndex, + int cellIndex, Cell cell ); diff --git a/main/src/com/google/gridworks/protograph/transpose/Transposer.java b/main/src/com/google/gridworks/protograph/transpose/Transposer.java index 642e83621..12d38b058 100644 --- a/main/src/com/google/gridworks/protograph/transpose/Transposer.java +++ b/main/src/com/google/gridworks/protograph/transpose/Transposer.java @@ -97,84 +97,88 @@ public class Transposer { Node node, Context context ) { - TransposedNode tnode = null; + List tnodes = new LinkedList(); TransposedNode parentNode = context.parent == null ? null : context.parent.transposedNode; Link link = context.parent == null ? null : context.link; if (node instanceof CellNode) { CellNode node2 = (CellNode) node; - Column column = project.columnModel.getColumnByName(node2.columnName); - if (column != null) { - Cell cell = row.getCell(column.getCellIndex()); - if (cell != null && ExpressionUtils.isNonBlankData(cell.value)) { - if (node2 instanceof CellTopicNode && - (cell.recon == null || cell.recon.judgment == Judgment.None)) { + for (String columnName : node2.columnNames) { + Column column = project.columnModel.getColumnByName(columnName); + if (column != null) { + int cellIndex = column.getCellIndex(); + + Cell cell = row.getCell(cellIndex); + if (cell != null && ExpressionUtils.isNonBlankData(cell.value)) { + if (node2 instanceof CellTopicNode && + (cell.recon == null || cell.recon.judgment == Judgment.None)) { + return; + } + + context.count++; + if (context.limit > 0 && context.count > context.limit) { return; + } + + tnodes.add(nodeFactory.transposeCellNode( + parentNode, + link, + node2, + rowIndex, + cellIndex, + cell + )); } - - context.count++; - if (context.limit > 0 && context.count > context.limit) { - return; - } - - tnode = nodeFactory.transposeCellNode( - parentNode, - link, - node2, - rowIndex, - cell - ); } } } else { if (node instanceof AnonymousNode) { - tnode = nodeFactory.transposeAnonymousNode( + tnodes.add(nodeFactory.transposeAnonymousNode( parentNode, link, (AnonymousNode) node, rowIndex - ); + )); } else if (node instanceof FreebaseTopicNode) { - tnode = nodeFactory.transposeTopicNode( + tnodes.add(nodeFactory.transposeTopicNode( parentNode, link, (FreebaseTopicNode) node, rowIndex - ); + )); } else if (node instanceof ValueNode) { - tnode = nodeFactory.transposeValueNode( + tnodes.add(nodeFactory.transposeValueNode( parentNode, link, (ValueNode) node, rowIndex - ); + )); } } - if (tnode != null) { - context.transposedNode = tnode; - context.nullifySubContextNodes(); - } /* - else, previous rows might have set the context transposed node already, - and we simply inherit that transposed node. - */ - - if (node instanceof NodeWithLinks && context.transposedNode != null) { + if (node instanceof NodeWithLinks) { NodeWithLinks node2 = (NodeWithLinks) node; - int linkCount = node2.getLinkCount(); for (int i = 0; i < linkCount; i++) { - descend( - project, - protograph, - nodeFactory, - rowIndex, - row, - node2.getLink(i).getTarget(), - context.subContexts.get(i) - ); + Link link2 = node2.getLink(i); + if (link2.condition == null || link2.condition.test(project, rowIndex, row)) { + for (TransposedNode tnode : tnodes) { + context.transposedNode = tnode; + context.nullifySubContextNodes(); + + descend( + project, + protograph, + nodeFactory, + rowIndex, + row, + link2.getTarget(), + context.subContexts.get(i) + ); + } + } } } } diff --git a/main/src/com/google/gridworks/protograph/transpose/TripleLoaderTransposedNodeFactory.java b/main/src/com/google/gridworks/protograph/transpose/TripleLoaderTransposedNodeFactory.java index 61722e78b..86441b5ae 100644 --- a/main/src/com/google/gridworks/protograph/transpose/TripleLoaderTransposedNodeFactory.java +++ b/main/src/com/google/gridworks/protograph/transpose/TripleLoaderTransposedNodeFactory.java @@ -352,17 +352,19 @@ public class TripleLoaderTransposedNodeFactory implements TransposedNodeFactory if (newTopicVars.containsKey(cell.recon.id)) { id = newTopicVars.get(cell.recon.id); } else { - long var = 0; - if (varPool.containsKey(node.columnName)) { - var = varPool.get(node.columnName); - } - varPool.put(node.columnName, var + 1); + Column column = project.columnModel.getColumnByCellIndex(cellIndex); + String columnName = column.getName(); - id = "$" + node.columnName.replaceAll("\\W+", "_") + "_" + var; + long var = 0; + if (varPool.containsKey(columnName)) { + var = varPool.get(columnName); + } + varPool.put(columnName, var + 1); + + id = "$" + columnName.replaceAll("\\W+", "_") + "_" + var; String typeID = node.type.id; - Column column = project.columnModel.getColumnByName(node.columnName); ReconConfig reconConfig = column.getReconConfig(); if (reconConfig instanceof StandardReconConfig) { typeID = ((StandardReconConfig) reconConfig).typeID; @@ -510,14 +512,12 @@ public class TripleLoaderTransposedNodeFactory implements TransposedNodeFactory TransposedNode parentNode, Link link, CellNode node, - int rowIndex, + int rowIndex, + int cellIndex, Cell cell) { WritingTransposedNode parentNode2 = (WritingTransposedNode) parentNode; - Column column = project.columnModel.getColumnByName(node.columnName); - int cellIndex = column != null ? column.getCellIndex() : -1; - WritingTransposedNode tnode = null; if (node instanceof CellTopicNode) { if (cell.recon != null && diff --git a/main/webapp/modules/core/scripts/protograph/schema-alignment-ui-node.js b/main/webapp/modules/core/scripts/protograph/schema-alignment-ui-node.js index 91334c5fe..23c832f18 100644 --- a/main/webapp/modules/core/scripts/protograph/schema-alignment-ui-node.js +++ b/main/webapp/modules/core/scripts/protograph/schema-alignment-ui-node.js @@ -3,6 +3,11 @@ SchemaAlignmentDialog.UINode = function(dialog, node, table, options) { this._node = node; this._options = options; + if ("columnName" in this._node) { + this._node.columnNames = [ this._node.columnName ]; + delete this._node.columnName; + } + this._linkUIs = []; this._detailsRendered = false; @@ -74,13 +79,19 @@ SchemaAlignmentDialog.UINode.prototype._renderMain = function() { this._node.nodeType == "cell-as-value" || this._node.nodeType == "cell-as-key") { - if ("columnName" in this._node) { - a.html(" cell"); + if ("columnNames" in this._node) { + for (var c = 0; c < this._node.columnNames.length; c++) { + if (c > 0) { + $('').text(", ").appendTo(a); + } + + $('') + .text(this._node.columnNames[c]) + .addClass("schema-alignment-node-column") + .appendTo(a); + } - $('') - .text(this._node.columnName) - .addClass("schema-alignment-node-column") - .prependTo(a); + $('').text(this._node.columnNames.length > 1 ? " cells" : " cell").appendTo(a); } else { a.html(this._options.mustBeCellTopic ? "Which column?" : "Configure..."); } @@ -229,7 +240,7 @@ SchemaAlignmentDialog.UINode.prototype._showColumnPopupMenu = function(elmt) { label: columns[index].name, click: function() { self._node.nodeType = "cell-as-topic"; - self._node.columnName = columns[index].name; + self._node.columnNames = [ columns[index].name ]; self._showExpandable(); self._renderMain(); } @@ -398,11 +409,18 @@ SchemaAlignmentDialog.UINode.prototype._showNodeConfigDialog = function() { .attr("cellpadding", "0") .appendTo(elmts.divColumns)[0]; + var columnMap = {}; + if ("columnNames" in self._node) { + for (var i = 0; i < self._node.columnNames.length; i++) { + columnMap[self._node.columnNames[i]] = true; + } + } + var makeColumnChoice = function(column, columnIndex) { var tr = tableColumns.insertRow(tableColumns.rows.length); var radio = $('') - .attr("type", "radio") + .attr("type", "checkbox") .attr("value", column.name) .attr("name", "schema-align-node-dialog-column") .appendTo(tr.insertCell(0)) @@ -420,9 +438,7 @@ SchemaAlignmentDialog.UINode.prototype._showNodeConfigDialog = function() { } }); - if ((!("columnName" in self._node) || !self._node.columnName) && columnIndex === 0) { - radio.attr("checked", "true"); - } else if (column.name == self._node.columnName) { + if (column.name in columnMap) { radio.attr("checked", "true"); } @@ -539,7 +555,14 @@ SchemaAlignmentDialog.UINode.prototype._showNodeConfigDialog = function() { }; if (node.nodeType == "cell-as") { node.nodeType = $("input[name='schema-align-node-dialog-node-subtype']:checked")[0].value; - node.columnName = $("input[name='schema-align-node-dialog-column']:checked")[0].value; + node.columnNames = $("input[name='schema-align-node-dialog-column']:checked").map(function() { + return this.getAttribute("value"); + }).get(); + + if (node.columnNames.length == 0) { + alert("You must select at least one column."); + return null; + } if (node.nodeType == "cell-as-topic") { node.createForNoReconMatch = elmts.radioNodeTypeCellAsTopicCreate[0].checked; @@ -631,14 +654,14 @@ SchemaAlignmentDialog.UINode.prototype.getJSON = function() { var getLinks = false; if (this._node.nodeType.match(/^cell-as-/)) { - if (!("columnName" in this._node) || !this._node.columnName) { + if (!("columnNames" in this._node) || !this._node.columnNames) { return null; } if (this._node.nodeType == "cell-as-topic") { result = { nodeType: this._node.nodeType, - columnName: this._node.columnName, + columnNames: this._node.columnNames, type: "type" in this._node ? cloneDeep(this._node.type) : { "id" : "/common/topic", "name" : "Topic", "cvt" : false }, createForNoReconMatch: "createForNoReconMatch" in this._node ? this._node.createForNoReconMatch : true }; @@ -646,7 +669,7 @@ SchemaAlignmentDialog.UINode.prototype.getJSON = function() { } else if (this._node.nodeType == "cell-as-value") { result = { nodeType: this._node.nodeType, - columnName: this._node.columnName, + columnNames: this._node.columnNames, valueType: "valueType" in this._node ? this._node.valueType : "/type/text", lang: "lang" in this._node ? this._node.lang : "/lang/en" }; @@ -656,7 +679,7 @@ SchemaAlignmentDialog.UINode.prototype.getJSON = function() { } result = { nodeType: this._node.nodeType, - columnName: this._node.columnName, + columnNames: this._node.columnNames, type: cloneDeep(this._node.namespace) }; }