package com.bagri.core.server.api.df.json;
import static javax.xml.xquery.XQItemType.*;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
import com.bagri.core.api.BagriException;
import com.bagri.core.model.Data;
import com.bagri.core.model.Element;
import com.bagri.core.model.NodeKind;
import com.bagri.core.model.Path;
import com.bagri.core.server.api.ContentBuilder;
import com.bagri.core.server.api.ModelManagement;
import com.bagri.core.server.api.impl.ContentBuilderBase;
/**
* Content Builder implementation for JSON format. Uses reference implementation (Glassfish) of json generator.
*
*
* @author Denis Sukhoroslov
*
*/
public class JsonpBuilder extends ContentBuilderBase<String> implements ContentBuilder<String> {
private JsonGeneratorFactory factory = Json.createGeneratorFactory(null);
/**
*
* @param model the XDM model management component
*/
JsonpBuilder(ModelManagement model) {
super(model);
}
/**
* {@inheritDoc}
*/
public void init(Properties properties) {
logger.trace("init; got properties: {}", properties);
if (properties != null) {
Map<String, Object> params = new HashMap<>(properties.size());
for (Map.Entry e: properties.entrySet()) {
params.put(e.getKey().toString(), e.getValue());
}
factory = Json.createGeneratorFactory(params);
}
}
@Override
public String buildContent(Collection<Data> elements) throws BagriException {
Writer writer = new StringWriter();
JsonGenerator stream = factory.createGenerator(writer);
// TODO: think on how to figure out - do we need normalize or not?
// can do it in case of exception, actually...
//elements = normalizeXmlData(elements);
Deque<Data> dataStack = new LinkedList<>();
for (Data data: elements) {
writeElement(dataStack, stream, data);
}
while (!dataStack.isEmpty()) {
stream.writeEnd();
dataStack.pop();
}
stream.flush();
String result = writer.toString();
try {
writer.close();
} catch (IOException ex) {
// just skip it..
logger.info("buildString; exception closing stream: {}", ex.getMessage());
}
return result;
}
private Collection<Data> normalizeXmlData(Collection<Data> source) {
List<Data> result = new ArrayList<>(source.size());
Data prev = null;
for (Data data: source) {
if (prev != null) {
if (prev.getNodeKind() == NodeKind.element && data.getNodeKind() == NodeKind.text) {
Path path = new Path(prev.getPath(), prev.getDataPath().getRoot(), NodeKind.attribute, prev.getPathId(), prev.getParentPathId(),
prev.getPostId(), prev.getDataPath().getDataType(), prev.getDataPath().getOccurrence());
Element elt = new Element(prev.getElement().getPosition(), data.getValue());
result.add(new Data(path, elt));
prev = null;
} else {
result.add(prev);
prev = data;
}
} else {
prev = data;
}
// TODO: normalize xml arrays!
}
return result;
}
private void writeElement(Deque<Data> dataStack, JsonGenerator stream, Data data) {
switch (data.getNodeKind()) {
case document: { // this must be the first row..
stream.writeStartObject();
dataStack.push(data);
break;
}
case namespace: {
String ns = "xmlns";
String name = data.getName();
if (name != null && name.trim().length() > 0) {
ns += ":" + name;
}
stream.write(ns, data.getValue().toString());
break;
}
case element: {
endElement(dataStack, stream, data);
// must call writeStartObject() in array!
Data top = dataStack.peek();
if (top != null && top.getNodeKind() == NodeKind.array) {
stream.writeStartObject();
} else {
stream.writeStartObject(data.getName());
}
dataStack.push(data);
break;
}
case array: {
//..
endElement(dataStack, stream, data);
stream.writeStartArray(data.getName());
dataStack.push(data);
break;
}
case attribute: {
if (data.isNull()) {
stream.writeNull(data.getName());
} else {
switch (data.getDataPath().getDataType()) {
case XQBASETYPE_BOOLEAN:
stream.write(data.getName(), (Boolean) data.getValue());
break;
case XQBASETYPE_DECIMAL:
stream.write(data.getName(), (BigDecimal) data.getValue());
break;
case XQBASETYPE_LONG:
stream.write(data.getName(), (Long) data.getValue());
break;
default:
stream.write(data.getName(), (String) data.getValue());
}
}
break;
}
case comment: {
// don't have comments in JSON ?
break;
}
case pi: {
// don't have processing instructions in JSON ?
break;
}
case text: {
endElement(dataStack, stream, data);
if (data.isNull()) {
stream.writeNull();
} else {
switch (data.getDataPath().getDataType()) {
case XQBASETYPE_BOOLEAN:
stream.write((Boolean) data.getValue());
break;
case XQBASETYPE_DECIMAL:
stream.write((BigDecimal) data.getValue());
break;
case XQBASETYPE_LONG:
stream.write((Long) data.getValue());
break;
default:
stream.write((String) data.getValue());
}
}
break;
}
default: {
//logger.warn("writeElement; unknown NodeKind: {}", data.getNodeKind());
}
}
}
private void endElement(Deque<Data> dataStack, JsonGenerator stream, Data data) {
do {
Data top = dataStack.peek();
if (top != null && (top.getPos() != data.getParentPos())) {
stream.writeEnd();
dataStack.pop();
} else {
break;
}
} while (true);
}
}