package edu.isi.karma.imp.json;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.IteratorUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.isi.karma.controller.command.selection.SuperSelectionManager;
import edu.isi.karma.rep.ColumnMetadata.DataStructure;
import edu.isi.karma.rep.HNode;
import edu.isi.karma.rep.HNode.HNodeType;
import edu.isi.karma.rep.HNodePath;
import edu.isi.karma.rep.HTable;
import edu.isi.karma.rep.Node;
import edu.isi.karma.rep.RepFactory;
import edu.isi.karma.rep.Row;
import edu.isi.karma.rep.Table;
import edu.isi.karma.rep.Worksheet;
public class JsonImportValues {
private static Logger logger = LoggerFactory.getLogger(JsonImportValues.class);
private int maxNumLines;
private int numObjects;
private RepFactory factory;
private Worksheet worksheet;
private JSONArray columnsJson;
private Map<String, Boolean> columnsCache = new HashMap<>();
public JsonImportValues(int maxNumLines, int numObjects, RepFactory factory,
Worksheet worksheet, JSONArray columnsJson) {
this.maxNumLines = maxNumLines;
this.numObjects = numObjects;
this.factory = factory;
this.worksheet = worksheet;
this.columnsJson = columnsJson;
}
public void addObjectElement(String key, Object value, HTable headers,
Row row) throws JSONException {
HNode hNode = addHNode(headers, key, DataStructure.OBJECT, factory, worksheet);
String hNodeId = hNode.getId();
if (value instanceof String) {
if (((String) value).isEmpty() && hNode.hasNestedTable()) {
addEmptyRow(row.getNode(hNodeId).getNestedTable(), hNode);
}
row.setValue(hNodeId, (String) value, factory);
} else if (value instanceof Integer) {
row.setValue(hNodeId, value.toString(), factory);
} else if (value instanceof Double) {
row.setValue(hNodeId, value.toString(), factory);
} else if (value instanceof Long) {
row.setValue(hNodeId, value.toString(), factory);
} else if (value instanceof Boolean) {
row.setValue(hNodeId, value.toString(), factory);
} else if (value instanceof JSONObject) {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
HTable nestedHTable = addNestedHTable(hNode, key, row);
Table nestedTable = row.getNode(hNodeId).getNestedTable();
addKeysAndValues((JSONObject) value, nestedHTable, nestedTable);
}
} else if (value instanceof JSONArray) {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
HTable nestedHTable = addNestedHTable(hNode, key, row);
Table nestedTable = row.getNode(hNodeId).getNestedTable();
JSONArray a = (JSONArray) value;
for (int i = 0; i < a.length(); i++) {
addListElement(a.get(i), nestedHTable, nestedTable);
}
}
} else if (value == JSONObject.NULL) {
// Ignore
} else {
throw new Error("Cannot handle " + key + ": " + value + " yet.");
}
}
public void addObjectElement(String key, JSONTokener token, HTable headers,
Row row) throws JSONException {
HNode hNode = addHNode(headers, key, DataStructure.OBJECT, factory, worksheet);
String hNodeId = hNode == null ? null : hNode.getId();
char c = token.nextClean();
if (maxNumLines > 0 && numObjects >= maxNumLines)
return;
if (c != '{' && c != '[') {
token.back();
Object tokenObj = token.nextValue();
String value;
if(tokenObj == null || tokenObj == JSONObject.NULL)
value = "";
else
value = tokenObj.toString();
if (value.isEmpty() && hNode != null && hNode.hasNestedTable()) {
addEmptyRow(row.getNode(hNodeId).getNestedTable(), hNode);
}
if (hNodeId != null)
row.setValue(hNodeId, value, factory);
}
else if (c == '{') {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
if (hNode != null) {
HTable nestedHTable = addNestedHTable(hNode, key, row);
Table nestedTable = row.getNode(hNodeId).getNestedTable();
addKeysAndValues(token, nestedHTable, nestedTable);
}
else
addKeysAndValues(token, null, null);
}
} else if (c == '[') {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
if (hNode != null) {
HTable nestedHTable = addNestedHTable(hNode, key, row);
Table nestedTable = row.getNode(hNodeId).getNestedTable();
addListElement(token, nestedHTable, nestedTable);
}
else
addListElement(token, null, null);
}
} else {
throw new Error("Cannot handle " + key + " yet.");
}
}
public void addEmptyRow(Table nestedTable, HNode hNode) {
HTable headersNestedTable = hNode.getNestedTable();
Row emptyRow = nestedTable.addRow(factory);
numObjects++;
if (maxNumLines > 0 && numObjects >= maxNumLines)
return;
for (HNode nestedHNode : headersNestedTable.getHNodes()) {
if (nestedHNode.hasNestedTable()) {
addEmptyRow(emptyRow.getNode(nestedHNode.getId())
.getNestedTable(), nestedHNode);
} else {
emptyRow.setValue(nestedHNode.getId(), "", factory);
}
}
}
public void addKeysAndValues(JSONObject object, HTable nestedHTable,
Table nestedTable) throws JSONException {
if (maxNumLines > 0 && numObjects >= maxNumLines)
return;
Row nestedRow = nestedTable.addRow(factory);
numObjects++;
// if(maxNumLines > 0 && numObjects >= maxNumLines)
// return;
Iterator<String> it = getSortedKeysIterator(object);
while (it.hasNext()) {
String nestedKey = it.next();
addObjectElement(nestedKey, object.get(nestedKey), nestedHTable,
nestedRow);
}
}
public void addKeysAndValues(JSONTokener token, HTable nestedHTable,
Table nestedTable) throws JSONException {
if (maxNumLines > 0 && numObjects >= maxNumLines)
return;
Row nestedRow = null;
if (nestedTable != null) {
nestedRow = nestedTable.addRow(factory);
numObjects++;
}
// if(maxNumLines > 0 && numObjects >= maxNumLines)
// return;
char c = token.nextClean();
while (c != '}') {
if (maxNumLines > 0 && numObjects >= maxNumLines)
break;
token.back();
Object key = token.nextValue();
char t = token.nextClean();
if (t != ':')
throw new JSONException("Parse JSON object error");
addObjectElement((String)key, token, nestedHTable,
nestedRow);
if (maxNumLines > 0 && numObjects >= maxNumLines)
break;
c = token.nextClean();
if (c != ',' && c != '}')
throw new JSONException("Parse JSON object error");
if (c == ',') {
c = token.nextClean();
}
}
}
@SuppressWarnings("unchecked")
public Iterator<String> getSortedKeysIterator(JSONObject object) {
List<String> keys = IteratorUtils.toList(object.keys());
Collections.sort(keys);
Iterator<String> it = keys.iterator();
return it;
}
public void addListElement(Object listValue, HTable headers,
Table dataTable) throws JSONException {
if(JSONObject.NULL.equals(listValue)) {
listValue = "";
}
if (listValue instanceof JSONObject) {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
Row row = dataTable.addRow(factory);
numObjects++;
JSONObject o = (JSONObject) listValue;
Iterator<String> it = getSortedKeysIterator(o);
while (it.hasNext()) {
String key = it.next();
addObjectElement(key, o.get(key), headers, row);
}
}
} else if (isPrimitiveValue(listValue)) {
HNode hNode = addHNode(headers, HTable.VALUES_COLUMN, DataStructure.PRIMITIVE, factory, worksheet);
String hNodeId = hNode.getId();
Row row = dataTable.addRow(factory);
numObjects++;
// TODO, conserve the types of the primitive types.
String value = "";
if (listValue instanceof String || listValue instanceof Boolean) {
value = listValue.toString();
} else if (listValue instanceof Double) {
value = Double.toString((Double) listValue);
} else if (listValue instanceof Integer) {
value = Integer.toString((Integer) listValue);
} else if (listValue instanceof Long) {
value = Long.toString((Long) listValue);
} else {
// Pedro 2012/09/14
logger.error("Unexpected value in JSON array:"
+ listValue.toString());
}
row.setValue(hNodeId, value, factory);
} else if (listValue instanceof JSONArray) {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
HNode hNode = addHNode(headers, "nested array", DataStructure.COLLECTION, factory, worksheet);
String hNodeId = hNode.getId();
Row row = dataTable.addRow(factory);
numObjects++;
if (maxNumLines > 0 && numObjects >= maxNumLines)
return;
HTable nestedHTable = addNestedHTable(hNode,
"nested array values", row);
Table nestedTable = row.getNode(hNodeId).getNestedTable();
JSONArray a = (JSONArray) listValue;
for (int i = 0; i < a.length(); i++) {
addListElement(a.get(i), nestedHTable, nestedTable);
}
}
} else {
logger.error("Cannot handle whatever case is not covered by the if statements. Sorry");
logger.error(listValue.toString());
}
}
public void addListElement(JSONTokener token, HTable headers,
Table dataTable) throws JSONException {
char c = token.nextClean();
while (c != ']') {
if (maxNumLines > 0 && numObjects >= maxNumLines)
break;
if (c != '{' && c != '[') {
token.back();
HNode hNode = addHNode(headers, HTable.VALUES_COLUMN, DataStructure.PRIMITIVE, factory, worksheet);
String hNodeId = hNode == null ? null : hNode.getId();
String value = token.nextValue().toString();
if (hNodeId != null) {
Row row = dataTable.addRow(factory);
numObjects++;
row.setValue(hNodeId, value, factory);
}
}
else if (c == '{') {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
if (headers != null && dataTable != null)
numObjects++;
addKeysAndValues(token, headers, dataTable);
}
}
else if (c == '[') {
if (maxNumLines <= 0 || numObjects < maxNumLines) {
HNode hNode = addHNode(headers, "nested array", DataStructure.COLLECTION, factory, worksheet);
String hNodeId = hNode == null ? null : hNode.getId();
if (hNodeId != null) {
Row row = dataTable.addRow(factory);
numObjects++;
if (maxNumLines > 0 && numObjects >= maxNumLines)
return;
HTable nestedHTable = addNestedHTable(hNode,
"nested array values", row);
Table nestedTable = row.getNode(hNodeId).getNestedTable();
addListElement(token, nestedHTable, nestedTable);
}
else
addListElement(token, null, null);
}
}
else if (c != ','){
logger.error("Cannot handle whatever case is not covered by the if statements. Sorry.");
}
if (maxNumLines > 0 && numObjects >= maxNumLines)
break;
c = token.nextClean();
if (c != ',' && c != ']')
throw new JSONException("Parse JSON array error");
if (c == ',') {
c = token.nextClean();
}
}
}
public boolean isPrimitiveValue(Object value) {
return value instanceof String || value instanceof Boolean
|| value instanceof Integer || value instanceof Double
|| value instanceof Long;
}
public HTable addNestedHTable(HNode hNode, String key, Row row) {
HTable ht = hNode.getNestedTable();
if (ht == null) {
ht = hNode.addNestedTable(createNestedTableName(key),
worksheet, factory);
// Check for all the nodes that have value and nested tables
Collection<Node> nodes = new ArrayList<>();
worksheet.getDataTable().collectNodes(
hNode.getHNodePath(factory), nodes, SuperSelectionManager.DEFAULT_SELECTION);
for (Node node : nodes) {
if (node.getBelongsToRow() == row)
break;
// Add an empty row for each nested table that does not have any
// row
if (node.getNestedTable().getNumRows() == 0) {
addEmptyRow(node.getNestedTable(), hNode);
}
}
}
return ht;
}
public HNode addHNode(HTable headers, String key, DataStructure dataStructure, RepFactory factory, Worksheet worksheet) {
if (headers == null)
return null;
HNode hn = headers.getHNodeFromColumnName(key);
if (hn == null && isVisible(headers, key, factory)) {
hn = headers.addHNode(key, HNodeType.Regular, worksheet, factory);
Worksheet ws = worksheet;
ws.getMetadataContainer().getColumnMetadata().addColumnDataStructure(hn.getId(), dataStructure);
}
return hn;
}
public String createNestedTableName(String key) {
return "Table for " + key;
}
private boolean isVisible(HTable headers, String key, RepFactory factory) {
if (columnsJson == null)
return true;
HNode hn = headers.getParentHNode();
if (hn != null) {
HNodePath hPath = hn.getHNodePath(factory);
String path = hPath.toColumnNamePath() + "/" + key;
Boolean b = columnsCache.get(path);
if (b != null)
return b;
HNode first = null;
JSONArray t = columnsJson;
JSONObject tree = null;
while (first != hn) {
first = hPath.getFirst();
tree = getCorrespondingObject(t, first.getColumnName());
if (tree == null || !tree.has("children")) {
columnsCache.put(path, true);
return true;
}
t = tree.getJSONArray("children");
hPath = hPath.getRest();
}
if (tree == null || !tree.has("children")) {
columnsCache.put(path, true);
return true;
}
JSONObject obj = getCorrespondingObject(tree.getJSONArray("children"), key);
if (obj == null || !obj.has(key)) {
columnsCache.put(path, true);
return true;
}
b = obj.getBoolean(key);
columnsCache.put(path, b);
return b;
}
else {
Boolean b = columnsCache.get(key);
if (b != null)
return b;
JSONObject obj = getCorrespondingObject(columnsJson, key);
if (obj == null || !obj.has(key)) {
columnsCache.put(key, true);
return true;
}
b = obj.getBoolean(key);
columnsCache.put(key, b);
return b;
}
}
private JSONObject getCorrespondingObject(JSONArray array, String colName) {
for (int i = 0; i < array.length(); i++) {
JSONObject obj = array.getJSONObject(i);
if (obj.has(colName))
return obj;
}
return null;
}
public int getNumberOfObjectsImported() {
return numObjects;
}
}