/*******************************************************************************
* Copyright 2012 Pearson Education
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.semantictools.json;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.ObjectNode;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.semantictools.context.renderer.model.ContextProperties;
import org.semantictools.context.renderer.model.FrameConstraints;
import org.semantictools.context.renderer.model.JsonContext;
import org.semantictools.context.renderer.model.TermInfo;
import org.semantictools.frame.api.FrameNotFoundException;
import org.semantictools.frame.api.TypeManager;
import org.semantictools.frame.model.Datatype;
import org.semantictools.frame.model.Field;
import org.semantictools.frame.model.Frame;
import org.semantictools.frame.model.ListType;
import org.semantictools.frame.model.RdfType;
import org.semantictools.frame.model.RestCategory;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.vocabulary.XSD;
public class JsonSampleGenerator {
private static final String CONTAINER = "http://www.w3.org/ns/ldp#Container";
private static final String PAGE = "http://www.w3.org/ns/ldp#Page";
private static final String pageOf = "http://www.w3.org/ns/ldp#pageOf";
private TypeManager typeManager;
private JsonContext context;
private ContextProperties contextProperties;
private JsonNodeFactory factory;
private Random random;
private int maxCyclicDepth = 2;
private int maxRepeat = 2;
private int maxDepth = 5;
public JsonSampleGenerator(TypeManager typeManager) {
this.typeManager = typeManager;
factory = JsonNodeFactory.instance;
random = new Random(new Date().getTime());
}
public ObjectNode generateSample(JsonContext context, ContextProperties properties) {
ObjectNode node = factory.objectNode();
if (context == null) {
return node;
}
this.contextProperties = properties;
this.context = context;
addContextProperty(node);
List<String> graphTypes = properties.getGraphTypes();
if (graphTypes.isEmpty()) {
Frame frame = typeManager.getFrameByUri(context.getRootType());
if (frame.isSubclassOf(CONTAINER)) {
frame = typeManager.getFrameByUri(PAGE);
}
Branch branch = new Branch(null, null, node, frame);
addTypeProperty(node, frame);
addProperties(branch);
} else {
buildGraph(node, graphTypes);
}
return node;
}
private void buildGraph(ObjectNode node, List<String> graphTypes) {
ArrayNode array = factory.arrayNode();
node.put("@graph", array);
for (String typeURI : graphTypes) {
Frame frame = typeManager.getFrameByUri(typeURI);
if (frame == null) {
throw new FrameNotFoundException(typeURI);
}
ObjectNode obj = factory.objectNode();
array.add(obj);
addTypeProperty(obj, frame);
Branch branch = new Branch(null, null, obj, frame);
addProperties(branch);
}
}
private void addProperties(Branch branch) {
addConditionalTypeProperty(branch);
addIdProperty(branch);
if (branch.getDepth() < maxDepth) {
List<Field> fieldList = branch.getFrame().listAllFields();
for (Field field : fieldList) {
if (!isIncluded(field, contextProperties, branch.getFrame())) continue;
addField(branch, field, null);
}
}
}
private boolean isIncluded(Field field, ContextProperties properties, Frame declaringFrame) {
String fieldType = field.getRdfType().getUri();
if (properties.getExcludedTypes().contains(fieldType)) return false;
FrameConstraints constraints = properties.getFrameConstraints(declaringFrame.getLocalName());
return (constraints == null) || constraints.isIncludedProperty(field.getURI());
}
private void addField(Branch branch, Field field, String fieldName) {
RdfType type = field.getRdfType();
if (field.getURI().equals(pageOf)) {
// Special handling for LDP pageOf
type = typeManager.getFrameByUri(contextProperties.getRdfTypeURI());
}
if (fieldName == null && contextProperties.isSetProperty(field.getURI())) {
addSetProperty(branch, field, type);
} else if (type.canAsDatatype()) {
addDatatype(branch, field, fieldName, type.asDatatype());
} else if (type.canAsListType()) {
addList(branch, field, type.asListType());
} else if (type.canAsFrame() && !shortCircuit(branch, field, fieldName, type.asFrame())) {
addFrame(branch, field, fieldName, type.asFrame());
}
}
private boolean shortCircuit(Branch branch, Field field, String fieldName, Frame frame) {
List<Frame> frameList = frame.listAllSubtypes();
List<Datatype> typeList = frame.getSubdatatypeList();
if ((frameList.size()+typeList.size()) != 1) return false;
if (frameList.isEmpty()) {
addDatatype(branch, field, fieldName, typeList.get(0));
} else {
addFrame(branch, field, fieldName, frameList.get(0));
}
return true;
}
private void addFrame(final Branch branch, Field field, String fieldNameOverride, Frame frame) {
int maxCount = field.getMaxCardinality();
if (maxCount < 0 || maxCount>maxRepeat) {
maxCount = maxRepeat;
}
String uri = field.getURI();
TermInfo term = context.getTermInfoByURI(uri);
if (term == null) {
return;
}
final String fieldName = (fieldNameOverride==null) ? term.getTermName() : fieldNameOverride;
NodeConsumer callback = null;
if (maxCount == 1) {
callback = new NodeConsumer() {
@Override
public void consume(JsonNode node) {
branch.getNode().put(fieldName, node);
}
};
} else {
ArrayNode array = factory.arrayNode();
branch.getNode().put(fieldName, array);
callback = new ArrayNodeConsumer(array);
}
for (int i=0; i<maxCount; i++) {
createFrame(branch, term, field, frame, callback);
}
}
static class ArrayNodeConsumer implements NodeConsumer {
private ArrayNode array;
public ArrayNodeConsumer(ArrayNode array) {
this.array = array;
}
@Override
public void consume(JsonNode node) {
array.add(node);
}
}
interface NodeConsumer {
void consume(JsonNode node);
}
private void createFrame(Branch branch, TermInfo term, Field field, Frame frame, NodeConsumer callback) {
boolean iriReference = term!=null && term.isCoercedAsIriRef();
if (field.getType().canAs(OntProperty.class)) {
createPropertyReference(branch, field, callback);
} else if (iriReference && frame.getCategory() == RestCategory.ENUMERABLE) {
createEnumReference(branch, frame, callback);
} else if (iriReference && field.getValueRestriction()!=null) {
String text = field.getValueRestriction().getLocalName();
TermInfo info = context.getTermInfoByShortName(text);
if (info != null) {
callback.consume(factory.textNode(text));
} else {
text = field.getValueRestriction().getUri();
callback.consume(factory.textNode(text));
}
} else if (iriReference) {
String typeName = frame.getLocalName();
int id = random.nextInt(100000);
callback.consume(factory.textNode("http://server.example.com/resources/" + typeName + "/" + id));
} else {
ObjectNode child = factory.objectNode();
if (exceedsMaxCyclicDepth(branch, frame)) {
return;
}
Frame childFrame = selectType(frame);
Branch childBranch = new Branch(branch, field, child, childFrame);
addProperties(childBranch);
callback.consume(child);
}
}
private void createPropertyReference(Branch branch, Field field, NodeConsumer callback) {
List<? extends OntProperty> list = field.getType().asProperty().listSubProperties().toList();
for (int i=0; i<list.size(); i++) {
int rand = random.nextInt(list.size());
OntProperty value = list.get(rand);
String uri = value.getURI();
TermInfo info = context.getTermInfoByURI(uri);
if (info != null) {
callback.consume(factory.textNode(info.getTermName()));
return;
}
}
callback.consume(factory.textNode("http://schema.example.com/foo"));
}
private void createEnumReference(Branch branch, Frame frame, NodeConsumer callback) {
// TODO: it is expensive to compute the list of instances each time this method is called.
// We might want to cache instance lists, so that they get generated only one.
OntClass type = frame.getType();
List<? extends OntResource> list = type.listInstances(false).toList();
OntResource target = list.isEmpty() ? null : list.get(random.nextInt(list.size()));
if (target == null) {
String typeName = frame.getLocalName();
int id = random.nextInt(100000);
callback.consume(factory.textNode("http://server.example.com/resources/" + typeName + "/" + id));
} else {
String name = getSimpleName(target.getURI());
callback.consume(factory.textNode(name));
}
}
// private void printDebug(Branch branch, Field field) {
// List<String> list = new ArrayList<String>();
// list.add(field.getLocalName());
// while (branch != null) {
// field = branch.getField();
// if (field != null) {
// list.add(0, field.getLocalName());
// }
// branch = branch.getParent();
// }
// StringBuilder builder = new StringBuilder();
// for (int i=0; i<list.size(); i++) {
// if (i>0) {
// builder.append(".");
// }
// builder.append(list.get(i));
// }
//
// System.out.println(builder.toString());
//
//
// }
private Frame selectType(Frame frame) {
if (frame.getSubtypeList().isEmpty()) return frame;
List<Frame> list = frame.listAllSubtypes();
if (!frame.isAbstract()) {
list.add(frame);
}
Iterator<Frame> sequence = list.iterator();
while (sequence.hasNext()) {
Frame type = sequence.next();
if (type.isAbstract()) sequence.remove();
}
if (list.isEmpty()) return frame;
return list.get(random.nextInt(list.size()));
}
private boolean exceedsMaxCyclicDepth(Branch branch, Frame frame) {
int count = 0;
while (branch != null) {
RdfType type = (branch.getField()==null) ? branch.getFrame() : branch.getField().getRdfType();
if (type.canAsFrame() && type.asFrame() == frame) {
count++;
if (count >= maxCyclicDepth) return true;
}
branch = branch.getParent();
}
return false;
}
private void addList(Branch branch, Field field, ListType listType) {
String fieldName = getSimpleName(field.getURI());
if (fieldName == null) return;
RdfType elementType = listType.getElementType();
ArrayNode array = factory.arrayNode();
branch.getNode().put(fieldName, array);
if (elementType.canAsDatatype()) {
addDatatypesToArray(array, elementType.asDatatype());
} else if (elementType.canAsFrame()) {
addFramesToArray(branch, field, array, elementType.asFrame());
}
}
private void addFramesToArray(Branch branch, Field field, ArrayNode array, Frame frame) {
ArrayNodeConsumer callback = new ArrayNodeConsumer(array);
TermInfo term = context.getTermInfoByURI(field.getURI());
for (int i=0; i<maxRepeat; i++) {
createFrame(branch, term, field, frame, callback);
}
}
private void addDatatypesToArray(ArrayNode array, Datatype datatype) {
for (int i=0; i<maxRepeat; i++) {
JsonNode value = createDatatype(datatype);
array.add(value);
}
}
private void addSetProperty(Branch branch, Field field, RdfType type) {
String fieldName = getSimpleName(field.getURI());
ObjectNode setContainer = factory.objectNode();
branch.getNode().put(fieldName, setContainer);
TermInfo term = context.getTermInfoByURI(branch.getFrame().getUri());
String typeName = (term==null) ? branch.getFrame().getLocalName() : term.getTermName();
String value = "http://server.example.com/resources/" + typeName + "/" + random.nextInt(100000) + "/" + fieldName;
setContainer.put("@id", value);
Branch setBranch = new Branch(branch, field, setContainer, branch.getFrame());
addField(setBranch, field, "@set");
}
private void addDatatype(Branch branch, Field field, String fieldName, Datatype type) {
int maxCount = field.getMaxCardinality();
if (maxCount<0 || maxCount>maxRepeat) {
maxCount = maxRepeat;
}
if (fieldName == null) {
fieldName = getSimpleName(field.getURI());
}
if (fieldName == null) return;
if (maxCount == 1) {
JsonNode value = createDatatype(type);
branch.getNode().put(fieldName, value);
return;
}
ArrayNode array = factory.arrayNode();
branch.getNode().put(fieldName, array);
for (int i=0; i<maxCount; i++) {
JsonNode value = createDatatype(type);
array.add(value);
}
}
private JsonNode createDatatype(Datatype datatype) {
JsonNode node = null;
String uri = datatype.getUri();
String baseURI = typeManager.getXsdBaseURI(datatype);
if (baseURI == null) {
throw new UnsupportedDatatypeException(uri);
}
if (XSD.anyURI.getURI().equals(uri)) {
node = factory.textNode("http://www.example.com/sampleURI");
} else if (XSD.date.getURI().equals(baseURI)) {
DateTime now = DateTime.now();
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-ddZZ");
node = factory.textNode(formatter.print(now));
} else if (XSD.dateTime.getURI().equals(baseURI)) {
DateTime now = DateTime.now();
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ssZZ");
node = factory.textNode(formatter.print(now));
} else if (XSD.xboolean.getURI().equals(baseURI)) {
boolean value[] = new boolean[] {true, false};
node = factory.booleanNode(value[random.nextInt(2)]);
} else if (
XSD.xbyte.getURI().equals(baseURI) ||
XSD.unsignedByte.getURI().equals(baseURI)
) {
byte value = (byte) random.nextInt(8);
node = factory.numberNode(value);
} else if (
XSD.decimal.getURI().equals(baseURI) ||
XSD.xdouble.getURI().equals(baseURI) ||
XSD.xfloat.getURI().equals(baseURI)
) {
String text = Double.toString(random.nextInt(1000) * random.nextDouble());
if (text.length() > 5) {
text = text.substring(0, 5) + "000000000";
}
double value = Double.parseDouble(text);
node = factory.numberNode(value);
} else if (
XSD.duration.getURI().equals(baseURI) ||
"http://www.w3.org/2004/10/xpath-datatypes#dayTimeDuration".equals(baseURI)
) {
int hour = random.nextInt(24);
int min = random.nextInt(60);
node = factory.textNode("PT" + hour + "H" + min + "M");
} else if ("http://www.w3.org/2004/10/xpath-datatypes#yearMonthDuration".equals(baseURI)) {
int year = GregorianCalendar.getInstance().get(GregorianCalendar.YEAR);
int month = random.nextInt(12)+1;
node = factory.textNode("P" + year + "Y" + month + "M");
} else if (XSD.gDay.getURI().equals(baseURI)) {
String text = "---" + zeroPad(random.nextInt(30), 2);
node = factory.textNode(text);
} else if (XSD.gMonth.getURI().equals(baseURI)) {
String text = "--" +zeroPad(random.nextInt(12)+1, 2);
node = factory.textNode(text);
} else if (XSD.gMonthDay.getURI().equals(baseURI)) {
String text = "--" +zeroPad(random.nextInt(12)+1, 2) + "-" + zeroPad(random.nextInt(30), 2);
node = factory.textNode(text);
} else if (XSD.gYear.getURI().equals(baseURI)) {
String text = Integer.toString( GregorianCalendar.getInstance().get(GregorianCalendar.YEAR) );
node = factory.textNode(text);
} else if (XSD.gYearMonth.getURI().equals(baseURI)) {
int year = GregorianCalendar.getInstance().get(GregorianCalendar.YEAR);
String month = zeroPad(random.nextInt(12) + 1, 2);
node = factory.textNode(year + "-" + month);
} else if (
XSD.ID.getURI().equals(baseURI) ||
XSD.IDREF.getURI().equals(baseURI)
) {
node = factory.textNode("x" + random.nextInt(10000));
} else if (
XSD.xint.getURI().equals(baseURI) ||
XSD.integer.getURI().equals(baseURI) ||
XSD.nonNegativeInteger.getURI().equals(baseURI) ||
XSD.positiveInteger.getURI().equals(baseURI) ||
XSD.xlong.getURI().equals(baseURI) ||
XSD.unsignedInt.getURI().equals(baseURI) ||
XSD.unsignedLong.getURI().equals(baseURI)
) {
node = factory.numberNode(random.nextInt(10000));
} else if (XSD.language.getURI().equals(baseURI)) {
String[] languageList = new String[] {
"ar", "en", "en-us", "fr", "de", "it", "ja", "pl", "ru", "es", "sv", "zh"
};
String text = languageList[random.nextInt(languageList.length)];
node = factory.textNode(text);
} else if (
XSD.Name.getURI().equals(baseURI) ||
XSD.NCName.getURI().equals(baseURI) ||
XSD.token.getURI().equals(baseURI) ||
XSD.normalizedString.getURI().equals(baseURI) ||
XSD.xstring.getURI().equals(baseURI)
) {
String name[] = new String[] {
"alpha", "beta", "gamma", "delta", "epsilon", "zeta",
"eta", "theta", "iota", "kappa", "lambda", "mu", "nu",
"xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon",
"phi", "chi", "psi", "omega"
};
node = factory.textNode(name[random.nextInt(name.length)]);
} else if (XSD.negativeInteger.getURI().equals(baseURI)) {
int value = -random.nextInt(10000);
node = factory.numberNode(value);
} else if (
XSD.xshort.getURI().equals(baseURI) ||
XSD.unsignedShort.getURI().equals(baseURI)
) {
short value = (short) random.nextInt(100);
node = factory.numberNode(value);
} else if (XSD.time.getURI().equals(baseURI)){
DateTime now = DateTime.now();
DateTimeFormatter formatter = DateTimeFormat.forPattern("HH:mm:ss.SSS");
String text = formatter.print(now);
node = factory.textNode(text);
} else {
throw new UnsupportedDatatypeException(uri);
}
return node;
}
private String zeroPad(int value, int len) {
String zero = "00000";
String text = Integer.toString(value);
if (text.length() < len) {
text = zero.substring(0, len - text.length());
}
if (text.length() > len) {
text = text.substring(0, len);
}
return text;
}
private void addIdProperty(Branch branch) {
if (branch.getFrame().getCategory() == RestCategory.ADDRESSABLE) {
String typeName = simpleTypeName(branch.getFrame());
String value = "http://server.example.com/resources/" + typeName + "/" + random.nextInt(100000);
branch.getNode().put("@id", value);
}
}
private String simpleTypeName(Frame frame) {
TermInfo term = context.getTermInfoByURI(frame.getUri());
return (term==null) ? frame.getLocalName() : term.getTermName();
}
private void addConditionalTypeProperty(Branch branch) {
Field field = branch.getField();
RdfType type = field==null ? null : field.getRdfType();
Frame declaredFrame =
(type!=null && type.canAsListType()) ? type.asListType().getElementType().asFrame() :
(type != null) ? type.asFrame() : null;
Frame actualFrame = branch.getFrame();
if ((actualFrame==null) || (actualFrame == declaredFrame) || branch.getNode().has("@type")) return;
addTypeProperty(branch.getNode(), actualFrame);
}
private void addTypeProperty(ObjectNode node, Frame frame) {
node.put("@type", getSimpleName(frame.getUri()));
}
private String getSimpleName(String uri) {
TermInfo term = context.getTermInfoByURI(uri);
return term==null ? null : term.getTermName();
}
private void addContextProperty(ObjectNode node) {
node.put("@context", context.getContextURI());
}
/**
* A wrapper around an ObjectNode so that we can track associated objects
* such as the parent within which the ObjectNode is placed, the Frame
* that defines the properties of the ObjectNode, and the field through which
* the ObjectNode is accessed.
*
*/
static class Branch {
private Branch parent;
private ObjectNode node;
private Field field;
private Frame frame;
public Branch(Branch parent, Field field, ObjectNode node, Frame frame) {
this.node = node;
this.field = field;
this.frame = frame;
this.parent = parent;
}
public ObjectNode getNode() {
return node;
}
public Frame getFrame() {
return frame;
}
public Branch getParent() {
return parent;
}
public Field getField() {
return field;
}
public int getDepth() {
return parent==null ? 0 : parent.getDepth() + 1;
}
}
}