/*******************************************************************************
* 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.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
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 com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.rdf.model.AnonId;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFList;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.XSD;
/**
* Generates a sample RDF resource of a given type.
* @author Greg McFall
*
*/
public class SampleGenerator {
private Random random;
private int maxRepeat = 2;
private int maxInstances = 3;
private int sequence = 0;
private Model model;
public SampleGenerator(Model model) {
this.model = model;
random = new Random(new Date().getTime());
}
public Resource generateSample(Frame frame) {
Resource root = createInstance(frame);
addFields(root, frame);
return root;
}
private Resource createInstance(Frame frame) {
OntClass type = frame.getType();
String typeName = type.getLocalName();
Resource resource = null;
switch (frame.getCategory()) {
case EMBEDDABLE:
AnonId id = new AnonId(typeName + (sequence++));
resource = model.createResource(id);
break;
case ENUMERABLE :
Resource value = selectInstance(type);
if (value != null) {
resource = model.getResource(value.getURI());
if (resource != null) return resource;
resource = model.createResource(value.getURI());
break;
}
// if (value != null), then fall through to the default case.
default:
String instanceURI =
"http://server.example.com/resources/" + typeName + "/" + random.nextInt(100000);
resource = model.createResource(instanceURI);
break;
}
resource.addProperty(RDF.type, type);
return resource;
}
private Resource selectInstance(OntClass type) {
List<Resource> list = new ArrayList<Resource>();
Iterator<? extends OntResource> sequence = type.listInstances(false);
while (sequence.hasNext()) {
list.add(sequence.next());
}
return list.get(random.nextInt(list.size()));
}
private void addFields(Resource parent, Frame frame) {
for (Field field : frame.listAllFields()) {
addField(parent, field);
}
}
private void addField(Resource parent, Field field) {
RdfType type = field.getRdfType();
if (type.canAsDatatype()) {
addDatatype(parent, field);
} else if (type.canAsListType()) {
addList(parent, field);
} else if (type.canAsFrame()) {
addFrame(parent, field);
}
}
private void addList(Resource parent, Field field) {
ListType listType = field.getRdfType().asListType();
RdfType type = listType.getElementType();
RDFList list = model.createList();
if (type.canAsDatatype()) {
list = addDatatypes(list, type.asDatatype());
} else if (type.canAsFrame()) {
list = addFrames(list, type.asFrame());
}
parent.addProperty(field.getProperty(), list);
}
static class ListResourceConsumer implements ResourceConsumer {
private RDFList list;
public ListResourceConsumer(RDFList list) {
this.list = list;
}
@Override
public void consume(Resource value) {
list = list.with(value);
}
public RDFList getList() {
return list;
}
}
private RDFList addFrames(RDFList list, Frame frame) {
ListResourceConsumer consumer = new ListResourceConsumer(list);
addFrame(frame, maxRepeat, consumer);
return consumer.getList();
}
private RDFList addDatatypes(RDFList list, Datatype datatype) {
for (int i=0; i<maxRepeat; i++) {
RDFNode node = createDatatype(datatype);
list = list.with(node);
}
return list;
}
private void addDatatype(Resource parent, Field field) {
Datatype type = field.getRdfType().asDatatype();
int max = field.getMaxCardinality();
max = max<0 ? maxRepeat : Math.min(max, maxRepeat);
for (int i=0; i<max; i++) {
RDFNode node = createDatatype(type);
parent.addProperty(field.getProperty(), node);
}
}
private static interface ResourceConsumer {
void consume(Resource value);
}
private void addFrame(final Resource parent, final Field field) {
final Frame frame = field.getRdfType().asFrame();
int max = field.getMaxCardinality();
max = max<0 ? maxRepeat : Math.min(max, maxRepeat);
addFrame(frame, max, new ResourceConsumer(){
@Override
public void consume(Resource value) {
parent.addProperty(field.getProperty(), value);
}});
}
private void addFrame(Frame frame, int count, ResourceConsumer consumer) {
Map<String, InstanceInfo> map = new HashMap<String, InstanceInfo>();
for (int i=0; i<count; i++) {
Resource value = null;
Frame selectedType = selectType(frame);
InstanceInfo info = map.get(selectedType.getUri());
if (info == null) {
info = new InstanceInfo(listInstancesOfType(selectedType.getType()));
map.put(selectedType.getUri(), info);
}
value = info.get();
if (value == null) {
value = createInstance(selectedType);
addFields(value, selectedType);
info.count++;
}
consumer.consume(value);
}
}
/**
* A structure that keeps track of the set of instances of a given type that have
* already been created.
*
* @author Greg McFall
*
*/
private class InstanceInfo {
List<Resource> list;
int count;
/**
* Initialize this InstanceInfo object with the list of resources of a given
* type that already exist.
* @param list
*/
InstanceInfo(List<Resource> list) {
this.list = list;
count = list.size();
}
private Resource get() {
if (count >= maxInstances && !list.isEmpty()) {
return list.remove(random.nextInt(list.size()));
}
return null;
}
}
private List<Resource> listInstancesOfType(OntClass type) {
List<Resource> list = new ArrayList<Resource>();
Iterator<Resource> sequence = model.listResourcesWithProperty(RDF.type, type);
while (sequence.hasNext()) {
list.add(sequence.next());
}
return list;
}
private RDFNode createDatatype(Datatype datatype) {
RDFNode node = null;
String uri = datatype.getUri();
String baseURI = getBaseURI(datatype);
if (baseURI == null) {
throw new UnsupportedDatatypeException(uri);
}
if (XSD.anyURI.getURI().equals(uri)) {
node = model.createTypedLiteral("http://www.example.com/sampleURI", uri);
} else if (XSD.date.getURI().equals(baseURI)) {
DateTime now = DateTime.now();
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-ddZZ");
node = model.createTypedLiteral(formatter.print(now), uri);
} else if (XSD.dateTime.getURI().equals(baseURI)) {
DateTime now = DateTime.now();
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ssZZ");
node = model.createTypedLiteral(formatter.print(now), uri);
} else if (XSD.xboolean.getURI().equals(baseURI)) {
boolean value[] = new boolean[] {true, false};
node = model.createTypedLiteral(value[random.nextInt(2)], uri);
} else if (
XSD.xbyte.getURI().equals(baseURI) ||
XSD.unsignedByte.getURI().equals(baseURI)
) {
byte value = (byte) random.nextInt(8);
node = model.createTypedLiteral(value, uri);
} 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);
}
node = model.createTypedLiteral(Float.parseFloat(text), uri);
} 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 = model.createTypedLiteral("PT" + hour + "H" + min + "M", uri);
} 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 = model.createTypedLiteral("P" + year + "Y" + month + "M", uri);
} else if (XSD.gDay.getURI().equals(baseURI)) {
String text = "---" + zeroPad(random.nextInt(30), 2);
node = model.createTypedLiteral(text, uri);
} else if (XSD.gMonth.getURI().equals(baseURI)) {
String text = "--" +zeroPad(random.nextInt(12)+1, 2);
node = model.createTypedLiteral(text, uri);
} else if (XSD.gMonthDay.getURI().equals(baseURI)) {
String text = "--" +zeroPad(random.nextInt(12)+1, 2) + "-" + zeroPad(random.nextInt(30), 2);
node = model.createTypedLiteral(text, uri);
} else if (XSD.gYear.getURI().equals(baseURI)) {
String text = Integer.toString( GregorianCalendar.getInstance().get(GregorianCalendar.YEAR) );
node = model.createTypedLiteral(text, uri);
} else if (XSD.gYearMonth.getURI().equals(baseURI)) {
int year = GregorianCalendar.getInstance().get(GregorianCalendar.YEAR);
String month = zeroPad(random.nextInt(12) + 1, 2);
node = model.createTypedLiteral(year + "-" + month, uri);
} else if (
XSD.ID.getURI().equals(baseURI) ||
XSD.IDREF.getURI().equals(baseURI)
) {
node = model.createTypedLiteral("x" + random.nextInt(10000), uri);
} 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 = model.createTypedLiteral(random.nextInt(10000), uri);
} 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 = model.createTypedLiteral(text, uri);
} 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 = model.createTypedLiteral(name[random.nextInt(name.length)], uri);
} else if (XSD.negativeInteger.getURI().equals(baseURI)) {
int value = -random.nextInt(10000);
node = model.createTypedLiteral(value, uri);
} else if (
XSD.xshort.getURI().equals(baseURI) ||
XSD.unsignedShort.getURI().equals(baseURI)
) {
short value = (short) random.nextInt(100);
node = model.createTypedLiteral(value, uri);
} 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 = model.createTypedLiteral(text, uri);
} else {
throw new UnsupportedDatatypeException(uri);
}
return node;
}
private String getBaseURI(Datatype datatype) {
String xsdURI = XSD.getURI();
while (datatype != null) {
if (datatype.getUri().startsWith(xsdURI)) {
return datatype.getUri();
}
datatype = datatype.getBase();
}
return null;
}
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 Frame selectType(Frame frame) {
// TODO: exclude abstract types from list
if (frame.getSubtypeList().isEmpty()) return frame;
List<Frame> list = frame.listAllSubtypes();
list.add(frame);
return list.get(random.nextInt(list.size()));
}
}