commit c888c5f681d4543cbe2bf969c5476a5c3b94f973 parent 6443a512e21466244f7dc9fdefe40812cf87fc77 Author: Dan Callaghan <djc@djc.id.au> Date: Sun, 16 May 2010 16:30:22 +1000 changed group/package to au.id.djc --HG-- rename : src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g => src/main/antlr3/au/id/djc/rdftemplate/selector/Selector.g rename : src/main/java/au/com/miskinhill/rdftemplate/ContentAction.java => src/main/java/au/id/djc/rdftemplate/ContentAction.java rename : src/main/java/au/com/miskinhill/rdftemplate/ForAction.java => src/main/java/au/id/djc/rdftemplate/ForAction.java rename : src/main/java/au/com/miskinhill/rdftemplate/IfAction.java => src/main/java/au/id/djc/rdftemplate/IfAction.java rename : src/main/java/au/com/miskinhill/rdftemplate/JoinAction.java => src/main/java/au/id/djc/rdftemplate/JoinAction.java rename : src/main/java/au/com/miskinhill/rdftemplate/TemplateAction.java => src/main/java/au/id/djc/rdftemplate/TemplateAction.java rename : src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolationException.java => src/main/java/au/id/djc/rdftemplate/TemplateInterpolationException.java rename : src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java => src/main/java/au/id/djc/rdftemplate/TemplateInterpolator.java rename : src/main/java/au/com/miskinhill/rdftemplate/TemplateSyntaxException.java => src/main/java/au/id/djc/rdftemplate/TemplateSyntaxException.java rename : src/main/java/au/com/miskinhill/rdftemplate/XMLStream.java => src/main/java/au/id/djc/rdftemplate/XMLStream.java rename : src/main/java/au/com/miskinhill/rdftemplate/datatype/DateDataType.java => src/main/java/au/id/djc/rdftemplate/datatype/DateDataType.java rename : src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java => src/main/java/au/id/djc/rdftemplate/datatype/DateTimeDataType.java rename : src/main/java/au/com/miskinhill/rdftemplate/datatype/Year.java => src/main/java/au/id/djc/rdftemplate/datatype/Year.java rename : src/main/java/au/com/miskinhill/rdftemplate/datatype/YearMonth.java => src/main/java/au/id/djc/rdftemplate/datatype/YearMonth.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/AbstractAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/AbstractAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/AbstractSelector.java => src/main/java/au/id/djc/rdftemplate/selector/AbstractSelector.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/Adaptation.java => src/main/java/au/id/djc/rdftemplate/selector/Adaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/AdaptationFactory.java => src/main/java/au/id/djc/rdftemplate/selector/AdaptationFactory.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/AntlrSelectorFactory.java => src/main/java/au/id/djc/rdftemplate/selector/AntlrSelectorFactory.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/BooleanAndPredicate.java => src/main/java/au/id/djc/rdftemplate/selector/BooleanAndPredicate.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/ComparableLiteralValueAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/ComparableLiteralValueAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationFactory.java => src/main/java/au/id/djc/rdftemplate/selector/DefaultAdaptationFactory.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultPredicateResolver.java => src/main/java/au/id/djc/rdftemplate/selector/DefaultPredicateResolver.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactory.java => src/main/java/au/id/djc/rdftemplate/selector/EternallyCachingSelectorFactory.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/FormattedDateTimeAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/InvalidSelectorSyntaxException.java => src/main/java/au/id/djc/rdftemplate/selector/InvalidSelectorSyntaxException.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/LiteralValueAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/NoopSelector.java => src/main/java/au/id/djc/rdftemplate/selector/NoopSelector.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/Predicate.java => src/main/java/au/id/djc/rdftemplate/selector/Predicate.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/PredicateResolver.java => src/main/java/au/id/djc/rdftemplate/selector/PredicateResolver.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/Selector.java => src/main/java/au/id/djc/rdftemplate/selector/Selector.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java => src/main/java/au/id/djc/rdftemplate/selector/SelectorComparator.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationException.java => src/main/java/au/id/djc/rdftemplate/selector/SelectorEvaluationException.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorFactory.java => src/main/java/au/id/djc/rdftemplate/selector/SelectorFactory.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorWithAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/SelectorWithAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/StringLiteralValueAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/StringLiteralValueAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/Traversal.java => src/main/java/au/id/djc/rdftemplate/selector/Traversal.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/TraversingSelector.java => src/main/java/au/id/djc/rdftemplate/selector/TraversingSelector.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/TypePredicate.java => src/main/java/au/id/djc/rdftemplate/selector/TypePredicate.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/UnionSelector.java => src/main/java/au/id/djc/rdftemplate/selector/UnionSelector.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/UriAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/UriAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/UriAnchorAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/UriAnchorAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/UriPrefixPredicate.java => src/main/java/au/id/djc/rdftemplate/selector/UriPrefixPredicate.java rename : src/main/java/au/com/miskinhill/rdftemplate/selector/UriSliceAdaptation.java => src/main/java/au/id/djc/rdftemplate/selector/UriSliceAdaptation.java rename : src/main/java/au/com/miskinhill/rdftemplate/view/RDFTemplateView.java => src/main/java/au/id/djc/rdftemplate/view/RDFTemplateView.java rename : src/main/java/au/com/miskinhill/rdftemplate/view/RDFTemplateViewResolver.java => src/main/java/au/id/djc/rdftemplate/view/RDFTemplateViewResolver.java rename : src/test/java/au/com/miskinhill/rdftemplate/TemplateInterpolatorUnitTest.java => src/test/java/au/id/djc/rdftemplate/TemplateInterpolatorUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/TestNamespacePrefixMap.java => src/test/java/au/id/djc/rdftemplate/TestNamespacePrefixMap.java rename : src/test/java/au/com/miskinhill/rdftemplate/datatype/DateDataTypeUnitTest.java => src/test/java/au/id/djc/rdftemplate/datatype/DateDataTypeUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataTypeUnitTest.java => src/test/java/au/id/djc/rdftemplate/datatype/DateTimeDataTypeUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/datatype/YearMonthUnitTest.java => src/test/java/au/id/djc/rdftemplate/datatype/YearMonthUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/datatype/YearUnitTest.java => src/test/java/au/id/djc/rdftemplate/datatype/YearUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java => src/test/java/au/id/djc/rdftemplate/selector/AdaptationMatcher.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/BeanPropertyMatcher.java => src/test/java/au/id/djc/rdftemplate/selector/BeanPropertyMatcher.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java => src/test/java/au/id/djc/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/PredicateMatcher.java => src/test/java/au/id/djc/rdftemplate/selector/PredicateMatcher.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorComparatorMatcher.java => src/test/java/au/id/djc/rdftemplate/selector/SelectorComparatorMatcher.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java => src/test/java/au/id/djc/rdftemplate/selector/SelectorEvaluationUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorMatcher.java => src/test/java/au/id/djc/rdftemplate/selector/SelectorMatcher.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java => src/test/java/au/id/djc/rdftemplate/selector/SelectorParserUnitTest.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/TraversalMatcher.java => src/test/java/au/id/djc/rdftemplate/selector/TraversalMatcher.java rename : src/test/java/au/com/miskinhill/rdftemplate/selector/UriAnchorAdaptationUnitTest.java => src/test/java/au/id/djc/rdftemplate/selector/UriAnchorAdaptationUnitTest.java rename : src/test/resources/au/com/miskinhill/rdftemplate/conditional.xml => src/test/resources/au/id/djc/rdftemplate/conditional.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/for-seq.xml => src/test/resources/au/id/djc/rdftemplate/for-seq.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/for.xml => src/test/resources/au/id/djc/rdftemplate/for.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/join-seq.xml => src/test/resources/au/id/djc/rdftemplate/join-seq.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/join.xml => src/test/resources/au/id/djc/rdftemplate/join.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/namespaces.xml => src/test/resources/au/id/djc/rdftemplate/namespaces.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/replace-subtree.xml => src/test/resources/au/id/djc/rdftemplate/replace-subtree.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/replace-xml.xml => src/test/resources/au/id/djc/rdftemplate/replace-xml.xml rename : src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml => src/test/resources/au/id/djc/rdftemplate/test-data.xml Diffstat:
133 files changed, 3187 insertions(+), 3187 deletions(-) diff --git a/pom.xml b/pom.xml @@ -1,24 +1,24 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>au.com.miskinhill</groupId> + <groupId>au.id.djc</groupId> <artifactId>rdftemplate</artifactId> <packaging>jar</packaging> <version>1.1-SNAPSHOT</version> <name>rdftemplate</name> - <url>http://code.miskinhill.com.au/</url> + <url>http://code.djc.id.au/</url> <distributionManagement> <repository> - <id>code.miskinhill.com.au</id> - <name>Miskin Hill Maven repository</name> - <url>dav:http://code.miskinhill.com.au/maven2/</url> + <id>code.djc.id.au</id> + <name>code.djc.id.au Maven repository</name> + <url>dav:http://code.djc.id.au/maven2/</url> </repository> </distributionManagement> <scm> - <url>http://code.miskinhill.com.au/hg/</url> - <connection>scm:hg:http://code.miskinhill.com.au/hg/</connection> + <url>http://code.djc.id.au/hg/rdftemplate-master/</url> + <connection>scm:hg:http://code.djc.id.au/hg/rdftemplate-master/</connection> <developerConnection>scm:hg:file://${project.basedir}</developerConnection> </scm> diff --git a/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g b/src/main/antlr3/au/id/djc/rdftemplate/selector/Selector.g diff --git a/src/main/java/au/com/miskinhill/rdftemplate/ContentAction.java b/src/main/java/au/com/miskinhill/rdftemplate/ContentAction.java @@ -1,68 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.util.Set; - -import javax.xml.XMLConstants; -import javax.xml.namespace.QName; -import javax.xml.stream.XMLEventFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Attribute; -import javax.xml.stream.events.StartElement; -import javax.xml.stream.util.XMLEventConsumer; - -import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.ToStringBuilder; - -import au.com.miskinhill.rdftemplate.selector.Selector; - -public class ContentAction extends TemplateAction { - - public static final String ACTION_NAME = "content"; - public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); - private static final QName XML_LANG_QNAME = new QName(XMLConstants.XML_NS_URI, "lang", XMLConstants.XML_NS_PREFIX); - private static final String XHTML_NS_URI = "http://www.w3.org/1999/xhtml"; - - private final StartElement start; - private final Selector<?> selector; - - public ContentAction(StartElement start, Selector<?> selector) { - this.start = start; - this.selector = selector; - } - - public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer, XMLEventFactory eventFactory) - throws XMLStreamException { - Object replacement = selector.singleResult(node); - StartElement start = interpolator.interpolateAttributes(this.start, node); - Set<Attribute> attributes = interpolator.cloneAttributesWithout(start, ACTION_QNAME); - if (replacement instanceof Literal) { - Literal literal = (Literal) replacement; - if (!StringUtils.isEmpty(literal.getLanguage())) { - attributes.add(eventFactory.createAttribute(XML_LANG_QNAME, ((Literal) replacement).getLanguage())); - if (start.getName().getNamespaceURI().equals(XHTML_NS_URI)) { - String xhtmlPrefixInContext = start.getNamespaceContext().getPrefix(XHTML_NS_URI); - QName xhtmlLangQNameForContext; // ugh - if (xhtmlPrefixInContext.isEmpty()) - xhtmlLangQNameForContext = new QName("lang"); - else - xhtmlLangQNameForContext = new QName(XHTML_NS_URI, "lang", xhtmlPrefixInContext); - attributes.add(eventFactory.createAttribute(xhtmlLangQNameForContext, literal.getLanguage())); - } - } - } - writer.add(eventFactory.createStartElement(start.getName(), attributes.iterator(), start.getNamespaces())); - interpolator.writeTreeForContent(writer, replacement); - writer.add(eventFactory.createEndElement(start.getName(), start.getNamespaces())); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("start", start) - .append("selector", selector) - .toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/ForAction.java b/src/main/java/au/com/miskinhill/rdftemplate/ForAction.java @@ -1,55 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.util.List; -import java.util.logging.Logger; - -import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.XMLEvent; -import javax.xml.stream.util.XMLEventConsumer; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.Seq; -import com.hp.hpl.jena.vocabulary.RDF; -import org.apache.commons.lang.builder.ToStringBuilder; - -import au.com.miskinhill.rdftemplate.selector.Selector; - -public class ForAction extends TemplateAction { - - public static final String ACTION_NAME = "for"; - public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); - private static final Logger LOG = Logger.getLogger(ForAction.class.getName()); - - private final List<XMLEvent> tree; - private final Selector<RDFNode> selector; - - public ForAction(List<XMLEvent> tree, Selector<RDFNode> selector) { - this.tree = tree; - this.selector = selector; - } - - public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer) - throws XMLStreamException { - List<RDFNode> result = selector.result(node); - if (result.size() == 1 && result.get(0).canAs(Resource.class)) { - if (result.get(0).as(Resource.class).hasProperty(RDF.type, RDF.Seq)) { - LOG.fine("Apply rdf:Seq special case for " + result.get(0)); - result = result.get(0).as(Seq.class).iterator().toList(); - LOG.fine("Resulting sequence is " + result); - } - } - for (RDFNode eachNode: result) { - interpolator.interpolate(tree.iterator(), eachNode, writer); - } - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("selector", selector) - .toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/IfAction.java b/src/main/java/au/com/miskinhill/rdftemplate/IfAction.java @@ -1,46 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.util.List; - -import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.XMLEvent; -import javax.xml.stream.util.XMLEventConsumer; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.apache.commons.lang.builder.ToStringBuilder; - -import au.com.miskinhill.rdftemplate.selector.Selector; - -public class IfAction extends TemplateAction { - - public static final String ACTION_NAME = "if"; - public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); - - private final List<XMLEvent> tree; - private final Selector<?> condition; - private final boolean negate; - - public IfAction(List<XMLEvent> tree, Selector<?> condition, boolean negate) { - this.tree = tree; - this.condition = condition; - this.negate = negate; - } - - public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer) - throws XMLStreamException { - List<?> selectorResult = condition.result(node); - if (negate ? selectorResult.isEmpty() : !selectorResult.isEmpty()) { - interpolator.interpolate(tree.iterator(), node, writer); - } - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("condition", condition) - .append("negate", negate) - .toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/JoinAction.java b/src/main/java/au/com/miskinhill/rdftemplate/JoinAction.java @@ -1,64 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.util.List; -import java.util.logging.Logger; - -import javax.xml.namespace.QName; -import javax.xml.stream.XMLEventFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.XMLEvent; -import javax.xml.stream.util.XMLEventConsumer; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.Seq; -import com.hp.hpl.jena.vocabulary.RDF; -import org.apache.commons.lang.builder.ToStringBuilder; - -import au.com.miskinhill.rdftemplate.selector.Selector; - -public class JoinAction extends TemplateAction { - - public static final String ACTION_NAME = "join"; - public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); - private static final Logger LOG = Logger.getLogger(JoinAction.class.getName()); - - private final List<XMLEvent> tree; - private final Selector<RDFNode> selector; - private final String separator; - - public JoinAction(List<XMLEvent> tree, Selector<RDFNode> selector, String separator) { - this.tree = tree; - this.selector = selector; - this.separator = separator; - } - - public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer, XMLEventFactory eventFactory) - throws XMLStreamException { - List<RDFNode> result = selector.result(node); - if (result.size() == 1 && result.get(0).canAs(Resource.class)) { - if (result.get(0).as(Resource.class).hasProperty(RDF.type, RDF.Seq)) { - LOG.fine("Apply rdf:Seq special case for " + result.get(0)); - result = result.get(0).as(Seq.class).iterator().toList(); - LOG.fine("Resulting sequence is " + result); - } - } - boolean first = true; - for (RDFNode eachNode: result) { - if (!first) { - writer.add(eventFactory.createCharacters(separator)); - } - interpolator.interpolate(tree.iterator(), eachNode, writer); - first = false; - } - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("selector", selector) - .append("separator", separator) - .toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateAction.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateAction.java @@ -1,5 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -public abstract class TemplateAction { - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolationException.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolationException.java @@ -1,23 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import javax.xml.stream.Location; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public class TemplateInterpolationException extends RuntimeException { - - private static final long serialVersionUID = -1472104970210074672L; - - public TemplateInterpolationException(Location location, TemplateAction action, RDFNode node, Throwable cause) { - super("Exception evaluating action [" + action + "] " + - "at location [" + location.getLineNumber() + "," + location.getColumnNumber() + "] " + - "with context node " + node, cause); - } - - public TemplateInterpolationException(Location location, String selectorExpression, RDFNode node, Throwable cause) { - super("Exception evaluating selector expression [" + selectorExpression + "] " + - "at location [" + location.getLineNumber() + "," + location.getColumnNumber() + "] " + - "with context node " + node, cause); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java @@ -1,431 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.xml.namespace.QName; -import javax.xml.stream.XMLEventFactory; -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLEventWriter; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamConstants; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Attribute; -import javax.xml.stream.events.Characters; -import javax.xml.stream.events.EndElement; -import javax.xml.stream.events.Namespace; -import javax.xml.stream.events.StartElement; -import javax.xml.stream.events.XMLEvent; -import javax.xml.stream.util.XMLEventConsumer; - -import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.RDFNode; - -import au.com.miskinhill.rdftemplate.selector.InvalidSelectorSyntaxException; -import au.com.miskinhill.rdftemplate.selector.Selector; -import au.com.miskinhill.rdftemplate.selector.SelectorFactory; - -public class TemplateInterpolator { - - public static final String NS = "http://code.miskinhill.com.au/rdftemplate/"; - - private final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); - private final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - private final XMLEventFactory eventFactory = XMLEventFactory.newInstance(); - - private final SelectorFactory selectorFactory; - - public TemplateInterpolator(SelectorFactory selectorFactory) { - this.selectorFactory = selectorFactory; - inputFactory.setProperty(XMLInputFactory.IS_COALESCING, true); - outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); - } - - public String interpolate(Reader reader, RDFNode node) { - try { - StringWriter writer = new StringWriter(); - final XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); - XMLEventConsumer destination = new XMLEventConsumer() { - @Override - public void add(XMLEvent event) throws XMLStreamException { - eventWriter.add(event); - } - }; - interpolate(reader, node, destination); - return writer.toString(); - } catch (XMLStreamException e) { - throw new TemplateSyntaxException(e); - } - } - - @SuppressWarnings("unchecked") - public void interpolate(Reader reader, RDFNode node, XMLEventConsumer writer) { - try { - interpolate(inputFactory.createXMLEventReader(reader), node, writer); - } catch (XMLStreamException e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - public void interpolate(InputStream inputStream, RDFNode node, Writer writer) { - try { - final XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); - XMLEventConsumer destination = new XMLEventConsumer() { - @Override - public void add(XMLEvent event) throws XMLStreamException { - eventWriter.add(event); - } - }; - interpolate(inputFactory.createXMLEventReader(inputStream), node, destination); - } catch (XMLStreamException e) { - throw new RuntimeException(e); - } - } - - public void interpolate(Reader reader, RDFNode node, final Collection<XMLEvent> destination) { - interpolate(reader, node, new XMLEventConsumer() { - @Override - public void add(XMLEvent event) { - destination.add(event); - } - }); - } - - public void interpolate(Iterator<XMLEvent> reader, RDFNode node, XMLEventConsumer writer) - throws XMLStreamException { - while (reader.hasNext()) { - XMLEvent event = reader.next(); - switch (event.getEventType()) { - case XMLStreamConstants.START_ELEMENT: { - StartElement start = (StartElement) event; - if (start.getName().equals(IfAction.ACTION_QNAME)) { - Attribute testAttribute = start.getAttributeByName(new QName("test")); - Attribute notAttribute = start.getAttributeByName(new QName("not")); - String condition; - boolean negate = false; - if (testAttribute != null && notAttribute != null) - throw new TemplateSyntaxException(start.getLocation(), "test and not attribute on rdf:if are mutually exclusive"); - else if (testAttribute != null) - condition = testAttribute.getValue(); - else if (notAttribute != null) { - condition = notAttribute.getValue(); - negate = true; - } else - throw new TemplateSyntaxException(start.getLocation(), "rdf:if must have a test attribute or a not attribute"); - Selector<?> conditionSelector; - try { - conditionSelector = selectorFactory.get(condition); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(start.getLocation(), e); - } - List<XMLEvent> tree = consumeTree(start, reader); - // discard enclosing rdf:if - tree.remove(tree.size() - 1); - tree.remove(0); - IfAction action = new IfAction(tree, conditionSelector, negate); - try { - action.evaluate(this, node, writer); - } catch (Exception e) { - throw new TemplateInterpolationException(start.getLocation(), action, node, e); - } - } else if (start.getName().equals(JoinAction.ACTION_QNAME)) { - Attribute eachAttribute = start.getAttributeByName(new QName("each")); - if (eachAttribute == null) - throw new TemplateSyntaxException(start.getLocation(), "rdf:join must have an each attribute"); - String separator = ""; - Attribute separatorAttribute = start.getAttributeByName(new QName("separator")); - if (separatorAttribute != null) - separator = separatorAttribute.getValue(); - Selector<RDFNode> selector; - try { - selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(start.getLocation(), e); - } - List<XMLEvent> events = consumeTree(start, reader); - // discard enclosing rdf:join - events.remove(events.size() - 1); - events.remove(0); - JoinAction action = new JoinAction(events, selector, separator); - try { - action.evaluate(this, node, writer, eventFactory); - } catch (Exception e) { - throw new TemplateInterpolationException(start.getLocation(), action, node, e); - } - } else if (start.getName().equals(ForAction.ACTION_QNAME)) { - Attribute eachAttribute = start.getAttributeByName(new QName("each")); - if (eachAttribute == null) - throw new TemplateSyntaxException(start.getLocation(), "rdf:for must have an each attribute"); - Selector<RDFNode> selector; - try { - selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(start.getLocation(), e); - } - List<XMLEvent> events = consumeTree(start, reader); - // discard enclosing rdf:for - events.remove(events.size() - 1); - events.remove(0); - ForAction action = new ForAction(events, selector); - try { - action.evaluate(this, node, writer); - } catch (Exception e) { - throw new TemplateInterpolationException(start.getLocation(), action, node, e); - } - } else { - Attribute ifAttribute = start.getAttributeByName(IfAction.ACTION_QNAME); - Attribute contentAttribute = start.getAttributeByName(ContentAction.ACTION_QNAME); - Attribute forAttribute = start.getAttributeByName(ForAction.ACTION_QNAME); - if (ifAttribute != null) { - Selector<?> selector; - try { - selector = selectorFactory.get(ifAttribute.getValue()); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(ifAttribute.getLocation(), e); - } - start = cloneStart(start, cloneAttributesWithout(start, IfAction.ACTION_QNAME), cloneNamespacesWithoutRdf(start)); - IfAction action = new IfAction(consumeTree(start, reader), selector, false); - action.evaluate(this, node, writer); - } else if (contentAttribute != null && forAttribute != null) { - throw new TemplateSyntaxException(start.getLocation(), "rdf:for and rdf:content cannot both be present on an element"); - } else if (contentAttribute != null) { - consumeTree(start, reader); // discard - Selector<?> selector; - try { - selector = selectorFactory.get(contentAttribute.getValue()); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(contentAttribute.getLocation(), e); - } - ContentAction action = new ContentAction(start, selector); - try { - action.evaluate(this, node, writer, eventFactory); - } catch (Exception e) { - throw new TemplateInterpolationException(contentAttribute.getLocation(), action, node, e); - } - } else if (forAttribute != null) { - Selector<RDFNode> selector; - try { - selector = selectorFactory.get(forAttribute.getValue()).withResultType(RDFNode.class); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(forAttribute.getLocation(), e); - } - start = cloneStart(start, cloneAttributesWithout(start, ForAction.ACTION_QNAME), cloneNamespacesWithoutRdf(start)); - List<XMLEvent> tree = consumeTree(start, reader); - ForAction action = new ForAction(tree, selector); - try { - action.evaluate(this, node, writer); - } catch (Exception e) { - throw new TemplateInterpolationException(forAttribute.getLocation(), action, node, e); - } - } else { - start = interpolateAttributes(start, node); - writer.add(start); - } - } - break; - } - case XMLStreamConstants.CHARACTERS: { - Characters characters = (Characters) event; - interpolateCharacters(writer, characters, node); - break; - } - case XMLStreamConstants.CDATA: { - Characters characters = (Characters) event; - interpolateCharacters(writer, characters, node); - break; - } - default: - writer.add(event); - } - } - } - - private List<XMLEvent> consumeTree(StartElement start, Iterator<XMLEvent> reader) throws XMLStreamException { - List<XMLEvent> events = new ArrayList<XMLEvent>(); - events.add(start); - Deque<QName> elementStack = new LinkedList<QName>(); - while (reader.hasNext()) { - XMLEvent event = reader.next(); - events.add(event); - switch (event.getEventType()) { - case XMLStreamConstants.START_ELEMENT: - elementStack.addLast(((StartElement) event).getName()); - break; - case XMLStreamConstants.END_ELEMENT: - if (elementStack.isEmpty()) { - return events; - } else { - if (!elementStack.removeLast().equals(((EndElement) event).getName())) - throw new IllegalStateException("End element mismatch"); - } - break; - default: - } - } - throw new IllegalStateException("Reader exhausted before end element found"); - } - - @SuppressWarnings("unchecked") - protected StartElement interpolateAttributes(StartElement start, RDFNode node) { - Set<Attribute> replacementAttributes = new LinkedHashSet<Attribute>(); - for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) { - Attribute attribute = it.next(); - String replacementValue = attribute.getValue(); - if (!attribute.getName().getNamespaceURI().equals(NS)) { // skip rdf: attributes - try { - replacementValue = interpolateString(attribute.getValue(), node); - } catch (Exception e) { - throw new TemplateInterpolationException(attribute.getLocation(), attribute.getValue(), node, e); - } - } - replacementAttributes.add(eventFactory.createAttribute(attribute.getName(), - replacementValue)); - } - return cloneStart(start, replacementAttributes, cloneNamespacesWithoutRdf(start)); - } - - private StartElement cloneStart(StartElement start, Iterable<Attribute> attributes, Iterable<Namespace> namespaces) { - return eventFactory.createStartElement( - start.getName().getPrefix(), - start.getName().getNamespaceURI(), - start.getName().getLocalPart(), - attributes.iterator(), - namespaces.iterator(), - start.getNamespaceContext()); - } - - @SuppressWarnings("unchecked") - private Set<Namespace> cloneNamespacesWithoutRdf(StartElement start) { - Set<Namespace> clonedNamespaces = new LinkedHashSet<Namespace>(); - for (Iterator<Namespace> it = start.getNamespaces(); it.hasNext(); ) { - Namespace namespace = it.next(); - if (!namespace.getNamespaceURI().equals(NS)) - clonedNamespaces.add(namespace); - } - return clonedNamespaces; - } - - private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{([^}]*)\\}"); - public String interpolateString(String template, RDFNode node) { - if (!SUBSTITUTION_PATTERN.matcher(template).find()) { - return template; // fast path - } - StringBuffer substituted = new StringBuffer(); - Matcher matcher = SUBSTITUTION_PATTERN.matcher(template); - while (matcher.find()) { - String expression = matcher.group(1); - Object replacement = selectorFactory.get(expression).singleResult(node); - - String replacementValue; - if (replacement instanceof RDFNode) { - RDFNode replacementNode = (RDFNode) replacement; - if (replacementNode.isLiteral()) { - Literal replacementLiteral = (Literal) replacementNode; - replacementValue = replacementLiteral.getValue().toString(); - } else { - throw new UnsupportedOperationException("Not a literal: " + replacementNode); - } - } else { - replacementValue = replacement.toString(); - } - - matcher.appendReplacement(substituted, replacementValue.replace("$", "\\$")); - } - matcher.appendTail(substituted); - return substituted.toString(); - } - - private void interpolateCharacters(XMLEventConsumer writer, Characters characters, RDFNode node) throws XMLStreamException { - String template = characters.getData(); - if (!SUBSTITUTION_PATTERN.matcher(template).find()) { - writer.add(characters); // fast path - return; - } - Matcher matcher = SUBSTITUTION_PATTERN.matcher(template); - int lastAppendedPos = 0; - while (matcher.find()) { - writer.add(eventFactory.createCharacters(template.substring(lastAppendedPos, matcher.start()))); - lastAppendedPos = matcher.end(); - String expression = matcher.group(1); - Selector<?> selector; - try { - selector = selectorFactory.get(expression); - } catch (InvalidSelectorSyntaxException e) { - throw new TemplateSyntaxException(characters.getLocation(), e); - } - try { - Object replacement = selector.singleResult(node); - writeTreeForContent(writer, replacement); - } catch (Exception e) { - throw new TemplateInterpolationException(characters.getLocation(), expression, node, e); - } - } - writer.add(eventFactory.createCharacters(template.substring(lastAppendedPos))); - } - - protected void writeTreeForContent(XMLEventConsumer writer, Object replacement) - throws XMLStreamException { - if (replacement instanceof RDFNode) { - RDFNode replacementNode = (RDFNode) replacement; - if (replacementNode.isLiteral()) { - Literal literal = (Literal) replacementNode; - if (literal.isWellFormedXML()) { - writeXMLLiteral(literal.getLexicalForm(), writer); - } else { - writer.add(eventFactory.createCharacters(literal.getValue().toString())); - } - } else { - throw new UnsupportedOperationException("Not a literal: " + replacementNode); - } - } else if (replacement instanceof XMLStream) { - for (XMLEvent event: (XMLStream) replacement) { - writer.add(event); - } - } else { - writer.add(eventFactory.createCharacters(replacement.toString())); - } - } - - @SuppressWarnings("unchecked") - protected Set<Attribute> cloneAttributesWithout(StartElement start, QName omit) { - // clone attributes, but without rdf:content - Set<Attribute> attributes = new LinkedHashSet<Attribute>(); - for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) { - Attribute attribute = it.next(); - if (!attribute.getName().equals(omit)) - attributes.add(attribute); - } - return attributes; - } - - private void writeXMLLiteral(String literal, XMLEventConsumer writer) - throws XMLStreamException { - XMLEventReader reader = inputFactory.createXMLEventReader(new StringReader(literal)); - while (reader.hasNext()) { - XMLEvent event = reader.nextEvent(); - switch (event.getEventType()) { - case XMLStreamConstants.START_DOCUMENT: - case XMLStreamConstants.END_DOCUMENT: - break; // discard - default: - writer.add(event); - } - } - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateSyntaxException.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateSyntaxException.java @@ -1,22 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import javax.xml.stream.Location; -import javax.xml.stream.XMLStreamException; - -public class TemplateSyntaxException extends RuntimeException { - - private static final long serialVersionUID = 6518982504570154030L; - - public TemplateSyntaxException(Location location, String message) { - super("[location " + location.getLineNumber() + "," + location.getColumnNumber() + "] " + message); - } - - public TemplateSyntaxException(Location location, Throwable cause) { - super("[location " + location.getLineNumber() + "," + location.getColumnNumber() + "]", cause); - } - - public TemplateSyntaxException(XMLStreamException e) { - super(e); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/XMLStream.java b/src/main/java/au/com/miskinhill/rdftemplate/XMLStream.java @@ -1,43 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -import javax.xml.stream.XMLStreamConstants; -import javax.xml.stream.events.XMLEvent; - -public class XMLStream implements Iterable<XMLEvent> { - - public static XMLStream collect(Iterator<XMLEvent> it) { - List<XMLEvent> events = new ArrayList<XMLEvent>(); - while (it.hasNext()) { - XMLEvent event = it.next(); - switch (event.getEventType()) { - case XMLStreamConstants.START_DOCUMENT: - case XMLStreamConstants.END_DOCUMENT: - break; // discard - default: - events.add(event); - } - } - return new XMLStream(events); - } - - private final List<XMLEvent> events; - - public XMLStream(XMLEvent... events) { - this.events = Arrays.asList(events); - } - - public XMLStream(List<XMLEvent> events) { - this.events = events; - } - - @Override - public Iterator<XMLEvent> iterator() { - return events.iterator(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateDataType.java b/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateDataType.java @@ -1,112 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import org.springframework.stereotype.Component; - -import com.hp.hpl.jena.datatypes.DatatypeFormatException; -import com.hp.hpl.jena.datatypes.RDFDatatype; -import com.hp.hpl.jena.datatypes.TypeMapper; -import com.hp.hpl.jena.graph.impl.LiteralLabel; -import org.joda.time.LocalDate; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; - -@Component -public class DateDataType implements RDFDatatype { - - public static final String URI = "http://www.w3.org/TR/xmlschema-2/#date"; - - @SuppressWarnings("unused") - private static DateDataType instance; - public static void registerStaticInstance() { - instance = new DateDataType(); - } - - private final DateTimeFormatter yearParser = DateTimeFormat.forPattern("yyyy"); - private final DateTimeFormatter yearMonthParser = DateTimeFormat.forPattern("yyyy-MM"); - private final DateTimeFormatter dateParser = DateTimeFormat.forPattern("yyyy-MM-dd"); - - public DateDataType() { - TypeMapper.getInstance().registerDatatype(this); - } - - @Override - public String getURI() { - return URI; - } - - @Override - public Class<LocalDate> getJavaClass() { - return null; - } - - @Override - public String unparse(Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public Object cannonicalise(Object value) { - return value; - } - - @Override - public Object extendedTypeDefinition() { - return null; - } - - @Override - public int getHashCode(LiteralLabel lit) { - return lit.getValue().hashCode(); - } - - @Override - public boolean isEqual(LiteralLabel left, LiteralLabel right) { - return left.getValue().equals(right.getValue()); - } - - @Override - public Object parse(String lexicalForm) throws DatatypeFormatException { - try { - return dateParser.parseDateTime(lexicalForm).toLocalDate(); - } catch (IllegalArgumentException e) { - // pass - } - try { - return new YearMonth(yearMonthParser.parseDateTime(lexicalForm).toLocalDate()); - } catch (IllegalArgumentException e) { - // pass - } - try { - return new Year(yearParser.parseDateTime(lexicalForm).toLocalDate()); - } catch (IllegalArgumentException e) { - // pass - } - throw new DatatypeFormatException(lexicalForm, this, "No matching parsers found"); - } - - @Override - public boolean isValid(String lexicalForm) { - try { - parse(lexicalForm); - return true; - } catch (DatatypeFormatException e) { - return false; - } - } - - @Override - public boolean isValidLiteral(LiteralLabel lit) { - return lit.getDatatypeURI().equals(URI) && isValid(lit.getLexicalForm()); - } - - @Override - public boolean isValidValue(Object valueForm) { - return (valueForm instanceof LocalDate); - } - - @Override - public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) { - return dt; - } - -} -\ No newline at end of file diff --git a/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java b/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java @@ -1,98 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import com.hp.hpl.jena.datatypes.DatatypeFormatException; -import com.hp.hpl.jena.datatypes.RDFDatatype; -import com.hp.hpl.jena.datatypes.TypeMapper; -import com.hp.hpl.jena.graph.impl.LiteralLabel; -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; -import org.springframework.stereotype.Component; - -@Component -public class DateTimeDataType implements RDFDatatype { - - public static final String URI = "http://www.w3.org/TR/xmlschema-2/#datetime"; - - @SuppressWarnings("unused") - private static DateTimeDataType instance; - public static void registerStaticInstance() { - instance = new DateTimeDataType(); - } - - private final DateTimeFormatter format = ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed(); - - public DateTimeDataType() { - TypeMapper.getInstance().registerDatatype(this); - } - - @Override - public String getURI() { - return URI; - } - - @Override - public Class<DateTime> getJavaClass() { - return DateTime.class; - } - - @Override - public String unparse(Object value) { - return ((DateTime) value).toString(format); - } - - @Override - public Object cannonicalise(Object value) { - return value; - } - - @Override - public Object extendedTypeDefinition() { - return null; - } - - @Override - public int getHashCode(LiteralLabel lit) { - return lit.getValue().hashCode(); - } - - @Override - public boolean isEqual(LiteralLabel left, LiteralLabel right) { - return left.getValue().equals(right.getValue()); - } - - @Override - public DateTime parse(String lexicalForm) throws DatatypeFormatException { - try { - return format.parseDateTime(lexicalForm); - } catch (IllegalArgumentException e) { - throw new DatatypeFormatException(lexicalForm, this, "Parser barfed"); - } - } - - @Override - public boolean isValid(String lexicalForm) { - try { - parse(lexicalForm); - return true; - } catch (DatatypeFormatException e) { - return false; - } - } - - @Override - public boolean isValidLiteral(LiteralLabel lit) { - return lit.getDatatypeURI().equals(URI) && isValid(lit.getLexicalForm()); - } - - @Override - public boolean isValidValue(Object valueForm) { - return (valueForm instanceof DateTime); - } - - @Override - public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) { - return dt; - } - -} -\ No newline at end of file diff --git a/src/main/java/au/com/miskinhill/rdftemplate/datatype/Year.java b/src/main/java/au/com/miskinhill/rdftemplate/datatype/Year.java @@ -1,50 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import org.joda.time.LocalDate; - -public class Year { - - private final int year; - - public Year(int value) { - this.year = value; - } - - public Year(LocalDate date) { - this.year = date.getYear(); - } - - public Year(YearMonth yearMonth) { - this.year = yearMonth.getYear(); - } - - public int getYear() { - return year; - } - - @Override - public String toString() { - return Integer.toString(year); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + year; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Year other = (Year) obj; - return (year == other.year); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/datatype/YearMonth.java b/src/main/java/au/com/miskinhill/rdftemplate/datatype/YearMonth.java @@ -1,58 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import org.joda.time.LocalDate; - -public class YearMonth { - - private final int year; - private final int month; - - public YearMonth(int year, int month) { - this.year = year; - this.month = month; - } - - public YearMonth(LocalDate date) { - this.year = date.getYear(); - this.month = date.getMonthOfYear(); - } - - public int getYear() { - return year; - } - - public int getMonth() { - return month; - } - - @Override - public String toString() { - return String.format("%04d-%02d", year, month); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + month; - result = prime * result + year; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - YearMonth other = (YearMonth) obj; - if (month != other.month) - return false; - if (year != other.year) - return false; - return true; - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/AbstractAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/AbstractAdaptation.java @@ -1,58 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.Arrays; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public abstract class AbstractAdaptation<DestType, NodeType extends RDFNode> implements Adaptation<DestType> { - - private final Class<DestType> destinationType; - private final Class<?>[] argTypes; - private final Class<NodeType> nodeType; - - protected AbstractAdaptation(Class<DestType> destinationType, Class<?>[] argTypes, Class<NodeType> nodeType) { - this.destinationType = destinationType; - this.argTypes = argTypes; - this.nodeType = nodeType; - } - - @Override - public Class<DestType> getDestinationType() { - return destinationType; - } - - @Override - public Class<?>[] getArgTypes() { - return argTypes; - } - - @Override - public void setArgs(Object[] args) { - if (args.length != argTypes.length) - throw new SelectorEvaluationException("Expected args of types " + Arrays.toString(argTypes) + - " but invoked with " + Arrays.toString(args)); - for (int i = 0; i < args.length; i ++) { - if (!argTypes[i].isAssignableFrom(args[i].getClass())) - throw new SelectorEvaluationException("Arg " + i + ": expected type " + argTypes[i] + - " but was " + args[i].getClass()); - } - setCheckedArgs(args); - } - - protected void setCheckedArgs(Object[] args) { - throw new UnsupportedOperationException(); - } - - @Override - public DestType adapt(RDFNode node) { - if (!nodeType.equals(RDFNode.class)) { - if (!node.canAs(nodeType)) - throw new SelectorEvaluationException("Adaptation can only be applied to " + nodeType + - " but was applied to " + node); - } - return doAdapt(node.as(nodeType)); - } - - protected abstract DestType doAdapt(NodeType node); - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/AbstractSelector.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/AbstractSelector.java @@ -1,40 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.List; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public abstract class AbstractSelector<T> implements Selector<T> { - - private final Class<T> resultType; - - protected AbstractSelector(Class<T> resultType) { - this.resultType = resultType; - } - - @Override - public abstract List<T> result(RDFNode node); - - @Override - public T singleResult(RDFNode node) { - List<T> results = result(node); - if (results.size() != 1) { - throw new SelectorEvaluationException("Expected exactly one result but got " + results); - } - return results.get(0); - } - - @Override - public Class<T> getResultType() { - return resultType; - } - - @Override - public <Other> Selector<Other> withResultType(Class<Other> otherType) { - if (!otherType.isAssignableFrom(resultType)) { - throw new ClassCastException("Result type " + resultType + " incompatible with requested type " + otherType); - } - return (Selector<Other>) this; - } - -} -\ No newline at end of file diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/Adaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/Adaptation.java @@ -1,15 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public interface Adaptation<T> { - - Class<T> getDestinationType(); - - Class<?>[] getArgTypes(); - - void setArgs(Object[] args); - - T adapt(RDFNode node); - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/AdaptationFactory.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/AdaptationFactory.java @@ -1,9 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -public interface AdaptationFactory { - - boolean hasName(String name); - - Adaptation<?> getByName(String name); - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/AntlrSelectorFactory.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/AntlrSelectorFactory.java @@ -1,48 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.Collections; -import java.util.Map; - -import org.antlr.runtime.ANTLRStringStream; -import org.antlr.runtime.CharStream; -import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.RecognitionException; - -public class AntlrSelectorFactory implements SelectorFactory { - - private AdaptationFactory adaptationFactory = new DefaultAdaptationFactory(); - private PredicateResolver predicateResolver = new DefaultPredicateResolver(); - private Map<String, String> namespacePrefixMap = Collections.emptyMap(); - - public AntlrSelectorFactory() { - } - - public void setAdaptationFactory(AdaptationFactory adaptationFactory) { - this.adaptationFactory = adaptationFactory; - } - - public void setPredicateResolver(PredicateResolver predicateResolver) { - this.predicateResolver = predicateResolver; - } - - public void setNamespacePrefixMap(Map<String, String> namespacePrefixMap) { - this.namespacePrefixMap = namespacePrefixMap; - } - - @Override - public Selector<?> get(String expression) { - CharStream stream = new ANTLRStringStream(expression); - SelectorLexer lexer = new SelectorLexer(stream); - CommonTokenStream tokens = new CommonTokenStream(lexer); - SelectorParser parser = new SelectorParser(tokens); - parser.setAdaptationFactory(adaptationFactory); - parser.setPredicateResolver(predicateResolver); - parser.setNamespacePrefixMap(namespacePrefixMap); - try { - return parser.unionSelector(); - } catch (RecognitionException e) { - throw new InvalidSelectorSyntaxException(e); - } - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/BooleanAndPredicate.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/BooleanAndPredicate.java @@ -1,34 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.apache.commons.lang.builder.ToStringBuilder; - -public class BooleanAndPredicate implements Predicate { - - private final Predicate left; - private final Predicate right; - - public BooleanAndPredicate(Predicate left, Predicate right) { - this.left = left; - this.right = right; - } - - public Predicate getLeft() { - return left; - } - - public Predicate getRight() { - return right; - } - - @Override - public boolean evaluate(RDFNode node) { - return left.evaluate(node) && right.evaluate(node); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append(left).append(right).toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/ComparableLiteralValueAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/ComparableLiteralValueAdaptation.java @@ -1,22 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.Literal; - -@SuppressWarnings("unchecked") -public class ComparableLiteralValueAdaptation extends AbstractAdaptation<Comparable, Literal> { - - public ComparableLiteralValueAdaptation() { - super(Comparable.class, new Class<?>[] { }, Literal.class); - } - - @Override - protected Comparable<?> doAdapt(Literal node) { - Object literalValue = node.getValue(); - if (!(literalValue instanceof Comparable<?>)) { - throw new SelectorEvaluationException("Attempted to apply #comparable-lv to non-Comparable node " + node + - " with literal value of type " + literalValue.getClass()); - } - return (Comparable<?>) literalValue; - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationFactory.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationFactory.java @@ -1,35 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.beans.BeanUtils; - -public class DefaultAdaptationFactory implements AdaptationFactory { - - private static final Map<String, Class<? extends Adaptation<?>>> ADAPTATIONS = new HashMap<String, Class<? extends Adaptation<?>>>(); - static { - ADAPTATIONS.put("uri", UriAdaptation.class); - ADAPTATIONS.put("uri-slice", UriSliceAdaptation.class); - ADAPTATIONS.put("uri-anchor", UriAnchorAdaptation.class); - ADAPTATIONS.put("lv", LiteralValueAdaptation.class); - ADAPTATIONS.put("comparable-lv", ComparableLiteralValueAdaptation.class); - ADAPTATIONS.put("string-lv", StringLiteralValueAdaptation.class); - ADAPTATIONS.put("formatted-dt", FormattedDateTimeAdaptation.class); - } - - @Override - public boolean hasName(String name) { - return ADAPTATIONS.containsKey(name); - } - - @Override - public Adaptation<?> getByName(String name) { - Class<? extends Adaptation<?>> adaptationClass = ADAPTATIONS.get(name); - if (adaptationClass == null) { - throw new InvalidSelectorSyntaxException("No adaptation named " + name); - } - return BeanUtils.instantiate(adaptationClass); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultPredicateResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultPredicateResolver.java @@ -1,19 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.HashMap; -import java.util.Map; - -public class DefaultPredicateResolver implements PredicateResolver { - - private static final Map<String, Class<? extends Predicate>> PREDICATES = new HashMap<String, Class<? extends Predicate>>(); - static { - PREDICATES.put("type", TypePredicate.class); - PREDICATES.put("uri-prefix", UriPrefixPredicate.class); - } - - @Override - public Class<? extends Predicate> getByName(String name) { - return PREDICATES.get(name); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactory.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactory.java @@ -1,35 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.HashMap; -import java.util.Map; - -/** - * {@link SelectorFactory} implementation which indirects to a real - * implementation and caches its return values eternally. Do not use in - * situations where the set of input expressions can be unbounded (e.g. - * user-provided) as this will lead to unbounded cache growth. - * <p> - * A better implementation would use a LRU cache or similar, but I cbf. - */ -public class EternallyCachingSelectorFactory implements SelectorFactory { - - private final SelectorFactory real; - private final Map<String, Selector<?>> cache = new HashMap<String, Selector<?>>(); - - public EternallyCachingSelectorFactory(SelectorFactory real) { - this.real = real; - } - - @Override - public Selector<?> get(String expression) { - Selector<?> cached = cache.get(expression); - if (cached == null) { - Selector<?> fresh = real.get(expression); - cache.put(expression, fresh); - return fresh; - } else { - return cached; - } - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java @@ -1,49 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import org.apache.commons.lang.builder.ToStringBuilder; -import org.joda.time.ReadableInstant; -import org.joda.time.ReadablePartial; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; - -import com.hp.hpl.jena.rdf.model.Literal; - -public class FormattedDateTimeAdaptation extends AbstractAdaptation<String, Literal> { - - private String pattern; - private DateTimeFormatter formatter; - - public FormattedDateTimeAdaptation() { - super(String.class, new Class<?>[] { String.class }, Literal.class); - } - - @Override - protected void setCheckedArgs(Object[] args) { - this.pattern = (String) args[0]; - this.formatter = DateTimeFormat.forPattern(pattern.replace("\"", "'")); // for convenience in XML - } - - public String getPattern() { - return pattern; - } - - @Override - protected String doAdapt(Literal node) { - Object lv = node.getValue(); - if (lv instanceof ReadableInstant) { - ReadableInstant instant = (ReadableInstant) lv; - return formatter.print(instant); - } else if (lv instanceof ReadablePartial) { - ReadablePartial instant = (ReadablePartial) lv; - return formatter.print(instant); - } else { - throw new SelectorEvaluationException("Attempted to apply #formatted-dt to non-datetime literal " + lv); - } - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("pattern", pattern).toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/InvalidSelectorSyntaxException.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/InvalidSelectorSyntaxException.java @@ -1,15 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -public class InvalidSelectorSyntaxException extends RuntimeException { - - private static final long serialVersionUID = 5805546105865617336L; - - public InvalidSelectorSyntaxException(Throwable cause) { - super(cause); - } - - public InvalidSelectorSyntaxException(String message) { - super(message); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java @@ -1,16 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.Literal; - -public class LiteralValueAdaptation extends AbstractAdaptation<Object, Literal> { - - public LiteralValueAdaptation() { - super(Object.class, new Class<?>[] { }, Literal.class); - } - - @Override - protected Object doAdapt(Literal node) { - return node.getValue(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/NoopSelector.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/NoopSelector.java @@ -1,19 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.Collections; -import java.util.List; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public class NoopSelector extends AbstractSelector<RDFNode> { - - public NoopSelector() { - super(RDFNode.class); - } - - @Override - public List<RDFNode> result(RDFNode node) { - return Collections.singletonList(node); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/Predicate.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/Predicate.java @@ -1,7 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public interface Predicate extends org.apache.commons.collections15.Predicate<RDFNode> { - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/PredicateResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/PredicateResolver.java @@ -1,7 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -public interface PredicateResolver { - - Class<? extends Predicate> getByName(String name); - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/Selector.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/Selector.java @@ -1,17 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.List; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public interface Selector<T> { - - List<T> result(RDFNode node); - - T singleResult(RDFNode node); - - Class<T> getResultType(); - - <Other> Selector<Other> withResultType(Class<Other> otherType); - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java @@ -1,58 +0,0 @@ -/** - * - */ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.Comparator; - -import org.apache.commons.lang.builder.ToStringBuilder; - -import com.hp.hpl.jena.rdf.model.RDFNode; - -public class SelectorComparator<T extends Comparable<T>> implements Comparator<RDFNode> { - - private Selector<T> selector; - private boolean reversed = false; - - public Selector<T> getSelector() { - return selector; - } - - public void setSelector(Selector<T> selector) { - this.selector = selector; - } - - public boolean isReversed() { - return reversed; - } - - public void setReversed(boolean reversed) { - this.reversed = reversed; - } - - @Override - public String toString() { - return new ToStringBuilder(this).append(selector).append("reversed", reversed).toString(); - } - - @Override - public int compare(RDFNode left, RDFNode right) { - T leftKey; - try { - leftKey = selector.singleResult(left); - } catch (SelectorEvaluationException e) { - throw new SelectorEvaluationException("Exception evaluating selector [" + selector + "] " + - "with context node [" + left + "] for comparison", e); - } - T rightKey; - try { - rightKey = selector.singleResult(right); - } catch (SelectorEvaluationException e) { - throw new SelectorEvaluationException("Exception evaluating selector [" + selector + "] " + - "with context node [" + right + "] for comparison", e); - } - int result = leftKey.compareTo(rightKey); - return reversed ? -result : result; - } - -} -\ No newline at end of file diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationException.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationException.java @@ -1,15 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -public class SelectorEvaluationException extends RuntimeException { - - private static final long serialVersionUID = -398277800899471326L; - - public SelectorEvaluationException(String message) { - super(message); - } - - public SelectorEvaluationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorFactory.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorFactory.java @@ -1,7 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -public interface SelectorFactory { - - Selector<?> get(String expression); - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorWithAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorWithAdaptation.java @@ -1,48 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.ArrayList; -import java.util.List; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.apache.commons.lang.builder.ToStringBuilder; - -public class SelectorWithAdaptation<T> extends AbstractSelector<T> { - - private final Selector<RDFNode> baseSelector; - private final Adaptation<T> adaptation; - - public SelectorWithAdaptation(Selector<RDFNode> baseSelector, Adaptation<T> adaptation) { - super(adaptation.getDestinationType()); - this.baseSelector = baseSelector; - this.adaptation = adaptation; - } - - @Override - public String toString() { - return new ToStringBuilder(this).append(baseSelector).append(adaptation).toString(); - } - - @Override - public List<T> result(RDFNode node) { - List<RDFNode> baseResults = baseSelector.result(node); - List<T> results = new ArrayList<T>(); - for (RDFNode resultNode: baseResults) { - results.add(adaptation.adapt(resultNode)); - } - return results; - } - - @Override - public T singleResult(RDFNode node) { - return adaptation.adapt(baseSelector.singleResult(node)); - } - - public Selector<RDFNode> getBaseSelector() { - return baseSelector; - } - - public Adaptation<T> getAdaptation() { - return adaptation; - } - -} -\ No newline at end of file diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/StringLiteralValueAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/StringLiteralValueAdaptation.java @@ -1,45 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.io.StringReader; - -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.XMLEvent; - -import com.hp.hpl.jena.rdf.model.Literal; - -public class StringLiteralValueAdaptation extends AbstractAdaptation<String, Literal> { - - private static final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); - - public StringLiteralValueAdaptation() { - super(String.class, new Class<?>[] { }, Literal.class); - } - - @Override - protected String doAdapt(Literal literal) { - if (literal.isWellFormedXML()) { - try { - return stripTags(literal.getLexicalForm()); - } catch (XMLStreamException e) { - throw new RuntimeException(e); - } - } else { - return literal.getValue().toString(); - } - } - - private String stripTags(String literal) throws XMLStreamException { - StringBuilder sb = new StringBuilder(); - XMLEventReader reader = inputFactory.createXMLEventReader(new StringReader(literal)); - while (reader.hasNext()) { - XMLEvent event = reader.nextEvent(); - if (event.isCharacters()) { - sb.append(event.asCharacters().getData()); - } - } - return sb.toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/Traversal.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/Traversal.java @@ -1,126 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import org.apache.commons.collections15.CollectionUtils; - -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.ResIterator; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.StmtIterator; -import org.apache.commons.lang.builder.ToStringBuilder; - -public class Traversal { - - private String propertyNamespace; - private String propertyLocalName; - private boolean inverse = false; - private Predicate predicate; - private List<Comparator<RDFNode>> sortOrder = new ArrayList<Comparator<RDFNode>>(); - private Integer subscript; - - private class SortComparator implements Comparator<RDFNode> { - @Override - public int compare(RDFNode left, RDFNode right) { - for (Comparator<RDFNode> comparator: sortOrder) { - int result = comparator.compare(left, right); - if (result != 0) - return result; - } - return 0; - } - } - - public List<RDFNode> traverse(RDFNode node) { - if (!node.isResource()) { - throw new SelectorEvaluationException("Attempted to traverse non-resource node " + node); - } - Resource resource = (Resource) node; - Property property = resource.getModel().createProperty(propertyNamespace, propertyLocalName); - List<RDFNode> destinations = new ArrayList<RDFNode>(); - if (!inverse) { - for (StmtIterator it = resource.listProperties(property); it.hasNext(); ) { - destinations.add(it.nextStatement().getObject()); - } - } else { - for (ResIterator it = resource.getModel().listResourcesWithProperty(property, node); it.hasNext(); ) { - destinations.add(it.nextResource()); - } - } - CollectionUtils.filter(destinations, predicate); - if (!sortOrder.isEmpty()) - Collections.sort(destinations, new SortComparator()); - if (subscript != null) { - if (destinations.size() <= subscript) { - throw new SelectorEvaluationException("Cannot apply subscript " + subscript + " to nodes " + destinations); - } - destinations = Collections.singletonList(destinations.get(subscript)); - } - return destinations; - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("propertyNamespace", propertyNamespace) - .append("propertyLocalName", propertyLocalName) - .append("inverse", inverse) - .append("predicate", predicate) - .append("sortOrder", sortOrder) - .append("subscript", subscript) - .toString(); - } - - public String getPropertyLocalName() { - return propertyLocalName; - } - - public void setPropertyLocalName(String propertyLocalName) { - this.propertyLocalName = propertyLocalName; - } - - public String getPropertyNamespace() { - return propertyNamespace; - } - - public void setPropertyNamespace(String propertyNamespace) { - this.propertyNamespace = propertyNamespace; - } - - public boolean isInverse() { - return inverse; - } - - public void setInverse(boolean inverse) { - this.inverse = inverse; - } - - public Predicate getPredicate() { - return predicate; - } - - public void setPredicate(Predicate predicate) { - this.predicate = predicate; - } - - public List<Comparator<RDFNode>> getSortOrder() { - return sortOrder; - } - - public void addSortOrderComparator(Comparator<RDFNode> selector) { - this.sortOrder.add(selector); - } - - public Integer getSubscript() { - return subscript; - } - - public void setSubscript(Integer subscript) { - this.subscript = subscript; - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/TraversingSelector.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/TraversingSelector.java @@ -1,46 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.apache.commons.lang.builder.ToStringBuilder; - -public class TraversingSelector extends AbstractSelector<RDFNode> { - - private final List<Traversal> traversals = new ArrayList<Traversal>(); - - public TraversingSelector() { - super(RDFNode.class); - } - - @Override - public List<RDFNode> result(RDFNode node) { - Set<RDFNode> current = Collections.singleton(node); - for (Traversal traversal: traversals) { - LinkedHashSet<RDFNode> destinationsUnion = new LinkedHashSet<RDFNode>(); - for (RDFNode start: current) { - destinationsUnion.addAll(traversal.traverse(start)); - } - current = destinationsUnion; - } - return new ArrayList<RDFNode>(current); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append(traversals).toString(); - } - - public List<Traversal> getTraversals() { - return traversals; - } - - public void addTraversal(Traversal traversal) { - traversals.add(traversal); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/TypePredicate.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/TypePredicate.java @@ -1,46 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.Statement; -import com.hp.hpl.jena.vocabulary.RDF; -import org.apache.commons.lang.builder.ToStringBuilder; - -public class TypePredicate implements Predicate { - - private final String namespace; - private final String localName; - - public TypePredicate(String namespace, String localName) { - this.namespace = namespace; - this.localName = localName; - } - - public String getNamespace() { - return namespace; - } - - public String getLocalName() { - return localName; - } - - @Override - public boolean evaluate(RDFNode node) { - if (!node.isResource()) { - throw new SelectorEvaluationException("Attempted to apply [type] to non-resource node " + node); - } - Resource resource = (Resource) node; - Resource type = resource.getModel().createResource(namespace + localName); - for (Statement statement: resource.listProperties(RDF.type).toSet()) { - if (statement.getObject().equals(type)) - return true; - } - return false; - } - - @Override - public String toString() { - return new ToStringBuilder(this).append(namespace).append(localName).toString(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/UnionSelector.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/UnionSelector.java @@ -1,45 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.apache.commons.lang.builder.ToStringBuilder; - -public class UnionSelector<T> extends AbstractSelector<T> { - - private final List<Selector<? extends T>> selectors; - - public UnionSelector(List<Selector<? extends T>> selectors) { - super(null); - this.selectors = selectors; - } - - @Override - public List<T> result(RDFNode node) { - LinkedHashSet<T> results = new LinkedHashSet<T>(); - for (Selector<? extends T> selector: selectors) { - results.addAll(selector.result(node)); - } - return new ArrayList<T>(results); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append(selectors).toString(); - } - - public List<Selector<? extends T>> getSelectors() { - return selectors; - } - - @Override - public <Other> Selector<Other> withResultType(Class<Other> otherType) { - for (Selector<? extends T> selector: selectors) { - selector.withResultType(otherType); // class cast exception? - } - return (Selector<Other>) this; - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/UriAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/UriAdaptation.java @@ -1,16 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.Resource; - -public class UriAdaptation extends AbstractAdaptation<String, Resource> { - - public UriAdaptation() { - super(String.class, new Class<?>[] { }, Resource.class); - } - - @Override - protected String doAdapt(Resource node) { - return node.getURI(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/UriAnchorAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/UriAnchorAdaptation.java @@ -1,24 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.Resource; - -/** - * Returns the anchor component of the node's URI (excluding initial #), or the - * empty string if it has no anchor component. - */ -public class UriAnchorAdaptation extends AbstractAdaptation<String, Resource> { - - public UriAnchorAdaptation() { - super(String.class, new Class<?>[] { }, Resource.class); - } - - @Override - protected String doAdapt(Resource node) { - String uri = node.getURI(); - int hashIndex = uri.lastIndexOf('#'); - if (hashIndex < 0) - return ""; - return uri.substring(hashIndex + 1); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/UriPrefixPredicate.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/UriPrefixPredicate.java @@ -1,26 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; - -public class UriPrefixPredicate implements Predicate { - - private final String prefix; - - public UriPrefixPredicate(String prefix) { - this.prefix = prefix; - } - - public String getPrefix() { - return prefix; - } - - @Override - public boolean evaluate(RDFNode node) { - if (!node.isResource()) { - throw new SelectorEvaluationException("Attempted to apply [uri-prefix] to non-resource node " + node); - } - return ((Resource) node).getURI().startsWith(prefix); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/UriSliceAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/UriSliceAdaptation.java @@ -1,28 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import com.hp.hpl.jena.rdf.model.Resource; - -public class UriSliceAdaptation extends AbstractAdaptation<String, Resource> { - - private Integer startIndex; - - public UriSliceAdaptation() { - super(String.class, new Class<?>[] { Integer.class }, Resource.class); - } - - public Integer getStartIndex() { - return startIndex; - } - - @Override - protected void setCheckedArgs(Object[] args) { - this.startIndex = (Integer) args[0]; - } - - @Override - protected String doAdapt(Resource node) { - String uri = node.getURI(); - return uri.substring(startIndex); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/view/RDFTemplateView.java b/src/main/java/au/com/miskinhill/rdftemplate/view/RDFTemplateView.java @@ -1,76 +0,0 @@ -package au.com.miskinhill.rdftemplate.view; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Locale; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.view.AbstractTemplateView; - -import au.com.miskinhill.rdftemplate.TemplateInterpolator; -import au.com.miskinhill.rdftemplate.selector.SelectorFactory; -import au.id.djc.jena.util.ModelOperations; -import au.id.djc.jena.util.ModelOperations.ModelExecutionCallbackWithoutResult; - -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.Resource; - -public class RDFTemplateView extends AbstractTemplateView { - - public static final String NODE_URI_KEY = "nodeUri"; - - private TemplateInterpolator templateInterpolator; - private SelectorFactory selectorFactory; - private ModelOperations modelOperations; - - public void setSelectorFactory(SelectorFactory selectorFactory) { - this.selectorFactory = selectorFactory; - } - - public void setModelOperations(ModelOperations modelOperations) { - this.modelOperations = modelOperations; - } - - @Override - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - if (selectorFactory == null) { - throw new IllegalArgumentException("Property 'selectorFactory' is required"); - } - if (modelOperations == null) { - throw new IllegalArgumentException("Property 'sdbTemplate' is required"); - } - this.templateInterpolator = new TemplateInterpolator(selectorFactory); - } - - @Override - protected void renderMergedTemplateModel(final Map<String, Object> model, - final HttpServletRequest request, final HttpServletResponse response) - throws Exception { - final InputStream inputStream = getApplicationContext().getResource(getUrl()).getInputStream(); - try { - modelOperations.withModel(new ModelExecutionCallbackWithoutResult() { - @Override - protected void executeWithoutResult(Model rdfModel) { - Resource node = rdfModel.getResource((String) model.get(NODE_URI_KEY)); - try { - templateInterpolator.interpolate(inputStream, node, response.getWriter()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - } finally { - inputStream.close(); - } - } - - @Override - public boolean checkResource(Locale locale) throws Exception { - return getApplicationContext().getResource(getUrl()).exists(); - } - -} diff --git a/src/main/java/au/com/miskinhill/rdftemplate/view/RDFTemplateViewResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/view/RDFTemplateViewResolver.java @@ -1,54 +0,0 @@ -package au.com.miskinhill.rdftemplate.view; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.web.servlet.view.AbstractTemplateView; -import org.springframework.web.servlet.view.AbstractTemplateViewResolver; - -import au.com.miskinhill.rdftemplate.selector.SelectorFactory; -import au.id.djc.jena.util.ModelOperations; - -public class RDFTemplateViewResolver extends AbstractTemplateViewResolver implements InitializingBean { - - private SelectorFactory selectorFactory; - private ModelOperations modelOperations; - - public RDFTemplateViewResolver() { - super(); - setViewClass(requiredViewClass()); - setExposeRequestAttributes(false); - setExposeSessionAttributes(false); - setExposeSpringMacroHelpers(false); - } - - public void setSelectorFactory(SelectorFactory selectorFactory) { - this.selectorFactory = selectorFactory; - } - - public void setModelOperations(ModelOperations modelOperations) { - this.modelOperations = modelOperations; - } - - @Override - public void afterPropertiesSet() throws Exception { - if (selectorFactory == null) { - throw new IllegalArgumentException("Property 'selectorFactory' is required"); - } - if (modelOperations == null) { - throw new IllegalArgumentException("Property 'modelOperations' is required"); - } - } - - @Override - protected Class<? extends AbstractTemplateView> requiredViewClass() { - return RDFTemplateView.class; - } - - @Override - protected RDFTemplateView buildView(String viewName) throws Exception { - RDFTemplateView view = (RDFTemplateView) super.buildView(viewName); - view.setSelectorFactory(selectorFactory); - view.setModelOperations(modelOperations); - return view; - } - -} diff --git a/src/main/java/au/id/djc/rdftemplate/ContentAction.java b/src/main/java/au/id/djc/rdftemplate/ContentAction.java @@ -0,0 +1,68 @@ +package au.id.djc.rdftemplate; + +import java.util.Set; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.util.XMLEventConsumer; + +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.ToStringBuilder; + +import au.id.djc.rdftemplate.selector.Selector; + +public class ContentAction extends TemplateAction { + + public static final String ACTION_NAME = "content"; + public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); + private static final QName XML_LANG_QNAME = new QName(XMLConstants.XML_NS_URI, "lang", XMLConstants.XML_NS_PREFIX); + private static final String XHTML_NS_URI = "http://www.w3.org/1999/xhtml"; + + private final StartElement start; + private final Selector<?> selector; + + public ContentAction(StartElement start, Selector<?> selector) { + this.start = start; + this.selector = selector; + } + + public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer, XMLEventFactory eventFactory) + throws XMLStreamException { + Object replacement = selector.singleResult(node); + StartElement start = interpolator.interpolateAttributes(this.start, node); + Set<Attribute> attributes = interpolator.cloneAttributesWithout(start, ACTION_QNAME); + if (replacement instanceof Literal) { + Literal literal = (Literal) replacement; + if (!StringUtils.isEmpty(literal.getLanguage())) { + attributes.add(eventFactory.createAttribute(XML_LANG_QNAME, ((Literal) replacement).getLanguage())); + if (start.getName().getNamespaceURI().equals(XHTML_NS_URI)) { + String xhtmlPrefixInContext = start.getNamespaceContext().getPrefix(XHTML_NS_URI); + QName xhtmlLangQNameForContext; // ugh + if (xhtmlPrefixInContext.isEmpty()) + xhtmlLangQNameForContext = new QName("lang"); + else + xhtmlLangQNameForContext = new QName(XHTML_NS_URI, "lang", xhtmlPrefixInContext); + attributes.add(eventFactory.createAttribute(xhtmlLangQNameForContext, literal.getLanguage())); + } + } + } + writer.add(eventFactory.createStartElement(start.getName(), attributes.iterator(), start.getNamespaces())); + interpolator.writeTreeForContent(writer, replacement); + writer.add(eventFactory.createEndElement(start.getName(), start.getNamespaces())); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("start", start) + .append("selector", selector) + .toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/ForAction.java b/src/main/java/au/id/djc/rdftemplate/ForAction.java @@ -0,0 +1,55 @@ +package au.id.djc.rdftemplate; + +import java.util.List; +import java.util.logging.Logger; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Seq; +import com.hp.hpl.jena.vocabulary.RDF; +import org.apache.commons.lang.builder.ToStringBuilder; + +import au.id.djc.rdftemplate.selector.Selector; + +public class ForAction extends TemplateAction { + + public static final String ACTION_NAME = "for"; + public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); + private static final Logger LOG = Logger.getLogger(ForAction.class.getName()); + + private final List<XMLEvent> tree; + private final Selector<RDFNode> selector; + + public ForAction(List<XMLEvent> tree, Selector<RDFNode> selector) { + this.tree = tree; + this.selector = selector; + } + + public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer) + throws XMLStreamException { + List<RDFNode> result = selector.result(node); + if (result.size() == 1 && result.get(0).canAs(Resource.class)) { + if (result.get(0).as(Resource.class).hasProperty(RDF.type, RDF.Seq)) { + LOG.fine("Apply rdf:Seq special case for " + result.get(0)); + result = result.get(0).as(Seq.class).iterator().toList(); + LOG.fine("Resulting sequence is " + result); + } + } + for (RDFNode eachNode: result) { + interpolator.interpolate(tree.iterator(), eachNode, writer); + } + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("selector", selector) + .toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/IfAction.java b/src/main/java/au/id/djc/rdftemplate/IfAction.java @@ -0,0 +1,46 @@ +package au.id.djc.rdftemplate; + +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.apache.commons.lang.builder.ToStringBuilder; + +import au.id.djc.rdftemplate.selector.Selector; + +public class IfAction extends TemplateAction { + + public static final String ACTION_NAME = "if"; + public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); + + private final List<XMLEvent> tree; + private final Selector<?> condition; + private final boolean negate; + + public IfAction(List<XMLEvent> tree, Selector<?> condition, boolean negate) { + this.tree = tree; + this.condition = condition; + this.negate = negate; + } + + public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer) + throws XMLStreamException { + List<?> selectorResult = condition.result(node); + if (negate ? selectorResult.isEmpty() : !selectorResult.isEmpty()) { + interpolator.interpolate(tree.iterator(), node, writer); + } + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("condition", condition) + .append("negate", negate) + .toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/JoinAction.java b/src/main/java/au/id/djc/rdftemplate/JoinAction.java @@ -0,0 +1,64 @@ +package au.id.djc.rdftemplate; + +import java.util.List; +import java.util.logging.Logger; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Seq; +import com.hp.hpl.jena.vocabulary.RDF; +import org.apache.commons.lang.builder.ToStringBuilder; + +import au.id.djc.rdftemplate.selector.Selector; + +public class JoinAction extends TemplateAction { + + public static final String ACTION_NAME = "join"; + public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME); + private static final Logger LOG = Logger.getLogger(JoinAction.class.getName()); + + private final List<XMLEvent> tree; + private final Selector<RDFNode> selector; + private final String separator; + + public JoinAction(List<XMLEvent> tree, Selector<RDFNode> selector, String separator) { + this.tree = tree; + this.selector = selector; + this.separator = separator; + } + + public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventConsumer writer, XMLEventFactory eventFactory) + throws XMLStreamException { + List<RDFNode> result = selector.result(node); + if (result.size() == 1 && result.get(0).canAs(Resource.class)) { + if (result.get(0).as(Resource.class).hasProperty(RDF.type, RDF.Seq)) { + LOG.fine("Apply rdf:Seq special case for " + result.get(0)); + result = result.get(0).as(Seq.class).iterator().toList(); + LOG.fine("Resulting sequence is " + result); + } + } + boolean first = true; + for (RDFNode eachNode: result) { + if (!first) { + writer.add(eventFactory.createCharacters(separator)); + } + interpolator.interpolate(tree.iterator(), eachNode, writer); + first = false; + } + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("selector", selector) + .append("separator", separator) + .toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/TemplateAction.java b/src/main/java/au/id/djc/rdftemplate/TemplateAction.java @@ -0,0 +1,5 @@ +package au.id.djc.rdftemplate; + +public abstract class TemplateAction { + +} diff --git a/src/main/java/au/id/djc/rdftemplate/TemplateInterpolationException.java b/src/main/java/au/id/djc/rdftemplate/TemplateInterpolationException.java @@ -0,0 +1,23 @@ +package au.id.djc.rdftemplate; + +import javax.xml.stream.Location; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public class TemplateInterpolationException extends RuntimeException { + + private static final long serialVersionUID = -1472104970210074672L; + + public TemplateInterpolationException(Location location, TemplateAction action, RDFNode node, Throwable cause) { + super("Exception evaluating action [" + action + "] " + + "at location [" + location.getLineNumber() + "," + location.getColumnNumber() + "] " + + "with context node " + node, cause); + } + + public TemplateInterpolationException(Location location, String selectorExpression, RDFNode node, Throwable cause) { + super("Exception evaluating selector expression [" + selectorExpression + "] " + + "at location [" + location.getLineNumber() + "," + location.getColumnNumber() + "] " + + "with context node " + node, cause); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/TemplateInterpolator.java b/src/main/java/au/id/djc/rdftemplate/TemplateInterpolator.java @@ -0,0 +1,431 @@ +package au.id.djc.rdftemplate; + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.RDFNode; + +import au.id.djc.rdftemplate.selector.InvalidSelectorSyntaxException; +import au.id.djc.rdftemplate.selector.Selector; +import au.id.djc.rdftemplate.selector.SelectorFactory; + +public class TemplateInterpolator { + + public static final String NS = "http://code.miskinhill.com.au/rdftemplate/"; + + private final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + private final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + private final XMLEventFactory eventFactory = XMLEventFactory.newInstance(); + + private final SelectorFactory selectorFactory; + + public TemplateInterpolator(SelectorFactory selectorFactory) { + this.selectorFactory = selectorFactory; + inputFactory.setProperty(XMLInputFactory.IS_COALESCING, true); + outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + } + + public String interpolate(Reader reader, RDFNode node) { + try { + StringWriter writer = new StringWriter(); + final XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); + XMLEventConsumer destination = new XMLEventConsumer() { + @Override + public void add(XMLEvent event) throws XMLStreamException { + eventWriter.add(event); + } + }; + interpolate(reader, node, destination); + return writer.toString(); + } catch (XMLStreamException e) { + throw new TemplateSyntaxException(e); + } + } + + @SuppressWarnings("unchecked") + public void interpolate(Reader reader, RDFNode node, XMLEventConsumer writer) { + try { + interpolate(inputFactory.createXMLEventReader(reader), node, writer); + } catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + public void interpolate(InputStream inputStream, RDFNode node, Writer writer) { + try { + final XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); + XMLEventConsumer destination = new XMLEventConsumer() { + @Override + public void add(XMLEvent event) throws XMLStreamException { + eventWriter.add(event); + } + }; + interpolate(inputFactory.createXMLEventReader(inputStream), node, destination); + } catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + public void interpolate(Reader reader, RDFNode node, final Collection<XMLEvent> destination) { + interpolate(reader, node, new XMLEventConsumer() { + @Override + public void add(XMLEvent event) { + destination.add(event); + } + }); + } + + public void interpolate(Iterator<XMLEvent> reader, RDFNode node, XMLEventConsumer writer) + throws XMLStreamException { + while (reader.hasNext()) { + XMLEvent event = reader.next(); + switch (event.getEventType()) { + case XMLStreamConstants.START_ELEMENT: { + StartElement start = (StartElement) event; + if (start.getName().equals(IfAction.ACTION_QNAME)) { + Attribute testAttribute = start.getAttributeByName(new QName("test")); + Attribute notAttribute = start.getAttributeByName(new QName("not")); + String condition; + boolean negate = false; + if (testAttribute != null && notAttribute != null) + throw new TemplateSyntaxException(start.getLocation(), "test and not attribute on rdf:if are mutually exclusive"); + else if (testAttribute != null) + condition = testAttribute.getValue(); + else if (notAttribute != null) { + condition = notAttribute.getValue(); + negate = true; + } else + throw new TemplateSyntaxException(start.getLocation(), "rdf:if must have a test attribute or a not attribute"); + Selector<?> conditionSelector; + try { + conditionSelector = selectorFactory.get(condition); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(start.getLocation(), e); + } + List<XMLEvent> tree = consumeTree(start, reader); + // discard enclosing rdf:if + tree.remove(tree.size() - 1); + tree.remove(0); + IfAction action = new IfAction(tree, conditionSelector, negate); + try { + action.evaluate(this, node, writer); + } catch (Exception e) { + throw new TemplateInterpolationException(start.getLocation(), action, node, e); + } + } else if (start.getName().equals(JoinAction.ACTION_QNAME)) { + Attribute eachAttribute = start.getAttributeByName(new QName("each")); + if (eachAttribute == null) + throw new TemplateSyntaxException(start.getLocation(), "rdf:join must have an each attribute"); + String separator = ""; + Attribute separatorAttribute = start.getAttributeByName(new QName("separator")); + if (separatorAttribute != null) + separator = separatorAttribute.getValue(); + Selector<RDFNode> selector; + try { + selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(start.getLocation(), e); + } + List<XMLEvent> events = consumeTree(start, reader); + // discard enclosing rdf:join + events.remove(events.size() - 1); + events.remove(0); + JoinAction action = new JoinAction(events, selector, separator); + try { + action.evaluate(this, node, writer, eventFactory); + } catch (Exception e) { + throw new TemplateInterpolationException(start.getLocation(), action, node, e); + } + } else if (start.getName().equals(ForAction.ACTION_QNAME)) { + Attribute eachAttribute = start.getAttributeByName(new QName("each")); + if (eachAttribute == null) + throw new TemplateSyntaxException(start.getLocation(), "rdf:for must have an each attribute"); + Selector<RDFNode> selector; + try { + selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(start.getLocation(), e); + } + List<XMLEvent> events = consumeTree(start, reader); + // discard enclosing rdf:for + events.remove(events.size() - 1); + events.remove(0); + ForAction action = new ForAction(events, selector); + try { + action.evaluate(this, node, writer); + } catch (Exception e) { + throw new TemplateInterpolationException(start.getLocation(), action, node, e); + } + } else { + Attribute ifAttribute = start.getAttributeByName(IfAction.ACTION_QNAME); + Attribute contentAttribute = start.getAttributeByName(ContentAction.ACTION_QNAME); + Attribute forAttribute = start.getAttributeByName(ForAction.ACTION_QNAME); + if (ifAttribute != null) { + Selector<?> selector; + try { + selector = selectorFactory.get(ifAttribute.getValue()); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(ifAttribute.getLocation(), e); + } + start = cloneStart(start, cloneAttributesWithout(start, IfAction.ACTION_QNAME), cloneNamespacesWithoutRdf(start)); + IfAction action = new IfAction(consumeTree(start, reader), selector, false); + action.evaluate(this, node, writer); + } else if (contentAttribute != null && forAttribute != null) { + throw new TemplateSyntaxException(start.getLocation(), "rdf:for and rdf:content cannot both be present on an element"); + } else if (contentAttribute != null) { + consumeTree(start, reader); // discard + Selector<?> selector; + try { + selector = selectorFactory.get(contentAttribute.getValue()); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(contentAttribute.getLocation(), e); + } + ContentAction action = new ContentAction(start, selector); + try { + action.evaluate(this, node, writer, eventFactory); + } catch (Exception e) { + throw new TemplateInterpolationException(contentAttribute.getLocation(), action, node, e); + } + } else if (forAttribute != null) { + Selector<RDFNode> selector; + try { + selector = selectorFactory.get(forAttribute.getValue()).withResultType(RDFNode.class); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(forAttribute.getLocation(), e); + } + start = cloneStart(start, cloneAttributesWithout(start, ForAction.ACTION_QNAME), cloneNamespacesWithoutRdf(start)); + List<XMLEvent> tree = consumeTree(start, reader); + ForAction action = new ForAction(tree, selector); + try { + action.evaluate(this, node, writer); + } catch (Exception e) { + throw new TemplateInterpolationException(forAttribute.getLocation(), action, node, e); + } + } else { + start = interpolateAttributes(start, node); + writer.add(start); + } + } + break; + } + case XMLStreamConstants.CHARACTERS: { + Characters characters = (Characters) event; + interpolateCharacters(writer, characters, node); + break; + } + case XMLStreamConstants.CDATA: { + Characters characters = (Characters) event; + interpolateCharacters(writer, characters, node); + break; + } + default: + writer.add(event); + } + } + } + + private List<XMLEvent> consumeTree(StartElement start, Iterator<XMLEvent> reader) throws XMLStreamException { + List<XMLEvent> events = new ArrayList<XMLEvent>(); + events.add(start); + Deque<QName> elementStack = new LinkedList<QName>(); + while (reader.hasNext()) { + XMLEvent event = reader.next(); + events.add(event); + switch (event.getEventType()) { + case XMLStreamConstants.START_ELEMENT: + elementStack.addLast(((StartElement) event).getName()); + break; + case XMLStreamConstants.END_ELEMENT: + if (elementStack.isEmpty()) { + return events; + } else { + if (!elementStack.removeLast().equals(((EndElement) event).getName())) + throw new IllegalStateException("End element mismatch"); + } + break; + default: + } + } + throw new IllegalStateException("Reader exhausted before end element found"); + } + + @SuppressWarnings("unchecked") + protected StartElement interpolateAttributes(StartElement start, RDFNode node) { + Set<Attribute> replacementAttributes = new LinkedHashSet<Attribute>(); + for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) { + Attribute attribute = it.next(); + String replacementValue = attribute.getValue(); + if (!attribute.getName().getNamespaceURI().equals(NS)) { // skip rdf: attributes + try { + replacementValue = interpolateString(attribute.getValue(), node); + } catch (Exception e) { + throw new TemplateInterpolationException(attribute.getLocation(), attribute.getValue(), node, e); + } + } + replacementAttributes.add(eventFactory.createAttribute(attribute.getName(), + replacementValue)); + } + return cloneStart(start, replacementAttributes, cloneNamespacesWithoutRdf(start)); + } + + private StartElement cloneStart(StartElement start, Iterable<Attribute> attributes, Iterable<Namespace> namespaces) { + return eventFactory.createStartElement( + start.getName().getPrefix(), + start.getName().getNamespaceURI(), + start.getName().getLocalPart(), + attributes.iterator(), + namespaces.iterator(), + start.getNamespaceContext()); + } + + @SuppressWarnings("unchecked") + private Set<Namespace> cloneNamespacesWithoutRdf(StartElement start) { + Set<Namespace> clonedNamespaces = new LinkedHashSet<Namespace>(); + for (Iterator<Namespace> it = start.getNamespaces(); it.hasNext(); ) { + Namespace namespace = it.next(); + if (!namespace.getNamespaceURI().equals(NS)) + clonedNamespaces.add(namespace); + } + return clonedNamespaces; + } + + private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{([^}]*)\\}"); + public String interpolateString(String template, RDFNode node) { + if (!SUBSTITUTION_PATTERN.matcher(template).find()) { + return template; // fast path + } + StringBuffer substituted = new StringBuffer(); + Matcher matcher = SUBSTITUTION_PATTERN.matcher(template); + while (matcher.find()) { + String expression = matcher.group(1); + Object replacement = selectorFactory.get(expression).singleResult(node); + + String replacementValue; + if (replacement instanceof RDFNode) { + RDFNode replacementNode = (RDFNode) replacement; + if (replacementNode.isLiteral()) { + Literal replacementLiteral = (Literal) replacementNode; + replacementValue = replacementLiteral.getValue().toString(); + } else { + throw new UnsupportedOperationException("Not a literal: " + replacementNode); + } + } else { + replacementValue = replacement.toString(); + } + + matcher.appendReplacement(substituted, replacementValue.replace("$", "\\$")); + } + matcher.appendTail(substituted); + return substituted.toString(); + } + + private void interpolateCharacters(XMLEventConsumer writer, Characters characters, RDFNode node) throws XMLStreamException { + String template = characters.getData(); + if (!SUBSTITUTION_PATTERN.matcher(template).find()) { + writer.add(characters); // fast path + return; + } + Matcher matcher = SUBSTITUTION_PATTERN.matcher(template); + int lastAppendedPos = 0; + while (matcher.find()) { + writer.add(eventFactory.createCharacters(template.substring(lastAppendedPos, matcher.start()))); + lastAppendedPos = matcher.end(); + String expression = matcher.group(1); + Selector<?> selector; + try { + selector = selectorFactory.get(expression); + } catch (InvalidSelectorSyntaxException e) { + throw new TemplateSyntaxException(characters.getLocation(), e); + } + try { + Object replacement = selector.singleResult(node); + writeTreeForContent(writer, replacement); + } catch (Exception e) { + throw new TemplateInterpolationException(characters.getLocation(), expression, node, e); + } + } + writer.add(eventFactory.createCharacters(template.substring(lastAppendedPos))); + } + + protected void writeTreeForContent(XMLEventConsumer writer, Object replacement) + throws XMLStreamException { + if (replacement instanceof RDFNode) { + RDFNode replacementNode = (RDFNode) replacement; + if (replacementNode.isLiteral()) { + Literal literal = (Literal) replacementNode; + if (literal.isWellFormedXML()) { + writeXMLLiteral(literal.getLexicalForm(), writer); + } else { + writer.add(eventFactory.createCharacters(literal.getValue().toString())); + } + } else { + throw new UnsupportedOperationException("Not a literal: " + replacementNode); + } + } else if (replacement instanceof XMLStream) { + for (XMLEvent event: (XMLStream) replacement) { + writer.add(event); + } + } else { + writer.add(eventFactory.createCharacters(replacement.toString())); + } + } + + @SuppressWarnings("unchecked") + protected Set<Attribute> cloneAttributesWithout(StartElement start, QName omit) { + // clone attributes, but without rdf:content + Set<Attribute> attributes = new LinkedHashSet<Attribute>(); + for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) { + Attribute attribute = it.next(); + if (!attribute.getName().equals(omit)) + attributes.add(attribute); + } + return attributes; + } + + private void writeXMLLiteral(String literal, XMLEventConsumer writer) + throws XMLStreamException { + XMLEventReader reader = inputFactory.createXMLEventReader(new StringReader(literal)); + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + switch (event.getEventType()) { + case XMLStreamConstants.START_DOCUMENT: + case XMLStreamConstants.END_DOCUMENT: + break; // discard + default: + writer.add(event); + } + } + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/TemplateSyntaxException.java b/src/main/java/au/id/djc/rdftemplate/TemplateSyntaxException.java @@ -0,0 +1,22 @@ +package au.id.djc.rdftemplate; + +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamException; + +public class TemplateSyntaxException extends RuntimeException { + + private static final long serialVersionUID = 6518982504570154030L; + + public TemplateSyntaxException(Location location, String message) { + super("[location " + location.getLineNumber() + "," + location.getColumnNumber() + "] " + message); + } + + public TemplateSyntaxException(Location location, Throwable cause) { + super("[location " + location.getLineNumber() + "," + location.getColumnNumber() + "]", cause); + } + + public TemplateSyntaxException(XMLStreamException e) { + super(e); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/XMLStream.java b/src/main/java/au/id/djc/rdftemplate/XMLStream.java @@ -0,0 +1,43 @@ +package au.id.djc.rdftemplate; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.events.XMLEvent; + +public class XMLStream implements Iterable<XMLEvent> { + + public static XMLStream collect(Iterator<XMLEvent> it) { + List<XMLEvent> events = new ArrayList<XMLEvent>(); + while (it.hasNext()) { + XMLEvent event = it.next(); + switch (event.getEventType()) { + case XMLStreamConstants.START_DOCUMENT: + case XMLStreamConstants.END_DOCUMENT: + break; // discard + default: + events.add(event); + } + } + return new XMLStream(events); + } + + private final List<XMLEvent> events; + + public XMLStream(XMLEvent... events) { + this.events = Arrays.asList(events); + } + + public XMLStream(List<XMLEvent> events) { + this.events = events; + } + + @Override + public Iterator<XMLEvent> iterator() { + return events.iterator(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/datatype/DateDataType.java b/src/main/java/au/id/djc/rdftemplate/datatype/DateDataType.java @@ -0,0 +1,112 @@ +package au.id.djc.rdftemplate.datatype; + +import org.springframework.stereotype.Component; + +import com.hp.hpl.jena.datatypes.DatatypeFormatException; +import com.hp.hpl.jena.datatypes.RDFDatatype; +import com.hp.hpl.jena.datatypes.TypeMapper; +import com.hp.hpl.jena.graph.impl.LiteralLabel; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +@Component +public class DateDataType implements RDFDatatype { + + public static final String URI = "http://www.w3.org/TR/xmlschema-2/#date"; + + @SuppressWarnings("unused") + private static DateDataType instance; + public static void registerStaticInstance() { + instance = new DateDataType(); + } + + private final DateTimeFormatter yearParser = DateTimeFormat.forPattern("yyyy"); + private final DateTimeFormatter yearMonthParser = DateTimeFormat.forPattern("yyyy-MM"); + private final DateTimeFormatter dateParser = DateTimeFormat.forPattern("yyyy-MM-dd"); + + public DateDataType() { + TypeMapper.getInstance().registerDatatype(this); + } + + @Override + public String getURI() { + return URI; + } + + @Override + public Class<LocalDate> getJavaClass() { + return null; + } + + @Override + public String unparse(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object cannonicalise(Object value) { + return value; + } + + @Override + public Object extendedTypeDefinition() { + return null; + } + + @Override + public int getHashCode(LiteralLabel lit) { + return lit.getValue().hashCode(); + } + + @Override + public boolean isEqual(LiteralLabel left, LiteralLabel right) { + return left.getValue().equals(right.getValue()); + } + + @Override + public Object parse(String lexicalForm) throws DatatypeFormatException { + try { + return dateParser.parseDateTime(lexicalForm).toLocalDate(); + } catch (IllegalArgumentException e) { + // pass + } + try { + return new YearMonth(yearMonthParser.parseDateTime(lexicalForm).toLocalDate()); + } catch (IllegalArgumentException e) { + // pass + } + try { + return new Year(yearParser.parseDateTime(lexicalForm).toLocalDate()); + } catch (IllegalArgumentException e) { + // pass + } + throw new DatatypeFormatException(lexicalForm, this, "No matching parsers found"); + } + + @Override + public boolean isValid(String lexicalForm) { + try { + parse(lexicalForm); + return true; + } catch (DatatypeFormatException e) { + return false; + } + } + + @Override + public boolean isValidLiteral(LiteralLabel lit) { + return lit.getDatatypeURI().equals(URI) && isValid(lit.getLexicalForm()); + } + + @Override + public boolean isValidValue(Object valueForm) { + return (valueForm instanceof LocalDate); + } + + @Override + public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) { + return dt; + } + +} +\ No newline at end of file diff --git a/src/main/java/au/id/djc/rdftemplate/datatype/DateTimeDataType.java b/src/main/java/au/id/djc/rdftemplate/datatype/DateTimeDataType.java @@ -0,0 +1,98 @@ +package au.id.djc.rdftemplate.datatype; + +import com.hp.hpl.jena.datatypes.DatatypeFormatException; +import com.hp.hpl.jena.datatypes.RDFDatatype; +import com.hp.hpl.jena.datatypes.TypeMapper; +import com.hp.hpl.jena.graph.impl.LiteralLabel; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; +import org.springframework.stereotype.Component; + +@Component +public class DateTimeDataType implements RDFDatatype { + + public static final String URI = "http://www.w3.org/TR/xmlschema-2/#datetime"; + + @SuppressWarnings("unused") + private static DateTimeDataType instance; + public static void registerStaticInstance() { + instance = new DateTimeDataType(); + } + + private final DateTimeFormatter format = ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed(); + + public DateTimeDataType() { + TypeMapper.getInstance().registerDatatype(this); + } + + @Override + public String getURI() { + return URI; + } + + @Override + public Class<DateTime> getJavaClass() { + return DateTime.class; + } + + @Override + public String unparse(Object value) { + return ((DateTime) value).toString(format); + } + + @Override + public Object cannonicalise(Object value) { + return value; + } + + @Override + public Object extendedTypeDefinition() { + return null; + } + + @Override + public int getHashCode(LiteralLabel lit) { + return lit.getValue().hashCode(); + } + + @Override + public boolean isEqual(LiteralLabel left, LiteralLabel right) { + return left.getValue().equals(right.getValue()); + } + + @Override + public DateTime parse(String lexicalForm) throws DatatypeFormatException { + try { + return format.parseDateTime(lexicalForm); + } catch (IllegalArgumentException e) { + throw new DatatypeFormatException(lexicalForm, this, "Parser barfed"); + } + } + + @Override + public boolean isValid(String lexicalForm) { + try { + parse(lexicalForm); + return true; + } catch (DatatypeFormatException e) { + return false; + } + } + + @Override + public boolean isValidLiteral(LiteralLabel lit) { + return lit.getDatatypeURI().equals(URI) && isValid(lit.getLexicalForm()); + } + + @Override + public boolean isValidValue(Object valueForm) { + return (valueForm instanceof DateTime); + } + + @Override + public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) { + return dt; + } + +} +\ No newline at end of file diff --git a/src/main/java/au/id/djc/rdftemplate/datatype/Year.java b/src/main/java/au/id/djc/rdftemplate/datatype/Year.java @@ -0,0 +1,50 @@ +package au.id.djc.rdftemplate.datatype; + +import org.joda.time.LocalDate; + +public class Year { + + private final int year; + + public Year(int value) { + this.year = value; + } + + public Year(LocalDate date) { + this.year = date.getYear(); + } + + public Year(YearMonth yearMonth) { + this.year = yearMonth.getYear(); + } + + public int getYear() { + return year; + } + + @Override + public String toString() { + return Integer.toString(year); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + year; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Year other = (Year) obj; + return (year == other.year); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/datatype/YearMonth.java b/src/main/java/au/id/djc/rdftemplate/datatype/YearMonth.java @@ -0,0 +1,58 @@ +package au.id.djc.rdftemplate.datatype; + +import org.joda.time.LocalDate; + +public class YearMonth { + + private final int year; + private final int month; + + public YearMonth(int year, int month) { + this.year = year; + this.month = month; + } + + public YearMonth(LocalDate date) { + this.year = date.getYear(); + this.month = date.getMonthOfYear(); + } + + public int getYear() { + return year; + } + + public int getMonth() { + return month; + } + + @Override + public String toString() { + return String.format("%04d-%02d", year, month); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + month; + result = prime * result + year; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + YearMonth other = (YearMonth) obj; + if (month != other.month) + return false; + if (year != other.year) + return false; + return true; + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/AbstractAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/AbstractAdaptation.java @@ -0,0 +1,58 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.Arrays; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public abstract class AbstractAdaptation<DestType, NodeType extends RDFNode> implements Adaptation<DestType> { + + private final Class<DestType> destinationType; + private final Class<?>[] argTypes; + private final Class<NodeType> nodeType; + + protected AbstractAdaptation(Class<DestType> destinationType, Class<?>[] argTypes, Class<NodeType> nodeType) { + this.destinationType = destinationType; + this.argTypes = argTypes; + this.nodeType = nodeType; + } + + @Override + public Class<DestType> getDestinationType() { + return destinationType; + } + + @Override + public Class<?>[] getArgTypes() { + return argTypes; + } + + @Override + public void setArgs(Object[] args) { + if (args.length != argTypes.length) + throw new SelectorEvaluationException("Expected args of types " + Arrays.toString(argTypes) + + " but invoked with " + Arrays.toString(args)); + for (int i = 0; i < args.length; i ++) { + if (!argTypes[i].isAssignableFrom(args[i].getClass())) + throw new SelectorEvaluationException("Arg " + i + ": expected type " + argTypes[i] + + " but was " + args[i].getClass()); + } + setCheckedArgs(args); + } + + protected void setCheckedArgs(Object[] args) { + throw new UnsupportedOperationException(); + } + + @Override + public DestType adapt(RDFNode node) { + if (!nodeType.equals(RDFNode.class)) { + if (!node.canAs(nodeType)) + throw new SelectorEvaluationException("Adaptation can only be applied to " + nodeType + + " but was applied to " + node); + } + return doAdapt(node.as(nodeType)); + } + + protected abstract DestType doAdapt(NodeType node); + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/AbstractSelector.java b/src/main/java/au/id/djc/rdftemplate/selector/AbstractSelector.java @@ -0,0 +1,40 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.List; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public abstract class AbstractSelector<T> implements Selector<T> { + + private final Class<T> resultType; + + protected AbstractSelector(Class<T> resultType) { + this.resultType = resultType; + } + + @Override + public abstract List<T> result(RDFNode node); + + @Override + public T singleResult(RDFNode node) { + List<T> results = result(node); + if (results.size() != 1) { + throw new SelectorEvaluationException("Expected exactly one result but got " + results); + } + return results.get(0); + } + + @Override + public Class<T> getResultType() { + return resultType; + } + + @Override + public <Other> Selector<Other> withResultType(Class<Other> otherType) { + if (!otherType.isAssignableFrom(resultType)) { + throw new ClassCastException("Result type " + resultType + " incompatible with requested type " + otherType); + } + return (Selector<Other>) this; + } + +} +\ No newline at end of file diff --git a/src/main/java/au/id/djc/rdftemplate/selector/Adaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/Adaptation.java @@ -0,0 +1,15 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public interface Adaptation<T> { + + Class<T> getDestinationType(); + + Class<?>[] getArgTypes(); + + void setArgs(Object[] args); + + T adapt(RDFNode node); + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/AdaptationFactory.java b/src/main/java/au/id/djc/rdftemplate/selector/AdaptationFactory.java @@ -0,0 +1,9 @@ +package au.id.djc.rdftemplate.selector; + +public interface AdaptationFactory { + + boolean hasName(String name); + + Adaptation<?> getByName(String name); + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/AntlrSelectorFactory.java b/src/main/java/au/id/djc/rdftemplate/selector/AntlrSelectorFactory.java @@ -0,0 +1,48 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.Collections; +import java.util.Map; + +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.CharStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.RecognitionException; + +public class AntlrSelectorFactory implements SelectorFactory { + + private AdaptationFactory adaptationFactory = new DefaultAdaptationFactory(); + private PredicateResolver predicateResolver = new DefaultPredicateResolver(); + private Map<String, String> namespacePrefixMap = Collections.emptyMap(); + + public AntlrSelectorFactory() { + } + + public void setAdaptationFactory(AdaptationFactory adaptationFactory) { + this.adaptationFactory = adaptationFactory; + } + + public void setPredicateResolver(PredicateResolver predicateResolver) { + this.predicateResolver = predicateResolver; + } + + public void setNamespacePrefixMap(Map<String, String> namespacePrefixMap) { + this.namespacePrefixMap = namespacePrefixMap; + } + + @Override + public Selector<?> get(String expression) { + CharStream stream = new ANTLRStringStream(expression); + SelectorLexer lexer = new SelectorLexer(stream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + SelectorParser parser = new SelectorParser(tokens); + parser.setAdaptationFactory(adaptationFactory); + parser.setPredicateResolver(predicateResolver); + parser.setNamespacePrefixMap(namespacePrefixMap); + try { + return parser.unionSelector(); + } catch (RecognitionException e) { + throw new InvalidSelectorSyntaxException(e); + } + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/BooleanAndPredicate.java b/src/main/java/au/id/djc/rdftemplate/selector/BooleanAndPredicate.java @@ -0,0 +1,34 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class BooleanAndPredicate implements Predicate { + + private final Predicate left; + private final Predicate right; + + public BooleanAndPredicate(Predicate left, Predicate right) { + this.left = left; + this.right = right; + } + + public Predicate getLeft() { + return left; + } + + public Predicate getRight() { + return right; + } + + @Override + public boolean evaluate(RDFNode node) { + return left.evaluate(node) && right.evaluate(node); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(left).append(right).toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/ComparableLiteralValueAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/ComparableLiteralValueAdaptation.java @@ -0,0 +1,22 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.Literal; + +@SuppressWarnings("unchecked") +public class ComparableLiteralValueAdaptation extends AbstractAdaptation<Comparable, Literal> { + + public ComparableLiteralValueAdaptation() { + super(Comparable.class, new Class<?>[] { }, Literal.class); + } + + @Override + protected Comparable<?> doAdapt(Literal node) { + Object literalValue = node.getValue(); + if (!(literalValue instanceof Comparable<?>)) { + throw new SelectorEvaluationException("Attempted to apply #comparable-lv to non-Comparable node " + node + + " with literal value of type " + literalValue.getClass()); + } + return (Comparable<?>) literalValue; + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/DefaultAdaptationFactory.java b/src/main/java/au/id/djc/rdftemplate/selector/DefaultAdaptationFactory.java @@ -0,0 +1,35 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.BeanUtils; + +public class DefaultAdaptationFactory implements AdaptationFactory { + + private static final Map<String, Class<? extends Adaptation<?>>> ADAPTATIONS = new HashMap<String, Class<? extends Adaptation<?>>>(); + static { + ADAPTATIONS.put("uri", UriAdaptation.class); + ADAPTATIONS.put("uri-slice", UriSliceAdaptation.class); + ADAPTATIONS.put("uri-anchor", UriAnchorAdaptation.class); + ADAPTATIONS.put("lv", LiteralValueAdaptation.class); + ADAPTATIONS.put("comparable-lv", ComparableLiteralValueAdaptation.class); + ADAPTATIONS.put("string-lv", StringLiteralValueAdaptation.class); + ADAPTATIONS.put("formatted-dt", FormattedDateTimeAdaptation.class); + } + + @Override + public boolean hasName(String name) { + return ADAPTATIONS.containsKey(name); + } + + @Override + public Adaptation<?> getByName(String name) { + Class<? extends Adaptation<?>> adaptationClass = ADAPTATIONS.get(name); + if (adaptationClass == null) { + throw new InvalidSelectorSyntaxException("No adaptation named " + name); + } + return BeanUtils.instantiate(adaptationClass); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/DefaultPredicateResolver.java b/src/main/java/au/id/djc/rdftemplate/selector/DefaultPredicateResolver.java @@ -0,0 +1,19 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.HashMap; +import java.util.Map; + +public class DefaultPredicateResolver implements PredicateResolver { + + private static final Map<String, Class<? extends Predicate>> PREDICATES = new HashMap<String, Class<? extends Predicate>>(); + static { + PREDICATES.put("type", TypePredicate.class); + PREDICATES.put("uri-prefix", UriPrefixPredicate.class); + } + + @Override + public Class<? extends Predicate> getByName(String name) { + return PREDICATES.get(name); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/EternallyCachingSelectorFactory.java b/src/main/java/au/id/djc/rdftemplate/selector/EternallyCachingSelectorFactory.java @@ -0,0 +1,35 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.HashMap; +import java.util.Map; + +/** + * {@link SelectorFactory} implementation which indirects to a real + * implementation and caches its return values eternally. Do not use in + * situations where the set of input expressions can be unbounded (e.g. + * user-provided) as this will lead to unbounded cache growth. + * <p> + * A better implementation would use a LRU cache or similar, but I cbf. + */ +public class EternallyCachingSelectorFactory implements SelectorFactory { + + private final SelectorFactory real; + private final Map<String, Selector<?>> cache = new HashMap<String, Selector<?>>(); + + public EternallyCachingSelectorFactory(SelectorFactory real) { + this.real = real; + } + + @Override + public Selector<?> get(String expression) { + Selector<?> cached = cache.get(expression); + if (cached == null) { + Selector<?> fresh = real.get(expression); + cache.put(expression, fresh); + return fresh; + } else { + return cached; + } + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/FormattedDateTimeAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/FormattedDateTimeAdaptation.java @@ -0,0 +1,49 @@ +package au.id.djc.rdftemplate.selector; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.joda.time.ReadableInstant; +import org.joda.time.ReadablePartial; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import com.hp.hpl.jena.rdf.model.Literal; + +public class FormattedDateTimeAdaptation extends AbstractAdaptation<String, Literal> { + + private String pattern; + private DateTimeFormatter formatter; + + public FormattedDateTimeAdaptation() { + super(String.class, new Class<?>[] { String.class }, Literal.class); + } + + @Override + protected void setCheckedArgs(Object[] args) { + this.pattern = (String) args[0]; + this.formatter = DateTimeFormat.forPattern(pattern.replace("\"", "'")); // for convenience in XML + } + + public String getPattern() { + return pattern; + } + + @Override + protected String doAdapt(Literal node) { + Object lv = node.getValue(); + if (lv instanceof ReadableInstant) { + ReadableInstant instant = (ReadableInstant) lv; + return formatter.print(instant); + } else if (lv instanceof ReadablePartial) { + ReadablePartial instant = (ReadablePartial) lv; + return formatter.print(instant); + } else { + throw new SelectorEvaluationException("Attempted to apply #formatted-dt to non-datetime literal " + lv); + } + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("pattern", pattern).toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/InvalidSelectorSyntaxException.java b/src/main/java/au/id/djc/rdftemplate/selector/InvalidSelectorSyntaxException.java @@ -0,0 +1,15 @@ +package au.id.djc.rdftemplate.selector; + +public class InvalidSelectorSyntaxException extends RuntimeException { + + private static final long serialVersionUID = 5805546105865617336L; + + public InvalidSelectorSyntaxException(Throwable cause) { + super(cause); + } + + public InvalidSelectorSyntaxException(String message) { + super(message); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/LiteralValueAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/LiteralValueAdaptation.java @@ -0,0 +1,16 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.Literal; + +public class LiteralValueAdaptation extends AbstractAdaptation<Object, Literal> { + + public LiteralValueAdaptation() { + super(Object.class, new Class<?>[] { }, Literal.class); + } + + @Override + protected Object doAdapt(Literal node) { + return node.getValue(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/NoopSelector.java b/src/main/java/au/id/djc/rdftemplate/selector/NoopSelector.java @@ -0,0 +1,19 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.Collections; +import java.util.List; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public class NoopSelector extends AbstractSelector<RDFNode> { + + public NoopSelector() { + super(RDFNode.class); + } + + @Override + public List<RDFNode> result(RDFNode node) { + return Collections.singletonList(node); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/Predicate.java b/src/main/java/au/id/djc/rdftemplate/selector/Predicate.java @@ -0,0 +1,7 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public interface Predicate extends org.apache.commons.collections15.Predicate<RDFNode> { + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/PredicateResolver.java b/src/main/java/au/id/djc/rdftemplate/selector/PredicateResolver.java @@ -0,0 +1,7 @@ +package au.id.djc.rdftemplate.selector; + +public interface PredicateResolver { + + Class<? extends Predicate> getByName(String name); + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/Selector.java b/src/main/java/au/id/djc/rdftemplate/selector/Selector.java @@ -0,0 +1,17 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.List; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public interface Selector<T> { + + List<T> result(RDFNode node); + + T singleResult(RDFNode node); + + Class<T> getResultType(); + + <Other> Selector<Other> withResultType(Class<Other> otherType); + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/SelectorComparator.java b/src/main/java/au/id/djc/rdftemplate/selector/SelectorComparator.java @@ -0,0 +1,58 @@ +/** + * + */ +package au.id.djc.rdftemplate.selector; + +import java.util.Comparator; + +import org.apache.commons.lang.builder.ToStringBuilder; + +import com.hp.hpl.jena.rdf.model.RDFNode; + +public class SelectorComparator<T extends Comparable<T>> implements Comparator<RDFNode> { + + private Selector<T> selector; + private boolean reversed = false; + + public Selector<T> getSelector() { + return selector; + } + + public void setSelector(Selector<T> selector) { + this.selector = selector; + } + + public boolean isReversed() { + return reversed; + } + + public void setReversed(boolean reversed) { + this.reversed = reversed; + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(selector).append("reversed", reversed).toString(); + } + + @Override + public int compare(RDFNode left, RDFNode right) { + T leftKey; + try { + leftKey = selector.singleResult(left); + } catch (SelectorEvaluationException e) { + throw new SelectorEvaluationException("Exception evaluating selector [" + selector + "] " + + "with context node [" + left + "] for comparison", e); + } + T rightKey; + try { + rightKey = selector.singleResult(right); + } catch (SelectorEvaluationException e) { + throw new SelectorEvaluationException("Exception evaluating selector [" + selector + "] " + + "with context node [" + right + "] for comparison", e); + } + int result = leftKey.compareTo(rightKey); + return reversed ? -result : result; + } + +} +\ No newline at end of file diff --git a/src/main/java/au/id/djc/rdftemplate/selector/SelectorEvaluationException.java b/src/main/java/au/id/djc/rdftemplate/selector/SelectorEvaluationException.java @@ -0,0 +1,15 @@ +package au.id.djc.rdftemplate.selector; + +public class SelectorEvaluationException extends RuntimeException { + + private static final long serialVersionUID = -398277800899471326L; + + public SelectorEvaluationException(String message) { + super(message); + } + + public SelectorEvaluationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/SelectorFactory.java b/src/main/java/au/id/djc/rdftemplate/selector/SelectorFactory.java @@ -0,0 +1,7 @@ +package au.id.djc.rdftemplate.selector; + +public interface SelectorFactory { + + Selector<?> get(String expression); + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/SelectorWithAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/SelectorWithAdaptation.java @@ -0,0 +1,48 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.ArrayList; +import java.util.List; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class SelectorWithAdaptation<T> extends AbstractSelector<T> { + + private final Selector<RDFNode> baseSelector; + private final Adaptation<T> adaptation; + + public SelectorWithAdaptation(Selector<RDFNode> baseSelector, Adaptation<T> adaptation) { + super(adaptation.getDestinationType()); + this.baseSelector = baseSelector; + this.adaptation = adaptation; + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(baseSelector).append(adaptation).toString(); + } + + @Override + public List<T> result(RDFNode node) { + List<RDFNode> baseResults = baseSelector.result(node); + List<T> results = new ArrayList<T>(); + for (RDFNode resultNode: baseResults) { + results.add(adaptation.adapt(resultNode)); + } + return results; + } + + @Override + public T singleResult(RDFNode node) { + return adaptation.adapt(baseSelector.singleResult(node)); + } + + public Selector<RDFNode> getBaseSelector() { + return baseSelector; + } + + public Adaptation<T> getAdaptation() { + return adaptation; + } + +} +\ No newline at end of file diff --git a/src/main/java/au/id/djc/rdftemplate/selector/StringLiteralValueAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/StringLiteralValueAdaptation.java @@ -0,0 +1,45 @@ +package au.id.djc.rdftemplate.selector; + +import java.io.StringReader; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; + +import com.hp.hpl.jena.rdf.model.Literal; + +public class StringLiteralValueAdaptation extends AbstractAdaptation<String, Literal> { + + private static final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + + public StringLiteralValueAdaptation() { + super(String.class, new Class<?>[] { }, Literal.class); + } + + @Override + protected String doAdapt(Literal literal) { + if (literal.isWellFormedXML()) { + try { + return stripTags(literal.getLexicalForm()); + } catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } else { + return literal.getValue().toString(); + } + } + + private String stripTags(String literal) throws XMLStreamException { + StringBuilder sb = new StringBuilder(); + XMLEventReader reader = inputFactory.createXMLEventReader(new StringReader(literal)); + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + if (event.isCharacters()) { + sb.append(event.asCharacters().getData()); + } + } + return sb.toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/Traversal.java b/src/main/java/au/id/djc/rdftemplate/selector/Traversal.java @@ -0,0 +1,126 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.collections15.CollectionUtils; + +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.ResIterator; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.StmtIterator; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class Traversal { + + private String propertyNamespace; + private String propertyLocalName; + private boolean inverse = false; + private Predicate predicate; + private List<Comparator<RDFNode>> sortOrder = new ArrayList<Comparator<RDFNode>>(); + private Integer subscript; + + private class SortComparator implements Comparator<RDFNode> { + @Override + public int compare(RDFNode left, RDFNode right) { + for (Comparator<RDFNode> comparator: sortOrder) { + int result = comparator.compare(left, right); + if (result != 0) + return result; + } + return 0; + } + } + + public List<RDFNode> traverse(RDFNode node) { + if (!node.isResource()) { + throw new SelectorEvaluationException("Attempted to traverse non-resource node " + node); + } + Resource resource = (Resource) node; + Property property = resource.getModel().createProperty(propertyNamespace, propertyLocalName); + List<RDFNode> destinations = new ArrayList<RDFNode>(); + if (!inverse) { + for (StmtIterator it = resource.listProperties(property); it.hasNext(); ) { + destinations.add(it.nextStatement().getObject()); + } + } else { + for (ResIterator it = resource.getModel().listResourcesWithProperty(property, node); it.hasNext(); ) { + destinations.add(it.nextResource()); + } + } + CollectionUtils.filter(destinations, predicate); + if (!sortOrder.isEmpty()) + Collections.sort(destinations, new SortComparator()); + if (subscript != null) { + if (destinations.size() <= subscript) { + throw new SelectorEvaluationException("Cannot apply subscript " + subscript + " to nodes " + destinations); + } + destinations = Collections.singletonList(destinations.get(subscript)); + } + return destinations; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("propertyNamespace", propertyNamespace) + .append("propertyLocalName", propertyLocalName) + .append("inverse", inverse) + .append("predicate", predicate) + .append("sortOrder", sortOrder) + .append("subscript", subscript) + .toString(); + } + + public String getPropertyLocalName() { + return propertyLocalName; + } + + public void setPropertyLocalName(String propertyLocalName) { + this.propertyLocalName = propertyLocalName; + } + + public String getPropertyNamespace() { + return propertyNamespace; + } + + public void setPropertyNamespace(String propertyNamespace) { + this.propertyNamespace = propertyNamespace; + } + + public boolean isInverse() { + return inverse; + } + + public void setInverse(boolean inverse) { + this.inverse = inverse; + } + + public Predicate getPredicate() { + return predicate; + } + + public void setPredicate(Predicate predicate) { + this.predicate = predicate; + } + + public List<Comparator<RDFNode>> getSortOrder() { + return sortOrder; + } + + public void addSortOrderComparator(Comparator<RDFNode> selector) { + this.sortOrder.add(selector); + } + + public Integer getSubscript() { + return subscript; + } + + public void setSubscript(Integer subscript) { + this.subscript = subscript; + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/TraversingSelector.java b/src/main/java/au/id/djc/rdftemplate/selector/TraversingSelector.java @@ -0,0 +1,46 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class TraversingSelector extends AbstractSelector<RDFNode> { + + private final List<Traversal> traversals = new ArrayList<Traversal>(); + + public TraversingSelector() { + super(RDFNode.class); + } + + @Override + public List<RDFNode> result(RDFNode node) { + Set<RDFNode> current = Collections.singleton(node); + for (Traversal traversal: traversals) { + LinkedHashSet<RDFNode> destinationsUnion = new LinkedHashSet<RDFNode>(); + for (RDFNode start: current) { + destinationsUnion.addAll(traversal.traverse(start)); + } + current = destinationsUnion; + } + return new ArrayList<RDFNode>(current); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(traversals).toString(); + } + + public List<Traversal> getTraversals() { + return traversals; + } + + public void addTraversal(Traversal traversal) { + traversals.add(traversal); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/TypePredicate.java b/src/main/java/au/id/djc/rdftemplate/selector/TypePredicate.java @@ -0,0 +1,46 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.vocabulary.RDF; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class TypePredicate implements Predicate { + + private final String namespace; + private final String localName; + + public TypePredicate(String namespace, String localName) { + this.namespace = namespace; + this.localName = localName; + } + + public String getNamespace() { + return namespace; + } + + public String getLocalName() { + return localName; + } + + @Override + public boolean evaluate(RDFNode node) { + if (!node.isResource()) { + throw new SelectorEvaluationException("Attempted to apply [type] to non-resource node " + node); + } + Resource resource = (Resource) node; + Resource type = resource.getModel().createResource(namespace + localName); + for (Statement statement: resource.listProperties(RDF.type).toSet()) { + if (statement.getObject().equals(type)) + return true; + } + return false; + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(namespace).append(localName).toString(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/UnionSelector.java b/src/main/java/au/id/djc/rdftemplate/selector/UnionSelector.java @@ -0,0 +1,45 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.apache.commons.lang.builder.ToStringBuilder; + +public class UnionSelector<T> extends AbstractSelector<T> { + + private final List<Selector<? extends T>> selectors; + + public UnionSelector(List<Selector<? extends T>> selectors) { + super(null); + this.selectors = selectors; + } + + @Override + public List<T> result(RDFNode node) { + LinkedHashSet<T> results = new LinkedHashSet<T>(); + for (Selector<? extends T> selector: selectors) { + results.addAll(selector.result(node)); + } + return new ArrayList<T>(results); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(selectors).toString(); + } + + public List<Selector<? extends T>> getSelectors() { + return selectors; + } + + @Override + public <Other> Selector<Other> withResultType(Class<Other> otherType) { + for (Selector<? extends T> selector: selectors) { + selector.withResultType(otherType); // class cast exception? + } + return (Selector<Other>) this; + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/UriAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/UriAdaptation.java @@ -0,0 +1,16 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.Resource; + +public class UriAdaptation extends AbstractAdaptation<String, Resource> { + + public UriAdaptation() { + super(String.class, new Class<?>[] { }, Resource.class); + } + + @Override + protected String doAdapt(Resource node) { + return node.getURI(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/UriAnchorAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/UriAnchorAdaptation.java @@ -0,0 +1,24 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.Resource; + +/** + * Returns the anchor component of the node's URI (excluding initial #), or the + * empty string if it has no anchor component. + */ +public class UriAnchorAdaptation extends AbstractAdaptation<String, Resource> { + + public UriAnchorAdaptation() { + super(String.class, new Class<?>[] { }, Resource.class); + } + + @Override + protected String doAdapt(Resource node) { + String uri = node.getURI(); + int hashIndex = uri.lastIndexOf('#'); + if (hashIndex < 0) + return ""; + return uri.substring(hashIndex + 1); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/UriPrefixPredicate.java b/src/main/java/au/id/djc/rdftemplate/selector/UriPrefixPredicate.java @@ -0,0 +1,26 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; + +public class UriPrefixPredicate implements Predicate { + + private final String prefix; + + public UriPrefixPredicate(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } + + @Override + public boolean evaluate(RDFNode node) { + if (!node.isResource()) { + throw new SelectorEvaluationException("Attempted to apply [uri-prefix] to non-resource node " + node); + } + return ((Resource) node).getURI().startsWith(prefix); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/selector/UriSliceAdaptation.java b/src/main/java/au/id/djc/rdftemplate/selector/UriSliceAdaptation.java @@ -0,0 +1,28 @@ +package au.id.djc.rdftemplate.selector; + +import com.hp.hpl.jena.rdf.model.Resource; + +public class UriSliceAdaptation extends AbstractAdaptation<String, Resource> { + + private Integer startIndex; + + public UriSliceAdaptation() { + super(String.class, new Class<?>[] { Integer.class }, Resource.class); + } + + public Integer getStartIndex() { + return startIndex; + } + + @Override + protected void setCheckedArgs(Object[] args) { + this.startIndex = (Integer) args[0]; + } + + @Override + protected String doAdapt(Resource node) { + String uri = node.getURI(); + return uri.substring(startIndex); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/view/RDFTemplateView.java b/src/main/java/au/id/djc/rdftemplate/view/RDFTemplateView.java @@ -0,0 +1,76 @@ +package au.id.djc.rdftemplate.view; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.servlet.view.AbstractTemplateView; + +import au.id.djc.rdftemplate.TemplateInterpolator; +import au.id.djc.rdftemplate.selector.SelectorFactory; +import au.id.djc.jena.util.ModelOperations; +import au.id.djc.jena.util.ModelOperations.ModelExecutionCallbackWithoutResult; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.Resource; + +public class RDFTemplateView extends AbstractTemplateView { + + public static final String NODE_URI_KEY = "nodeUri"; + + private TemplateInterpolator templateInterpolator; + private SelectorFactory selectorFactory; + private ModelOperations modelOperations; + + public void setSelectorFactory(SelectorFactory selectorFactory) { + this.selectorFactory = selectorFactory; + } + + public void setModelOperations(ModelOperations modelOperations) { + this.modelOperations = modelOperations; + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + if (selectorFactory == null) { + throw new IllegalArgumentException("Property 'selectorFactory' is required"); + } + if (modelOperations == null) { + throw new IllegalArgumentException("Property 'sdbTemplate' is required"); + } + this.templateInterpolator = new TemplateInterpolator(selectorFactory); + } + + @Override + protected void renderMergedTemplateModel(final Map<String, Object> model, + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + final InputStream inputStream = getApplicationContext().getResource(getUrl()).getInputStream(); + try { + modelOperations.withModel(new ModelExecutionCallbackWithoutResult() { + @Override + protected void executeWithoutResult(Model rdfModel) { + Resource node = rdfModel.getResource((String) model.get(NODE_URI_KEY)); + try { + templateInterpolator.interpolate(inputStream, node, response.getWriter()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + } finally { + inputStream.close(); + } + } + + @Override + public boolean checkResource(Locale locale) throws Exception { + return getApplicationContext().getResource(getUrl()).exists(); + } + +} diff --git a/src/main/java/au/id/djc/rdftemplate/view/RDFTemplateViewResolver.java b/src/main/java/au/id/djc/rdftemplate/view/RDFTemplateViewResolver.java @@ -0,0 +1,54 @@ +package au.id.djc.rdftemplate.view; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.servlet.view.AbstractTemplateView; +import org.springframework.web.servlet.view.AbstractTemplateViewResolver; + +import au.id.djc.rdftemplate.selector.SelectorFactory; +import au.id.djc.jena.util.ModelOperations; + +public class RDFTemplateViewResolver extends AbstractTemplateViewResolver implements InitializingBean { + + private SelectorFactory selectorFactory; + private ModelOperations modelOperations; + + public RDFTemplateViewResolver() { + super(); + setViewClass(requiredViewClass()); + setExposeRequestAttributes(false); + setExposeSessionAttributes(false); + setExposeSpringMacroHelpers(false); + } + + public void setSelectorFactory(SelectorFactory selectorFactory) { + this.selectorFactory = selectorFactory; + } + + public void setModelOperations(ModelOperations modelOperations) { + this.modelOperations = modelOperations; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (selectorFactory == null) { + throw new IllegalArgumentException("Property 'selectorFactory' is required"); + } + if (modelOperations == null) { + throw new IllegalArgumentException("Property 'modelOperations' is required"); + } + } + + @Override + protected Class<? extends AbstractTemplateView> requiredViewClass() { + return RDFTemplateView.class; + } + + @Override + protected RDFTemplateView buildView(String viewName) throws Exception { + RDFTemplateView view = (RDFTemplateView) super.buildView(viewName); + view.setSelectorFactory(selectorFactory); + view.setModelOperations(modelOperations); + return view; + } + +} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/TemplateInterpolatorUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/TemplateInterpolatorUnitTest.java @@ -1,140 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.junit.matchers.JUnitMatchers.*; - -import java.io.InputStream; -import java.io.InputStreamReader; - -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.Resource; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import au.com.miskinhill.rdftemplate.datatype.DateDataType; -import au.com.miskinhill.rdftemplate.selector.AntlrSelectorFactory; - -public class TemplateInterpolatorUnitTest { - - @BeforeClass - public static void ensureDatatypesRegistered() { - DateDataType.registerStaticInstance(); - } - - private Model model; - private TemplateInterpolator templateInterpolator; - - @Before - public void setUp() { - model = ModelFactory.createDefaultModel(); - InputStream stream = this.getClass().getResourceAsStream( - "/au/com/miskinhill/rdftemplate/test-data.xml"); - model.read(stream, ""); - AntlrSelectorFactory selectorFactory = new AntlrSelectorFactory(); - selectorFactory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); - templateInterpolator = new TemplateInterpolator(selectorFactory); - } - - @Test - public void shouldReplaceSubtreesWithContent() throws Exception { - Resource journal = model.getResource("http://miskinhill.com.au/journals/test/"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("replace-subtree.xml")), journal); - assertThat(result, containsString("<div xml:lang=\"en\" lang=\"en\">Test Journal of Good Stuff</div>")); - assertThat(result, not(containsString("<p>This should all go <em>away</em>!</p>"))); - } - - @Test - public void shouldHandleXMLLiterals() throws Exception { - Resource journal = model.getResource("http://miskinhill.com.au/journals/test/"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("replace-xml.xml")), journal); - assertThat(result, containsString( - "<div lang=\"en\"><p><em>Test Journal</em> is a journal.</p></div>")); - } - - @Test - public void shouldHandleIfs() throws Exception { - Resource author = model.getResource("http://miskinhill.com.au/authors/test-author"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("conditional.xml")), author); - assertThat(result, containsString("attribute test")); - assertThat(result, containsString("element test")); - assertThat(result, not(containsString("rdf:if"))); - assertThat(result, not(containsString("negated test"))); - - Resource authorWithoutNotes = model.getResource("http://miskinhill.com.au/authors/another-author"); - result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("conditional.xml")), authorWithoutNotes); - assertThat(result, not(containsString("attribute test"))); - assertThat(result, not(containsString("element test"))); - assertThat(result, containsString("negated test")); - } - - @Test - public void shouldHandleJoins() throws Exception { - Resource citedArticle = model.getResource("http://miskinhill.com.au/cited/journals/asdf/1:1/article"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("join.xml")), citedArticle); - assertThat(result, containsString("<p><a href=\"http://miskinhill.com.au/authors/another-author\">Another Author</a>, " + - "<a href=\"http://miskinhill.com.au/authors/test-author\">Test Author</a></p>")); - } - - @Test - public void shouldHandleFor() throws Exception { - Resource journal = model.getResource("http://miskinhill.com.au/journals/test/"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("for.xml")), journal); - assertThat(result, containsString("<span>http://miskinhill.com.au/journals/test/1:1/</span>")); - assertThat(result, containsString("<span>http://miskinhill.com.au/journals/test/2:1/</span>")); - assertThat(result, containsString("<p>http://miskinhill.com.au/journals/test/1:1/</p>")); - assertThat(result, containsString("<p>http://miskinhill.com.au/journals/test/2:1/</p>")); - } - - @Test - public void shouldStripRdfNamespaceDeclarations() throws Exception { - Resource author = model.getResource("http://miskinhill.com.au/authors/test-author"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("namespaces.xml")), author); - assertThat(result, not(containsString("xmlns:rdf=\"http://code.miskinhill.com.au/rdftemplate/\""))); - assertThat(result, not(containsString("rdf:"))); - } - - @Test - public void forShouldIterateRdfSeqsInOrder() throws Exception { - Resource article = model.getResource("http://miskinhill.com.au/journals/test/1:1/multi-author-article"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("for-seq.xml")), article); - assertThat(result, containsString("Another Author\n\nTest Author")); - } - - @Test - public void joinShouldIterateRdfSeqsInOrder() throws Exception { - Resource article = model.getResource("http://miskinhill.com.au/journals/test/1:1/multi-author-article"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("join-seq.xml")), article); - assertThat(result, containsString("<p><a href=\"http://miskinhill.com.au/authors/another-author\">Another Author</a>, " + - "<a href=\"http://miskinhill.com.au/authors/test-author\">Test Author</a></p>")); - } - - @Test - public void forShouldWorkForSingleResult() throws Exception { - Resource journal = model.getResource("http://miskinhill.com.au/cited/journals/asdf/"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("for.xml")), journal); - assertThat(result, containsString("<span>http://miskinhill.com.au/cited/journals/asdf/1:1/</span>")); - assertThat(result, containsString("<p>http://miskinhill.com.au/cited/journals/asdf/1:1/</p>")); - } - - @Test - public void joinShouldWorkForSingleResult() throws Exception { - Resource review = model.getResource("http://miskinhill.com.au/journals/test/1:1/reviews/review"); - String result = templateInterpolator.interpolate( - new InputStreamReader(this.getClass().getResourceAsStream("join.xml")), review); - assertThat(result, containsString("<p><a href=\"http://miskinhill.com.au/authors/test-author\">Test Author</a></p>")); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/TestNamespacePrefixMap.java b/src/test/java/au/com/miskinhill/rdftemplate/TestNamespacePrefixMap.java @@ -1,32 +0,0 @@ -package au.com.miskinhill.rdftemplate; - -import java.util.HashMap; - -import com.hp.hpl.jena.vocabulary.DCTerms; -import com.hp.hpl.jena.vocabulary.RDF; -import org.junit.Ignore; - -@Ignore // why does JUnit think this is a test? -public final class TestNamespacePrefixMap extends HashMap<String, String> { - - public static final String MHS_NS = "http://miskinhill.com.au/rdfschema/1.0/"; - public static final String FOAF_NS = "http://xmlns.com/foaf/0.1/"; - - private static final long serialVersionUID = 2119318190108418683L; - - private static final TestNamespacePrefixMap instance = new TestNamespacePrefixMap(); - public static TestNamespacePrefixMap getInstance() { - return instance; - } - - private TestNamespacePrefixMap() { - put("mhs", MHS_NS); - put("dc", DCTerms.NS); - put("foaf", FOAF_NS); - put("rdf", RDF.getURI()); - put("sioc", "http://rdfs.org/sioc/ns#"); - put("awol", "http://bblfish.net/work/atom-owl/2006-06-06/#"); - put("lingvoj", "http://www.lingvoj.org/ontology#"); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/datatype/DateDataTypeUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/datatype/DateDataTypeUnitTest.java @@ -1,35 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; - -import com.hp.hpl.jena.datatypes.RDFDatatype; -import org.joda.time.LocalDate; -import org.junit.Before; -import org.junit.Test; - -public class DateDataTypeUnitTest { - - private RDFDatatype type; - - @Before - public void setUp() { - type = new DateDataType(); - } - - @Test - public void shouldParseYear() { - assertThat((Year) type.parse("2003"), equalTo(new Year(2003))); - } - - @Test - public void shouldParseYearMonth() { - assertThat((YearMonth) type.parse("2003-05"), equalTo(new YearMonth(2003, 5))); - } - - @Test - public void shouldParseDate() { - assertThat((LocalDate) type.parse("2003-05-25"), equalTo(new LocalDate(2003, 5, 25))); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataTypeUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataTypeUnitTest.java @@ -1,34 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.junit.Before; -import org.junit.Test; - -import com.hp.hpl.jena.datatypes.RDFDatatype; - -public class DateTimeDataTypeUnitTest { - - private RDFDatatype type; - - @Before - public void setUp() { - type = new DateTimeDataType(); - } - - @Test - public void shouldParseDate() { - assertThat((DateTime) type.parse("2003-05-25T10:11:12+05:00"), - equalTo(new DateTime(2003, 5, 25, 10, 11, 12, 0, DateTimeZone.forOffsetHours(5)))); - } - - @Test - public void shouldUnparseDate() { - assertThat(type.unparse(new DateTime(2003, 5, 25, 10, 11, 12, 0, DateTimeZone.forOffsetHours(5))), - equalTo("2003-05-25T10:11:12+05:00")); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/datatype/YearMonthUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/datatype/YearMonthUnitTest.java @@ -1,24 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; - -import org.joda.time.LocalDate; -import org.junit.Test; - -public class YearMonthUnitTest { - - @Test - public void testToString() { - assertThat(new YearMonth(new LocalDate(2001, 5, 1)).toString(), equalTo("2001-05")); - } - - @Test - public void testEqualsHashCode() { - YearMonth yearMonth1 = new YearMonth(new LocalDate(2001, 5, 1)); - YearMonth yearMonth2 = new YearMonth(new LocalDate(2001, 5, 1)); - assertThat(yearMonth1, equalTo(yearMonth2)); - assertThat(yearMonth1.hashCode(), equalTo(yearMonth2.hashCode())); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/datatype/YearUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/datatype/YearUnitTest.java @@ -1,24 +0,0 @@ -package au.com.miskinhill.rdftemplate.datatype; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; - -import org.joda.time.LocalDate; -import org.junit.Test; - -public class YearUnitTest { - - @Test - public void testToString() { - assertThat(new Year(new LocalDate(2001, 1, 1)).toString(), equalTo("2001")); - } - - @Test - public void testEqualsHashCode() { - Year year1 = new Year(new LocalDate(2001, 1, 1)); - Year year2 = new Year(new LocalDate(2001, 1, 1)); - assertThat(year1, equalTo(year2)); - assertThat(year1.hashCode(), equalTo(year2.hashCode())); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java @@ -1,36 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - - -import static org.hamcrest.CoreMatchers.equalTo; - -public class AdaptationMatcher<T extends Adaptation<?>> extends BeanPropertyMatcher<T> { - - private AdaptationMatcher(Class<T> type) { - super(type); - } - - public static AdaptationMatcher<UriAdaptation> uriAdaptation() { - return new AdaptationMatcher<UriAdaptation>(UriAdaptation.class); - } - - public static AdaptationMatcher<UriSliceAdaptation> uriSliceAdaptation(Integer startIndex) { - AdaptationMatcher<UriSliceAdaptation> m = new AdaptationMatcher<UriSliceAdaptation>(UriSliceAdaptation.class); - m.addRequiredProperty("startIndex", equalTo(startIndex)); - return m; - } - - public static AdaptationMatcher<LiteralValueAdaptation> lvAdaptation() { - return new AdaptationMatcher<LiteralValueAdaptation>(LiteralValueAdaptation.class); - } - - public static AdaptationMatcher<ComparableLiteralValueAdaptation> comparableLVAdaptation() { - return new AdaptationMatcher<ComparableLiteralValueAdaptation>(ComparableLiteralValueAdaptation.class); - } - - public static AdaptationMatcher<FormattedDateTimeAdaptation> formattedDTAdaptation(String pattern) { - AdaptationMatcher<FormattedDateTimeAdaptation> m = new AdaptationMatcher<FormattedDateTimeAdaptation>(FormattedDateTimeAdaptation.class); - m.addRequiredProperty("pattern", equalTo(pattern)); - return m; - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/BeanPropertyMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/BeanPropertyMatcher.java @@ -1,56 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.commons.beanutils.PropertyUtils; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -public class BeanPropertyMatcher<T> extends BaseMatcher<T> { - - private final Class<? extends T> matchedType; - private final Map<String, Matcher<?>> requiredProperties = new LinkedHashMap<String, Matcher<?>>(); - - public BeanPropertyMatcher(Class<? extends T> type) { - this.matchedType = type; - } - - public void addRequiredProperty(String name, Matcher<?> matcher) { - requiredProperties.put(name, matcher); - } - - @Override - public boolean matches(Object arg0) { - if (!matchedType.isInstance(arg0)) - return false; - for (Map.Entry<String, Matcher<?>> property: requiredProperties.entrySet()) { - try { - Object beanProperty = PropertyUtils.getProperty(arg0, property.getKey()); - if (!property.getValue().matches(beanProperty)) - return false; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - return true; - } - - @Override - public void describeTo(Description desc) { - desc.appendText(matchedType.getName()); - desc.appendText("["); - boolean first = true; - for (Map.Entry<String, Matcher<?>> property: requiredProperties.entrySet()) { - if (!first) - desc.appendText(","); - desc.appendText(property.getKey()); - desc.appendText("="); - property.getValue().describeTo(desc); - first = false; - } - desc.appendText("]"); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java @@ -1,22 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.hamcrest.CoreMatchers.sameInstance; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -import au.com.miskinhill.rdftemplate.TestNamespacePrefixMap; - -public class EternallyCachingSelectorFactoryUnitTest { - - @Test - public void shouldCacheSelectors() { - AntlrSelectorFactory wrappedFactory = new AntlrSelectorFactory(); - wrappedFactory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); - EternallyCachingSelectorFactory factory = new EternallyCachingSelectorFactory(wrappedFactory); - Selector<?> first = factory.get("dc:creator/foaf:name"); - Selector<?> second = factory.get("dc:creator/foaf:name"); - assertThat((Selector) first, sameInstance((Selector) second)); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/PredicateMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/PredicateMatcher.java @@ -1,34 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.hamcrest.CoreMatchers.equalTo; - -import org.hamcrest.Matcher; - -public class PredicateMatcher<T extends Predicate> extends BeanPropertyMatcher<T> { - - private PredicateMatcher(Class<T> type) { - super(type); - } - - public static PredicateMatcher<UriPrefixPredicate> uriPrefixPredicate(String prefix) { - PredicateMatcher<UriPrefixPredicate> m = new PredicateMatcher<UriPrefixPredicate>(UriPrefixPredicate.class); - m.addRequiredProperty("prefix", equalTo(prefix)); - return m; - } - - public static PredicateMatcher<TypePredicate> typePredicate(String namespace, String localName) { - PredicateMatcher<TypePredicate> m = new PredicateMatcher<TypePredicate>(TypePredicate.class); - m.addRequiredProperty("namespace", equalTo(namespace)); - m.addRequiredProperty("localName", equalTo(localName)); - return m; - } - - public static PredicateMatcher<BooleanAndPredicate> booleanAndPredicate( - Matcher<? extends Predicate> left, Matcher<? extends Predicate> right) { - PredicateMatcher<BooleanAndPredicate> m = new PredicateMatcher<BooleanAndPredicate>(BooleanAndPredicate.class); - m.addRequiredProperty("left", left); - m.addRequiredProperty("right", right); - return m; - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorComparatorMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorComparatorMatcher.java @@ -1,25 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.hamcrest.CoreMatchers.equalTo; - -import org.hamcrest.Matcher; - -public class SelectorComparatorMatcher<T extends Comparable<T>> extends BeanPropertyMatcher<SelectorComparator<T>> { - - @SuppressWarnings("unchecked") - public SelectorComparatorMatcher() { - super((Class) SelectorComparator.class); - } - - public static SelectorComparatorMatcher<?> selectorComparator(Matcher<? extends Selector<?>> selector) { - SelectorComparatorMatcher<?> m = new SelectorComparatorMatcher(); - m.addRequiredProperty("selector", selector); - return m; - } - - public SelectorComparatorMatcher<T> reversed() { - addRequiredProperty("reversed", equalTo(true)); - return this; - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java @@ -1,224 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.junit.matchers.JUnitMatchers.*; - -import java.io.InputStream; -import java.util.List; - -import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import au.com.miskinhill.rdftemplate.TestNamespacePrefixMap; -import au.com.miskinhill.rdftemplate.datatype.DateDataType; -import au.com.miskinhill.rdftemplate.datatype.DateTimeDataType; - -public class SelectorEvaluationUnitTest { - - private Model m; - private Resource journal, issue, article, multiAuthorArticle, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru, forum; - private AntlrSelectorFactory selectorFactory; - - @BeforeClass - public static void ensureDatatypesRegistered() { - DateDataType.registerStaticInstance(); - DateTimeDataType.registerStaticInstance(); - } - - @Before - public void setUp() { - m = ModelFactory.createDefaultModel(); - InputStream stream = this.getClass().getResourceAsStream("/au/com/miskinhill/rdftemplate/test-data.xml"); - m.read(stream, ""); - journal = m.createResource("http://miskinhill.com.au/journals/test/"); - issue = m.createResource("http://miskinhill.com.au/journals/test/1:1/"); - article = m.createResource("http://miskinhill.com.au/journals/test/1:1/article"); - multiAuthorArticle = m.createResource("http://miskinhill.com.au/journals/test/1:1/multi-author-article"); - citedArticle = m.createResource("http://miskinhill.com.au/cited/journals/asdf/1:1/article"); - author = m.createResource("http://miskinhill.com.au/authors/test-author"); - anotherAuthor = m.createResource("http://miskinhill.com.au/authors/another-author"); - book = m.createResource("http://miskinhill.com.au/cited/books/test"); - review = m.createResource("http://miskinhill.com.au/journals/test/1:1/reviews/review"); - anotherReview = m.createResource("http://miskinhill.com.au/journals/test/2:1/reviews/another-review"); - obituary = m.createResource("http://miskinhill.com.au/journals/test/1:1/in-memoriam-john-doe"); - en = m.createResource("http://www.lingvoj.org/lang/en"); - ru = m.createResource("http://www.lingvoj.org/lang/ru"); - forum = m.createResource("http://miskinhill.com.au/"); - selectorFactory = new AntlrSelectorFactory(); - selectorFactory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); - } - - @Test - public void shouldEvaluateTraversal() { - RDFNode result = selectorFactory.get("dc:creator").withResultType(RDFNode.class).singleResult(article); - assertThat(result, equalTo((RDFNode) author)); - } - - @Test - public void shouldEvaluateMultipleTraversals() throws Exception { - RDFNode result = selectorFactory.get("dc:creator/foaf:name") - .withResultType(RDFNode.class).singleResult(article); - assertThat(((Literal) result).getString(), equalTo("Test Author")); - } - - @Test - public void shouldEvaluateInverseTraversal() throws Exception { - List<RDFNode> results = selectorFactory.get("!dc:isPartOf") - .withResultType(RDFNode.class).result(issue); - assertThat(results.size(), equalTo(4)); - assertThat(results, hasItems((RDFNode) article, (RDFNode) multiAuthorArticle, (RDFNode) review, (RDFNode) obituary)); - } - - @Test - public void shouldEvaluateSortOrder() throws Exception { - List<RDFNode> results = selectorFactory.get("dc:language(lingvoj:iso1#comparable-lv)") - .withResultType(RDFNode.class).result(journal); - assertThat(results.size(), equalTo(2)); - assertThat(results.get(0), equalTo((RDFNode) en)); - assertThat(results.get(1), equalTo((RDFNode) ru)); - } - - @Test - public void shouldEvaluateReverseSortOrder() throws Exception { - List<RDFNode> results = selectorFactory.get("dc:language(~lingvoj:iso1#comparable-lv)") - .withResultType(RDFNode.class).result(journal); - assertThat(results.size(), equalTo(2)); - assertThat(results.get(0), equalTo((RDFNode) ru)); - assertThat(results.get(1), equalTo((RDFNode) en)); - } - - @Test - public void shouldEvaluateComplexSortOrder() throws Exception { - List<RDFNode> results = selectorFactory.get("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)") - .withResultType(RDFNode.class).result(book); - assertThat(results.size(), equalTo(2)); - assertThat(results.get(0), equalTo((RDFNode) review)); - assertThat(results.get(1), equalTo((RDFNode) anotherReview)); - } - - @Test - public void shouldEvaluateUriAdaptation() throws Exception { - String result = selectorFactory.get("mhs:coverThumbnail#uri") - .withResultType(String.class).singleResult(issue); - assertThat(result, equalTo("http://miskinhill.com.au/journals/test/1:1/cover.thumb.jpg")); - } - - @Test - public void shouldEvaluateBareUriAdaptation() throws Exception { - String result = selectorFactory.get("#uri").withResultType(String.class).singleResult(journal); - assertThat(result, equalTo("http://miskinhill.com.au/journals/test/")); - } - - @Test - public void shouldEvaluateUriSliceAdaptation() throws Exception { - String result = selectorFactory.get("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)") - .withResultType(String.class).singleResult(journal); - assertThat(result, equalTo("12345678")); - } - - @Test - public void shouldEvaluateUriAnchorAdaptation() throws Exception { - String result = selectorFactory.get("mhs:inSection#uri-anchor") - .withResultType(String.class).singleResult(anotherReview); - assertThat(result, equalTo("stuff")); - } - - @Test - public void shouldEvaluateSubscript() throws Exception { - String result = selectorFactory.get( - "!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri") - .withResultType(String.class).singleResult(journal); - assertThat(result, equalTo("http://miskinhill.com.au/journals/test/2:1/cover.thumb.jpg")); - result = selectorFactory.get( - "!mhs:isIssueOf(mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri") - .withResultType(String.class).singleResult(journal); - assertThat(result, equalTo("http://miskinhill.com.au/journals/test/1:1/cover.thumb.jpg")); - } - - @Test - public void shouldEvaluateLVAdaptation() throws Exception { - List<Object> results = selectorFactory.get("dc:language/lingvoj:iso1#lv") - .withResultType(Object.class).result(journal); - assertThat(results.size(), equalTo(2)); - assertThat(results, hasItems((Object) "en", (Object) "ru")); - } - - @Test - public void shouldEvaluateTypePredicate() throws Exception { - List<RDFNode> results = selectorFactory.get("!dc:creator[type=mhs:Review]") - .withResultType(RDFNode.class).result(author); - assertThat(results.size(), equalTo(1)); - assertThat(results, hasItems((RDFNode) review)); - } - - @Test - public void shouldEvaluateAndCombinationOfPredicates() throws Exception { - List<RDFNode> results = selectorFactory.get("!dc:creator[type=mhs:Article and uri-prefix='http://miskinhill.com.au/journals/']") - .withResultType(RDFNode.class).result(author); - assertThat(results.size(), equalTo(2)); - assertThat(results, hasItems((RDFNode) article, (RDFNode) multiAuthorArticle)); - } - - @Test - public void shouldEvaluateUnion() throws Exception { - List<RDFNode> results = selectorFactory.get("!dc:creator | !mhs:translator") - .withResultType(RDFNode.class).result(anotherAuthor); - assertThat(results.size(), equalTo(4)); - assertThat(results, hasItems((RDFNode) article, (RDFNode) multiAuthorArticle, (RDFNode) citedArticle, (RDFNode) anotherReview)); - } - - @Test - public void shouldEvaluateMultipleSortSelectors() throws Exception { - List<RDFNode> results = selectorFactory.get("!dc:creator[uri-prefix='http://miskinhill.com.au/journals/']" + - "(~dc:isPartOf/mhs:publicationDate#comparable-lv,mhs:startPage#comparable-lv)") - .withResultType(RDFNode.class).result(author); - assertThat(results.size(), equalTo(4)); - assertThat(results.get(0), equalTo((RDFNode) obituary)); - assertThat(results.get(1), equalTo((RDFNode) article)); - assertThat(results.get(2), equalTo((RDFNode) multiAuthorArticle)); - assertThat(results.get(3), equalTo((RDFNode) review)); - } - - @Test - public void shouldEvaluateFormattedDTAdaptation() throws Exception { - String result = selectorFactory.get("!sioc:has_container/dc:created#formatted-dt('d MMMM yyyy')") - .withResultType(String.class).singleResult(forum); - assertThat(result, equalTo("15 June 2009")); - } - - @Test - public void shouldEvaluateFormattedDTAdaptationWithDoubleQuotes() throws Exception { - String result = selectorFactory.get("!sioc:has_container/dc:created#formatted-dt('yyyy-MM-dd\"T\"HH:mm:ssZZ')") - .withResultType(String.class).singleResult(forum); - assertThat(result, equalTo("2009-06-15T18:21:32+10:00")); - } - - @Test - public void shouldEvaluateStringLVAdaptation() throws Exception { - List<String> results = selectorFactory.get("dc:language/lingvoj:iso1#string-lv") - .withResultType(String.class).result(journal); - assertThat(results.size(), equalTo(2)); - assertThat(results, hasItems("en", "ru")); - } - - @Test - public void stringLVAdaptationShouldStripTagsFromXMLLiteral() throws Exception { - String result = selectorFactory.get("!sioc:has_container/sioc:content/awol:body#string-lv") - .withResultType(String.class).singleResult(forum); - assertEquals("To coincide with the publication of our second issue, " + - "the 2008 volume of Australian Slavonic and East European Studies, " + - "we are making available two new data feeds: an Atom feed of all " + - "journal issues published on this site, and the complete RDF dataset " + - "underlying the site. We hope this helps our users and aggregators " + - "to discover new content as it is published.", - result.trim().replaceAll("\\s+", " ")); - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorMatcher.java @@ -1,36 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.junit.matchers.JUnitMatchers.hasItems; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.hamcrest.Matcher; - -public class SelectorMatcher<T extends Selector<?>> extends BeanPropertyMatcher<T> { - - private SelectorMatcher(Class<? extends T> type) { - super(type); - } - - public static SelectorMatcher<Selector<RDFNode>> selector(Matcher<Traversal>... traversals) { - if (traversals.length == 0) { - return new SelectorMatcher<Selector<RDFNode>>(NoopSelector.class); - } - SelectorMatcher<Selector<RDFNode>> m = new SelectorMatcher<Selector<RDFNode>>(TraversingSelector.class); - m.addRequiredProperty("traversals", hasItems(traversals)); - return m; - } - - public static <R> SelectorMatcher<UnionSelector<R>> unionSelector(Matcher<Selector<R>>... selectors) { - SelectorMatcher<UnionSelector<R>> m = new SelectorMatcher(UnionSelector.class); - m.addRequiredProperty("selectors", hasItems(selectors)); - return m; - } - - public <A> SelectorMatcher<Selector<?>> withAdaptation(Matcher<? extends Adaptation<A>> adaptation) { - SelectorMatcher<Selector<?>> m = new SelectorMatcher(SelectorWithAdaptation.class); - m.addRequiredProperty("baseSelector", this); - m.addRequiredProperty("adaptation", adaptation); - return m; - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java @@ -1,206 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static au.com.miskinhill.rdftemplate.TestNamespacePrefixMap.*; -import static au.com.miskinhill.rdftemplate.selector.AdaptationMatcher.*; -import static au.com.miskinhill.rdftemplate.selector.PredicateMatcher.*; -import static au.com.miskinhill.rdftemplate.selector.SelectorComparatorMatcher.*; -import static au.com.miskinhill.rdftemplate.selector.SelectorMatcher.*; -import static au.com.miskinhill.rdftemplate.selector.TraversalMatcher.*; -import static org.junit.Assert.*; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.vocabulary.DCTerms; -import com.hp.hpl.jena.vocabulary.RDF; -import org.junit.Before; -import org.junit.Test; - -import au.com.miskinhill.rdftemplate.TestNamespacePrefixMap; - -public class SelectorParserUnitTest { - - private AntlrSelectorFactory factory; - - @Before - public void setUp() { - factory = new AntlrSelectorFactory(); - factory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); - } - - @Test - public void shouldRecogniseSingleTraversal() throws Exception { - Selector<RDFNode> selector = factory.get("dc:creator").withResultType(RDFNode.class); - assertThat(selector, selector(traversal(DCTerms.NS, "creator"))); - } - - @Test - public void shouldRecogniseMultipleTraversals() throws Exception { - Selector<RDFNode> selector = factory.get("dc:creator/foaf:name").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "creator"), - traversal(FOAF_NS, "name"))); - } - - @Test - public void shouldRecogniseMultipleTraversalsWithWhitespace() throws Exception { - Selector<RDFNode> selector = factory.get("dc:creator / foaf:name").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "creator"), - traversal(FOAF_NS, "name"))); - } - - @Test - public void shouldRecogniseInverseTraversal() throws Exception { - Selector<RDFNode> selector = factory.get("!dc:isPartOf/!dc:isPartOf").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "isPartOf").inverse(), - traversal(DCTerms.NS, "isPartOf").inverse())); - } - - @Test - public void shouldRecogniseSortOrder() throws Exception { - Selector<RDFNode> selector = factory.get("!mhs:isIssueOf(mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(MHS_NS, "isIssueOf").inverse() - .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) - .withAdaptation(comparableLVAdaptation()))))); - } - - @Test - public void shouldRecogniseReverseSortOrder() throws Exception { - Selector<RDFNode> selector = factory.get("!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(MHS_NS, "isIssueOf").inverse() - .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) - .withAdaptation(comparableLVAdaptation())).reversed()))); - } - - @Test - public void shouldRecogniseComplexSortOrder() throws Exception { - Selector<RDFNode> selector = factory.get("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(MHS_NS, "reviews") - .withSortOrder(selectorComparator(selector(traversal(DCTerms.NS, "isPartOf"), traversal(MHS_NS, "publicationDate")) - .withAdaptation(comparableLVAdaptation()))))); - } - - @Test - public void shouldRecogniseUriAdaptation() throws Exception { - Selector<?> selector = factory.get("mhs:coverThumbnail#uri"); - assertThat(selector, selector( - traversal(MHS_NS, "coverThumbnail")) - .withAdaptation(uriAdaptation())); - } - - @Test - public void shouldRecogniseBareUriAdaptation() throws Exception { - Selector<?> selector = factory.get("#uri"); - assertThat(selector, selector().withAdaptation(uriAdaptation())); - } - - @Test - public void shouldRecogniseUriSliceAdaptation() throws Exception { - Selector<?> selector = factory.get("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)"); - assertThat(selector, selector( - traversal(DCTerms.NS, "identifier") - .withPredicate(uriPrefixPredicate("urn:issn:"))) - .withAdaptation(uriSliceAdaptation(9))); - } - - @Test - public void shouldRecogniseUriPrefixPredicate() throws Exception { - Selector<RDFNode> selector = factory.get( - "!mhs:isIssueOf[uri-prefix='http://miskinhill.com.au/journals/'](~mhs:publicationDate#comparable-lv)") - .withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(MHS_NS, "isIssueOf") - .inverse() - .withPredicate(uriPrefixPredicate("http://miskinhill.com.au/journals/")) - .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) - .withAdaptation(comparableLVAdaptation())).reversed()))); - } - - @Test - public void shouldRecogniseSubscript() throws Exception { - Selector<String> selector = factory.get( - "!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri") - .withResultType(String.class); - assertThat(selector, selector( - traversal(MHS_NS, "isIssueOf") - .inverse() - .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) - .withAdaptation(comparableLVAdaptation())).reversed()) - .withSubscript(0), - traversal(MHS_NS, "coverThumbnail")) - .withAdaptation(uriAdaptation())); - } - - @Test - public void shouldRecogniseLVAdaptation() throws Exception { - Selector<Object> selector = factory.get("dc:language/lingvoj:iso1#lv").withResultType(Object.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "language"), - traversal("http://www.lingvoj.org/ontology#", "iso1")) - .withAdaptation(lvAdaptation())); - } - - @Test - public void shouldRecogniseTypePredicate() throws Exception { - Selector<RDFNode> selector = factory.get("!dc:creator[type=mhs:Review]").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "creator").inverse().withPredicate(typePredicate(MHS_NS, "Review")))); - } - - @Test - public void shouldRecogniseAndCombinationOfPredicates() throws Exception { - Selector<RDFNode> selector = factory.get("!dc:creator[type=mhs:Review and uri-prefix='http://miskinhill.com.au/journals/']").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "creator").inverse() - .withPredicate(booleanAndPredicate( - typePredicate(MHS_NS, "Review"), - uriPrefixPredicate("http://miskinhill.com.au/journals/"))))); - } - - @Test - public void shouldRecogniseUnion() throws Exception { - Selector<RDFNode> selector = factory.get("!dc:creator | !mhs:translator").withResultType(RDFNode.class); - assertThat((UnionSelector<RDFNode>) selector, unionSelector( - selector(traversal(DCTerms.NS, "creator").inverse()), - selector(traversal(MHS_NS, "translator").inverse()))); - } - - @Test - public void shouldRecogniseMultipleSortSelectors() throws Exception { - Selector<RDFNode> selector = factory.get("!dc:creator(~dc:isPartOf/mhs:publicationDate#comparable-lv,mhs:startPage#comparable-lv)").withResultType(RDFNode.class); - assertThat(selector, selector( - traversal(DCTerms.NS, "creator").inverse() - .withSortOrder( - selectorComparator(selector(traversal(DCTerms.NS, "isPartOf"), traversal(MHS_NS, "publicationDate")) - .withAdaptation(comparableLVAdaptation())).reversed(), - selectorComparator(selector(traversal(MHS_NS, "startPage")).withAdaptation(comparableLVAdaptation()))))); - } - - @Test - public void shouldRecogniseFormattedDTAdaptation() throws Exception { - Selector<?> selector = factory.get("dc:created#formatted-dt('d MMMM yyyy')"); - assertThat(selector, selector(traversal(DCTerms.NS, "created")) - .withAdaptation(formattedDTAdaptation("d MMMM yyyy"))); - } - - @Test - public void shouldRecogniseRdfType() throws Exception { - // was broken due to ANTLR being confused about the literal string "type" which was hardcoded to be a predicate - Selector<RDFNode> selector = factory.get("rdf:type").withResultType(RDFNode.class); - assertThat(selector, selector(traversal(RDF.getURI(), "type"))); - } - - @Test(expected = InvalidSelectorSyntaxException.class) - public void shouldThrowForInvalidSyntax() throws Exception { - factory.get("dc:creator]["); // this is a parser error - } - - @Test(expected = InvalidSelectorSyntaxException.class) - public void shouldThrowForUnrecognisedCharacter() throws Exception { - factory.get("dc:cre&ator"); // ... and this is a lexer error - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/TraversalMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/TraversalMatcher.java @@ -1,44 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.matchers.JUnitMatchers.hasItems; - -import java.util.Comparator; - -import com.hp.hpl.jena.rdf.model.RDFNode; -import org.hamcrest.Matcher; - -public class TraversalMatcher extends BeanPropertyMatcher<Traversal> { - - private TraversalMatcher() { - super(Traversal.class); - } - - public static TraversalMatcher traversal(String propertyNamespace, String propertyLocalName) { - TraversalMatcher m = new TraversalMatcher(); - m.addRequiredProperty("propertyNamespace", equalTo(propertyNamespace)); - m.addRequiredProperty("propertyLocalName", equalTo(propertyLocalName)); - return m; - } - - public TraversalMatcher inverse() { - addRequiredProperty("inverse", equalTo(true)); - return this; - } - - public TraversalMatcher withPredicate(Matcher<? extends Predicate> predicate) { - addRequiredProperty("predicate", predicate); - return this; - } - - public TraversalMatcher withSortOrder(Matcher<? extends Comparator<RDFNode>>... sortOrder) { - addRequiredProperty("sortOrder", hasItems(sortOrder)); - return this; - } - - public TraversalMatcher withSubscript(int subscript) { - addRequiredProperty("subscript", equalTo(subscript)); - return this; - } - -} diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/UriAnchorAdaptationUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/UriAnchorAdaptationUnitTest.java @@ -1,23 +0,0 @@ -package au.com.miskinhill.rdftemplate.selector; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import com.hp.hpl.jena.rdf.model.ResourceFactory; -import org.junit.Test; - -public class UriAnchorAdaptationUnitTest { - - @Test - public void shouldReturnAnchor() { - String result = new UriAnchorAdaptation().adapt(ResourceFactory.createResource("http://example.com/#asdf")); - assertThat(result, equalTo("asdf")); - } - - @Test - public void shouldReturnEmptyStringForUriWithoutAnchor() { - String result = new UriAnchorAdaptation().adapt(ResourceFactory.createResource("http://example.com/")); - assertThat(result, equalTo("")); - } - -} diff --git a/src/test/java/au/id/djc/rdftemplate/TemplateInterpolatorUnitTest.java b/src/test/java/au/id/djc/rdftemplate/TemplateInterpolatorUnitTest.java @@ -0,0 +1,140 @@ +package au.id.djc.rdftemplate; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.junit.matchers.JUnitMatchers.*; + +import java.io.InputStream; +import java.io.InputStreamReader; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Resource; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import au.id.djc.rdftemplate.datatype.DateDataType; +import au.id.djc.rdftemplate.selector.AntlrSelectorFactory; + +public class TemplateInterpolatorUnitTest { + + @BeforeClass + public static void ensureDatatypesRegistered() { + DateDataType.registerStaticInstance(); + } + + private Model model; + private TemplateInterpolator templateInterpolator; + + @Before + public void setUp() { + model = ModelFactory.createDefaultModel(); + InputStream stream = this.getClass().getResourceAsStream( + "/au/id/djc/rdftemplate/test-data.xml"); + model.read(stream, ""); + AntlrSelectorFactory selectorFactory = new AntlrSelectorFactory(); + selectorFactory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); + templateInterpolator = new TemplateInterpolator(selectorFactory); + } + + @Test + public void shouldReplaceSubtreesWithContent() throws Exception { + Resource journal = model.getResource("http://miskinhill.com.au/journals/test/"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("replace-subtree.xml")), journal); + assertThat(result, containsString("<div xml:lang=\"en\" lang=\"en\">Test Journal of Good Stuff</div>")); + assertThat(result, not(containsString("<p>This should all go <em>away</em>!</p>"))); + } + + @Test + public void shouldHandleXMLLiterals() throws Exception { + Resource journal = model.getResource("http://miskinhill.com.au/journals/test/"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("replace-xml.xml")), journal); + assertThat(result, containsString( + "<div lang=\"en\"><p><em>Test Journal</em> is a journal.</p></div>")); + } + + @Test + public void shouldHandleIfs() throws Exception { + Resource author = model.getResource("http://miskinhill.com.au/authors/test-author"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("conditional.xml")), author); + assertThat(result, containsString("attribute test")); + assertThat(result, containsString("element test")); + assertThat(result, not(containsString("rdf:if"))); + assertThat(result, not(containsString("negated test"))); + + Resource authorWithoutNotes = model.getResource("http://miskinhill.com.au/authors/another-author"); + result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("conditional.xml")), authorWithoutNotes); + assertThat(result, not(containsString("attribute test"))); + assertThat(result, not(containsString("element test"))); + assertThat(result, containsString("negated test")); + } + + @Test + public void shouldHandleJoins() throws Exception { + Resource citedArticle = model.getResource("http://miskinhill.com.au/cited/journals/asdf/1:1/article"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("join.xml")), citedArticle); + assertThat(result, containsString("<p><a href=\"http://miskinhill.com.au/authors/another-author\">Another Author</a>, " + + "<a href=\"http://miskinhill.com.au/authors/test-author\">Test Author</a></p>")); + } + + @Test + public void shouldHandleFor() throws Exception { + Resource journal = model.getResource("http://miskinhill.com.au/journals/test/"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("for.xml")), journal); + assertThat(result, containsString("<span>http://miskinhill.com.au/journals/test/1:1/</span>")); + assertThat(result, containsString("<span>http://miskinhill.com.au/journals/test/2:1/</span>")); + assertThat(result, containsString("<p>http://miskinhill.com.au/journals/test/1:1/</p>")); + assertThat(result, containsString("<p>http://miskinhill.com.au/journals/test/2:1/</p>")); + } + + @Test + public void shouldStripRdfNamespaceDeclarations() throws Exception { + Resource author = model.getResource("http://miskinhill.com.au/authors/test-author"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("namespaces.xml")), author); + assertThat(result, not(containsString("xmlns:rdf=\"http://code.miskinhill.com.au/rdftemplate/\""))); + assertThat(result, not(containsString("rdf:"))); + } + + @Test + public void forShouldIterateRdfSeqsInOrder() throws Exception { + Resource article = model.getResource("http://miskinhill.com.au/journals/test/1:1/multi-author-article"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("for-seq.xml")), article); + assertThat(result, containsString("Another Author\n\nTest Author")); + } + + @Test + public void joinShouldIterateRdfSeqsInOrder() throws Exception { + Resource article = model.getResource("http://miskinhill.com.au/journals/test/1:1/multi-author-article"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("join-seq.xml")), article); + assertThat(result, containsString("<p><a href=\"http://miskinhill.com.au/authors/another-author\">Another Author</a>, " + + "<a href=\"http://miskinhill.com.au/authors/test-author\">Test Author</a></p>")); + } + + @Test + public void forShouldWorkForSingleResult() throws Exception { + Resource journal = model.getResource("http://miskinhill.com.au/cited/journals/asdf/"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("for.xml")), journal); + assertThat(result, containsString("<span>http://miskinhill.com.au/cited/journals/asdf/1:1/</span>")); + assertThat(result, containsString("<p>http://miskinhill.com.au/cited/journals/asdf/1:1/</p>")); + } + + @Test + public void joinShouldWorkForSingleResult() throws Exception { + Resource review = model.getResource("http://miskinhill.com.au/journals/test/1:1/reviews/review"); + String result = templateInterpolator.interpolate( + new InputStreamReader(this.getClass().getResourceAsStream("join.xml")), review); + assertThat(result, containsString("<p><a href=\"http://miskinhill.com.au/authors/test-author\">Test Author</a></p>")); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/TestNamespacePrefixMap.java b/src/test/java/au/id/djc/rdftemplate/TestNamespacePrefixMap.java @@ -0,0 +1,32 @@ +package au.id.djc.rdftemplate; + +import java.util.HashMap; + +import com.hp.hpl.jena.vocabulary.DCTerms; +import com.hp.hpl.jena.vocabulary.RDF; +import org.junit.Ignore; + +@Ignore // why does JUnit think this is a test? +public final class TestNamespacePrefixMap extends HashMap<String, String> { + + public static final String MHS_NS = "http://miskinhill.com.au/rdfschema/1.0/"; + public static final String FOAF_NS = "http://xmlns.com/foaf/0.1/"; + + private static final long serialVersionUID = 2119318190108418683L; + + private static final TestNamespacePrefixMap instance = new TestNamespacePrefixMap(); + public static TestNamespacePrefixMap getInstance() { + return instance; + } + + private TestNamespacePrefixMap() { + put("mhs", MHS_NS); + put("dc", DCTerms.NS); + put("foaf", FOAF_NS); + put("rdf", RDF.getURI()); + put("sioc", "http://rdfs.org/sioc/ns#"); + put("awol", "http://bblfish.net/work/atom-owl/2006-06-06/#"); + put("lingvoj", "http://www.lingvoj.org/ontology#"); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/datatype/DateDataTypeUnitTest.java b/src/test/java/au/id/djc/rdftemplate/datatype/DateDataTypeUnitTest.java @@ -0,0 +1,35 @@ +package au.id.djc.rdftemplate.datatype; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import com.hp.hpl.jena.datatypes.RDFDatatype; +import org.joda.time.LocalDate; +import org.junit.Before; +import org.junit.Test; + +public class DateDataTypeUnitTest { + + private RDFDatatype type; + + @Before + public void setUp() { + type = new DateDataType(); + } + + @Test + public void shouldParseYear() { + assertThat((Year) type.parse("2003"), equalTo(new Year(2003))); + } + + @Test + public void shouldParseYearMonth() { + assertThat((YearMonth) type.parse("2003-05"), equalTo(new YearMonth(2003, 5))); + } + + @Test + public void shouldParseDate() { + assertThat((LocalDate) type.parse("2003-05-25"), equalTo(new LocalDate(2003, 5, 25))); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/datatype/DateTimeDataTypeUnitTest.java b/src/test/java/au/id/djc/rdftemplate/datatype/DateTimeDataTypeUnitTest.java @@ -0,0 +1,34 @@ +package au.id.djc.rdftemplate.datatype; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.junit.Before; +import org.junit.Test; + +import com.hp.hpl.jena.datatypes.RDFDatatype; + +public class DateTimeDataTypeUnitTest { + + private RDFDatatype type; + + @Before + public void setUp() { + type = new DateTimeDataType(); + } + + @Test + public void shouldParseDate() { + assertThat((DateTime) type.parse("2003-05-25T10:11:12+05:00"), + equalTo(new DateTime(2003, 5, 25, 10, 11, 12, 0, DateTimeZone.forOffsetHours(5)))); + } + + @Test + public void shouldUnparseDate() { + assertThat(type.unparse(new DateTime(2003, 5, 25, 10, 11, 12, 0, DateTimeZone.forOffsetHours(5))), + equalTo("2003-05-25T10:11:12+05:00")); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/datatype/YearMonthUnitTest.java b/src/test/java/au/id/djc/rdftemplate/datatype/YearMonthUnitTest.java @@ -0,0 +1,24 @@ +package au.id.djc.rdftemplate.datatype; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.joda.time.LocalDate; +import org.junit.Test; + +public class YearMonthUnitTest { + + @Test + public void testToString() { + assertThat(new YearMonth(new LocalDate(2001, 5, 1)).toString(), equalTo("2001-05")); + } + + @Test + public void testEqualsHashCode() { + YearMonth yearMonth1 = new YearMonth(new LocalDate(2001, 5, 1)); + YearMonth yearMonth2 = new YearMonth(new LocalDate(2001, 5, 1)); + assertThat(yearMonth1, equalTo(yearMonth2)); + assertThat(yearMonth1.hashCode(), equalTo(yearMonth2.hashCode())); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/datatype/YearUnitTest.java b/src/test/java/au/id/djc/rdftemplate/datatype/YearUnitTest.java @@ -0,0 +1,24 @@ +package au.id.djc.rdftemplate.datatype; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.joda.time.LocalDate; +import org.junit.Test; + +public class YearUnitTest { + + @Test + public void testToString() { + assertThat(new Year(new LocalDate(2001, 1, 1)).toString(), equalTo("2001")); + } + + @Test + public void testEqualsHashCode() { + Year year1 = new Year(new LocalDate(2001, 1, 1)); + Year year2 = new Year(new LocalDate(2001, 1, 1)); + assertThat(year1, equalTo(year2)); + assertThat(year1.hashCode(), equalTo(year2.hashCode())); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/AdaptationMatcher.java b/src/test/java/au/id/djc/rdftemplate/selector/AdaptationMatcher.java @@ -0,0 +1,36 @@ +package au.id.djc.rdftemplate.selector; + + +import static org.hamcrest.CoreMatchers.equalTo; + +public class AdaptationMatcher<T extends Adaptation<?>> extends BeanPropertyMatcher<T> { + + private AdaptationMatcher(Class<T> type) { + super(type); + } + + public static AdaptationMatcher<UriAdaptation> uriAdaptation() { + return new AdaptationMatcher<UriAdaptation>(UriAdaptation.class); + } + + public static AdaptationMatcher<UriSliceAdaptation> uriSliceAdaptation(Integer startIndex) { + AdaptationMatcher<UriSliceAdaptation> m = new AdaptationMatcher<UriSliceAdaptation>(UriSliceAdaptation.class); + m.addRequiredProperty("startIndex", equalTo(startIndex)); + return m; + } + + public static AdaptationMatcher<LiteralValueAdaptation> lvAdaptation() { + return new AdaptationMatcher<LiteralValueAdaptation>(LiteralValueAdaptation.class); + } + + public static AdaptationMatcher<ComparableLiteralValueAdaptation> comparableLVAdaptation() { + return new AdaptationMatcher<ComparableLiteralValueAdaptation>(ComparableLiteralValueAdaptation.class); + } + + public static AdaptationMatcher<FormattedDateTimeAdaptation> formattedDTAdaptation(String pattern) { + AdaptationMatcher<FormattedDateTimeAdaptation> m = new AdaptationMatcher<FormattedDateTimeAdaptation>(FormattedDateTimeAdaptation.class); + m.addRequiredProperty("pattern", equalTo(pattern)); + return m; + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/BeanPropertyMatcher.java b/src/test/java/au/id/djc/rdftemplate/selector/BeanPropertyMatcher.java @@ -0,0 +1,56 @@ +package au.id.djc.rdftemplate.selector; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.beanutils.PropertyUtils; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +public class BeanPropertyMatcher<T> extends BaseMatcher<T> { + + private final Class<? extends T> matchedType; + private final Map<String, Matcher<?>> requiredProperties = new LinkedHashMap<String, Matcher<?>>(); + + public BeanPropertyMatcher(Class<? extends T> type) { + this.matchedType = type; + } + + public void addRequiredProperty(String name, Matcher<?> matcher) { + requiredProperties.put(name, matcher); + } + + @Override + public boolean matches(Object arg0) { + if (!matchedType.isInstance(arg0)) + return false; + for (Map.Entry<String, Matcher<?>> property: requiredProperties.entrySet()) { + try { + Object beanProperty = PropertyUtils.getProperty(arg0, property.getKey()); + if (!property.getValue().matches(beanProperty)) + return false; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return true; + } + + @Override + public void describeTo(Description desc) { + desc.appendText(matchedType.getName()); + desc.appendText("["); + boolean first = true; + for (Map.Entry<String, Matcher<?>> property: requiredProperties.entrySet()) { + if (!first) + desc.appendText(","); + desc.appendText(property.getKey()); + desc.appendText("="); + property.getValue().describeTo(desc); + first = false; + } + desc.appendText("]"); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java b/src/test/java/au/id/djc/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java @@ -0,0 +1,22 @@ +package au.id.djc.rdftemplate.selector; + +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import au.id.djc.rdftemplate.TestNamespacePrefixMap; + +public class EternallyCachingSelectorFactoryUnitTest { + + @Test + public void shouldCacheSelectors() { + AntlrSelectorFactory wrappedFactory = new AntlrSelectorFactory(); + wrappedFactory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); + EternallyCachingSelectorFactory factory = new EternallyCachingSelectorFactory(wrappedFactory); + Selector<?> first = factory.get("dc:creator/foaf:name"); + Selector<?> second = factory.get("dc:creator/foaf:name"); + assertThat((Selector) first, sameInstance((Selector) second)); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/PredicateMatcher.java b/src/test/java/au/id/djc/rdftemplate/selector/PredicateMatcher.java @@ -0,0 +1,34 @@ +package au.id.djc.rdftemplate.selector; + +import static org.hamcrest.CoreMatchers.equalTo; + +import org.hamcrest.Matcher; + +public class PredicateMatcher<T extends Predicate> extends BeanPropertyMatcher<T> { + + private PredicateMatcher(Class<T> type) { + super(type); + } + + public static PredicateMatcher<UriPrefixPredicate> uriPrefixPredicate(String prefix) { + PredicateMatcher<UriPrefixPredicate> m = new PredicateMatcher<UriPrefixPredicate>(UriPrefixPredicate.class); + m.addRequiredProperty("prefix", equalTo(prefix)); + return m; + } + + public static PredicateMatcher<TypePredicate> typePredicate(String namespace, String localName) { + PredicateMatcher<TypePredicate> m = new PredicateMatcher<TypePredicate>(TypePredicate.class); + m.addRequiredProperty("namespace", equalTo(namespace)); + m.addRequiredProperty("localName", equalTo(localName)); + return m; + } + + public static PredicateMatcher<BooleanAndPredicate> booleanAndPredicate( + Matcher<? extends Predicate> left, Matcher<? extends Predicate> right) { + PredicateMatcher<BooleanAndPredicate> m = new PredicateMatcher<BooleanAndPredicate>(BooleanAndPredicate.class); + m.addRequiredProperty("left", left); + m.addRequiredProperty("right", right); + return m; + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/SelectorComparatorMatcher.java b/src/test/java/au/id/djc/rdftemplate/selector/SelectorComparatorMatcher.java @@ -0,0 +1,25 @@ +package au.id.djc.rdftemplate.selector; + +import static org.hamcrest.CoreMatchers.equalTo; + +import org.hamcrest.Matcher; + +public class SelectorComparatorMatcher<T extends Comparable<T>> extends BeanPropertyMatcher<SelectorComparator<T>> { + + @SuppressWarnings("unchecked") + public SelectorComparatorMatcher() { + super((Class) SelectorComparator.class); + } + + public static SelectorComparatorMatcher<?> selectorComparator(Matcher<? extends Selector<?>> selector) { + SelectorComparatorMatcher<?> m = new SelectorComparatorMatcher(); + m.addRequiredProperty("selector", selector); + return m; + } + + public SelectorComparatorMatcher<T> reversed() { + addRequiredProperty("reversed", equalTo(true)); + return this; + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/SelectorEvaluationUnitTest.java b/src/test/java/au/id/djc/rdftemplate/selector/SelectorEvaluationUnitTest.java @@ -0,0 +1,224 @@ +package au.id.djc.rdftemplate.selector; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.junit.matchers.JUnitMatchers.*; + +import java.io.InputStream; +import java.util.List; + +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import au.id.djc.rdftemplate.TestNamespacePrefixMap; +import au.id.djc.rdftemplate.datatype.DateDataType; +import au.id.djc.rdftemplate.datatype.DateTimeDataType; + +public class SelectorEvaluationUnitTest { + + private Model m; + private Resource journal, issue, article, multiAuthorArticle, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru, forum; + private AntlrSelectorFactory selectorFactory; + + @BeforeClass + public static void ensureDatatypesRegistered() { + DateDataType.registerStaticInstance(); + DateTimeDataType.registerStaticInstance(); + } + + @Before + public void setUp() { + m = ModelFactory.createDefaultModel(); + InputStream stream = this.getClass().getResourceAsStream("/au/id/djc/rdftemplate/test-data.xml"); + m.read(stream, ""); + journal = m.createResource("http://miskinhill.com.au/journals/test/"); + issue = m.createResource("http://miskinhill.com.au/journals/test/1:1/"); + article = m.createResource("http://miskinhill.com.au/journals/test/1:1/article"); + multiAuthorArticle = m.createResource("http://miskinhill.com.au/journals/test/1:1/multi-author-article"); + citedArticle = m.createResource("http://miskinhill.com.au/cited/journals/asdf/1:1/article"); + author = m.createResource("http://miskinhill.com.au/authors/test-author"); + anotherAuthor = m.createResource("http://miskinhill.com.au/authors/another-author"); + book = m.createResource("http://miskinhill.com.au/cited/books/test"); + review = m.createResource("http://miskinhill.com.au/journals/test/1:1/reviews/review"); + anotherReview = m.createResource("http://miskinhill.com.au/journals/test/2:1/reviews/another-review"); + obituary = m.createResource("http://miskinhill.com.au/journals/test/1:1/in-memoriam-john-doe"); + en = m.createResource("http://www.lingvoj.org/lang/en"); + ru = m.createResource("http://www.lingvoj.org/lang/ru"); + forum = m.createResource("http://miskinhill.com.au/"); + selectorFactory = new AntlrSelectorFactory(); + selectorFactory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); + } + + @Test + public void shouldEvaluateTraversal() { + RDFNode result = selectorFactory.get("dc:creator").withResultType(RDFNode.class).singleResult(article); + assertThat(result, equalTo((RDFNode) author)); + } + + @Test + public void shouldEvaluateMultipleTraversals() throws Exception { + RDFNode result = selectorFactory.get("dc:creator/foaf:name") + .withResultType(RDFNode.class).singleResult(article); + assertThat(((Literal) result).getString(), equalTo("Test Author")); + } + + @Test + public void shouldEvaluateInverseTraversal() throws Exception { + List<RDFNode> results = selectorFactory.get("!dc:isPartOf") + .withResultType(RDFNode.class).result(issue); + assertThat(results.size(), equalTo(4)); + assertThat(results, hasItems((RDFNode) article, (RDFNode) multiAuthorArticle, (RDFNode) review, (RDFNode) obituary)); + } + + @Test + public void shouldEvaluateSortOrder() throws Exception { + List<RDFNode> results = selectorFactory.get("dc:language(lingvoj:iso1#comparable-lv)") + .withResultType(RDFNode.class).result(journal); + assertThat(results.size(), equalTo(2)); + assertThat(results.get(0), equalTo((RDFNode) en)); + assertThat(results.get(1), equalTo((RDFNode) ru)); + } + + @Test + public void shouldEvaluateReverseSortOrder() throws Exception { + List<RDFNode> results = selectorFactory.get("dc:language(~lingvoj:iso1#comparable-lv)") + .withResultType(RDFNode.class).result(journal); + assertThat(results.size(), equalTo(2)); + assertThat(results.get(0), equalTo((RDFNode) ru)); + assertThat(results.get(1), equalTo((RDFNode) en)); + } + + @Test + public void shouldEvaluateComplexSortOrder() throws Exception { + List<RDFNode> results = selectorFactory.get("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)") + .withResultType(RDFNode.class).result(book); + assertThat(results.size(), equalTo(2)); + assertThat(results.get(0), equalTo((RDFNode) review)); + assertThat(results.get(1), equalTo((RDFNode) anotherReview)); + } + + @Test + public void shouldEvaluateUriAdaptation() throws Exception { + String result = selectorFactory.get("mhs:coverThumbnail#uri") + .withResultType(String.class).singleResult(issue); + assertThat(result, equalTo("http://miskinhill.com.au/journals/test/1:1/cover.thumb.jpg")); + } + + @Test + public void shouldEvaluateBareUriAdaptation() throws Exception { + String result = selectorFactory.get("#uri").withResultType(String.class).singleResult(journal); + assertThat(result, equalTo("http://miskinhill.com.au/journals/test/")); + } + + @Test + public void shouldEvaluateUriSliceAdaptation() throws Exception { + String result = selectorFactory.get("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)") + .withResultType(String.class).singleResult(journal); + assertThat(result, equalTo("12345678")); + } + + @Test + public void shouldEvaluateUriAnchorAdaptation() throws Exception { + String result = selectorFactory.get("mhs:inSection#uri-anchor") + .withResultType(String.class).singleResult(anotherReview); + assertThat(result, equalTo("stuff")); + } + + @Test + public void shouldEvaluateSubscript() throws Exception { + String result = selectorFactory.get( + "!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri") + .withResultType(String.class).singleResult(journal); + assertThat(result, equalTo("http://miskinhill.com.au/journals/test/2:1/cover.thumb.jpg")); + result = selectorFactory.get( + "!mhs:isIssueOf(mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri") + .withResultType(String.class).singleResult(journal); + assertThat(result, equalTo("http://miskinhill.com.au/journals/test/1:1/cover.thumb.jpg")); + } + + @Test + public void shouldEvaluateLVAdaptation() throws Exception { + List<Object> results = selectorFactory.get("dc:language/lingvoj:iso1#lv") + .withResultType(Object.class).result(journal); + assertThat(results.size(), equalTo(2)); + assertThat(results, hasItems((Object) "en", (Object) "ru")); + } + + @Test + public void shouldEvaluateTypePredicate() throws Exception { + List<RDFNode> results = selectorFactory.get("!dc:creator[type=mhs:Review]") + .withResultType(RDFNode.class).result(author); + assertThat(results.size(), equalTo(1)); + assertThat(results, hasItems((RDFNode) review)); + } + + @Test + public void shouldEvaluateAndCombinationOfPredicates() throws Exception { + List<RDFNode> results = selectorFactory.get("!dc:creator[type=mhs:Article and uri-prefix='http://miskinhill.com.au/journals/']") + .withResultType(RDFNode.class).result(author); + assertThat(results.size(), equalTo(2)); + assertThat(results, hasItems((RDFNode) article, (RDFNode) multiAuthorArticle)); + } + + @Test + public void shouldEvaluateUnion() throws Exception { + List<RDFNode> results = selectorFactory.get("!dc:creator | !mhs:translator") + .withResultType(RDFNode.class).result(anotherAuthor); + assertThat(results.size(), equalTo(4)); + assertThat(results, hasItems((RDFNode) article, (RDFNode) multiAuthorArticle, (RDFNode) citedArticle, (RDFNode) anotherReview)); + } + + @Test + public void shouldEvaluateMultipleSortSelectors() throws Exception { + List<RDFNode> results = selectorFactory.get("!dc:creator[uri-prefix='http://miskinhill.com.au/journals/']" + + "(~dc:isPartOf/mhs:publicationDate#comparable-lv,mhs:startPage#comparable-lv)") + .withResultType(RDFNode.class).result(author); + assertThat(results.size(), equalTo(4)); + assertThat(results.get(0), equalTo((RDFNode) obituary)); + assertThat(results.get(1), equalTo((RDFNode) article)); + assertThat(results.get(2), equalTo((RDFNode) multiAuthorArticle)); + assertThat(results.get(3), equalTo((RDFNode) review)); + } + + @Test + public void shouldEvaluateFormattedDTAdaptation() throws Exception { + String result = selectorFactory.get("!sioc:has_container/dc:created#formatted-dt('d MMMM yyyy')") + .withResultType(String.class).singleResult(forum); + assertThat(result, equalTo("15 June 2009")); + } + + @Test + public void shouldEvaluateFormattedDTAdaptationWithDoubleQuotes() throws Exception { + String result = selectorFactory.get("!sioc:has_container/dc:created#formatted-dt('yyyy-MM-dd\"T\"HH:mm:ssZZ')") + .withResultType(String.class).singleResult(forum); + assertThat(result, equalTo("2009-06-15T18:21:32+10:00")); + } + + @Test + public void shouldEvaluateStringLVAdaptation() throws Exception { + List<String> results = selectorFactory.get("dc:language/lingvoj:iso1#string-lv") + .withResultType(String.class).result(journal); + assertThat(results.size(), equalTo(2)); + assertThat(results, hasItems("en", "ru")); + } + + @Test + public void stringLVAdaptationShouldStripTagsFromXMLLiteral() throws Exception { + String result = selectorFactory.get("!sioc:has_container/sioc:content/awol:body#string-lv") + .withResultType(String.class).singleResult(forum); + assertEquals("To coincide with the publication of our second issue, " + + "the 2008 volume of Australian Slavonic and East European Studies, " + + "we are making available two new data feeds: an Atom feed of all " + + "journal issues published on this site, and the complete RDF dataset " + + "underlying the site. We hope this helps our users and aggregators " + + "to discover new content as it is published.", + result.trim().replaceAll("\\s+", " ")); + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/SelectorMatcher.java b/src/test/java/au/id/djc/rdftemplate/selector/SelectorMatcher.java @@ -0,0 +1,36 @@ +package au.id.djc.rdftemplate.selector; + +import static org.junit.matchers.JUnitMatchers.hasItems; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.hamcrest.Matcher; + +public class SelectorMatcher<T extends Selector<?>> extends BeanPropertyMatcher<T> { + + private SelectorMatcher(Class<? extends T> type) { + super(type); + } + + public static SelectorMatcher<Selector<RDFNode>> selector(Matcher<Traversal>... traversals) { + if (traversals.length == 0) { + return new SelectorMatcher<Selector<RDFNode>>(NoopSelector.class); + } + SelectorMatcher<Selector<RDFNode>> m = new SelectorMatcher<Selector<RDFNode>>(TraversingSelector.class); + m.addRequiredProperty("traversals", hasItems(traversals)); + return m; + } + + public static <R> SelectorMatcher<UnionSelector<R>> unionSelector(Matcher<Selector<R>>... selectors) { + SelectorMatcher<UnionSelector<R>> m = new SelectorMatcher(UnionSelector.class); + m.addRequiredProperty("selectors", hasItems(selectors)); + return m; + } + + public <A> SelectorMatcher<Selector<?>> withAdaptation(Matcher<? extends Adaptation<A>> adaptation) { + SelectorMatcher<Selector<?>> m = new SelectorMatcher(SelectorWithAdaptation.class); + m.addRequiredProperty("baseSelector", this); + m.addRequiredProperty("adaptation", adaptation); + return m; + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/SelectorParserUnitTest.java b/src/test/java/au/id/djc/rdftemplate/selector/SelectorParserUnitTest.java @@ -0,0 +1,206 @@ +package au.id.djc.rdftemplate.selector; + +import static au.id.djc.rdftemplate.TestNamespacePrefixMap.*; +import static au.id.djc.rdftemplate.selector.AdaptationMatcher.*; +import static au.id.djc.rdftemplate.selector.PredicateMatcher.*; +import static au.id.djc.rdftemplate.selector.SelectorComparatorMatcher.*; +import static au.id.djc.rdftemplate.selector.SelectorMatcher.*; +import static au.id.djc.rdftemplate.selector.TraversalMatcher.*; +import static org.junit.Assert.*; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.vocabulary.DCTerms; +import com.hp.hpl.jena.vocabulary.RDF; +import org.junit.Before; +import org.junit.Test; + +import au.id.djc.rdftemplate.TestNamespacePrefixMap; + +public class SelectorParserUnitTest { + + private AntlrSelectorFactory factory; + + @Before + public void setUp() { + factory = new AntlrSelectorFactory(); + factory.setNamespacePrefixMap(TestNamespacePrefixMap.getInstance()); + } + + @Test + public void shouldRecogniseSingleTraversal() throws Exception { + Selector<RDFNode> selector = factory.get("dc:creator").withResultType(RDFNode.class); + assertThat(selector, selector(traversal(DCTerms.NS, "creator"))); + } + + @Test + public void shouldRecogniseMultipleTraversals() throws Exception { + Selector<RDFNode> selector = factory.get("dc:creator/foaf:name").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "creator"), + traversal(FOAF_NS, "name"))); + } + + @Test + public void shouldRecogniseMultipleTraversalsWithWhitespace() throws Exception { + Selector<RDFNode> selector = factory.get("dc:creator / foaf:name").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "creator"), + traversal(FOAF_NS, "name"))); + } + + @Test + public void shouldRecogniseInverseTraversal() throws Exception { + Selector<RDFNode> selector = factory.get("!dc:isPartOf/!dc:isPartOf").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "isPartOf").inverse(), + traversal(DCTerms.NS, "isPartOf").inverse())); + } + + @Test + public void shouldRecogniseSortOrder() throws Exception { + Selector<RDFNode> selector = factory.get("!mhs:isIssueOf(mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(MHS_NS, "isIssueOf").inverse() + .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) + .withAdaptation(comparableLVAdaptation()))))); + } + + @Test + public void shouldRecogniseReverseSortOrder() throws Exception { + Selector<RDFNode> selector = factory.get("!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(MHS_NS, "isIssueOf").inverse() + .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) + .withAdaptation(comparableLVAdaptation())).reversed()))); + } + + @Test + public void shouldRecogniseComplexSortOrder() throws Exception { + Selector<RDFNode> selector = factory.get("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(MHS_NS, "reviews") + .withSortOrder(selectorComparator(selector(traversal(DCTerms.NS, "isPartOf"), traversal(MHS_NS, "publicationDate")) + .withAdaptation(comparableLVAdaptation()))))); + } + + @Test + public void shouldRecogniseUriAdaptation() throws Exception { + Selector<?> selector = factory.get("mhs:coverThumbnail#uri"); + assertThat(selector, selector( + traversal(MHS_NS, "coverThumbnail")) + .withAdaptation(uriAdaptation())); + } + + @Test + public void shouldRecogniseBareUriAdaptation() throws Exception { + Selector<?> selector = factory.get("#uri"); + assertThat(selector, selector().withAdaptation(uriAdaptation())); + } + + @Test + public void shouldRecogniseUriSliceAdaptation() throws Exception { + Selector<?> selector = factory.get("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)"); + assertThat(selector, selector( + traversal(DCTerms.NS, "identifier") + .withPredicate(uriPrefixPredicate("urn:issn:"))) + .withAdaptation(uriSliceAdaptation(9))); + } + + @Test + public void shouldRecogniseUriPrefixPredicate() throws Exception { + Selector<RDFNode> selector = factory.get( + "!mhs:isIssueOf[uri-prefix='http://miskinhill.com.au/journals/'](~mhs:publicationDate#comparable-lv)") + .withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(MHS_NS, "isIssueOf") + .inverse() + .withPredicate(uriPrefixPredicate("http://miskinhill.com.au/journals/")) + .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) + .withAdaptation(comparableLVAdaptation())).reversed()))); + } + + @Test + public void shouldRecogniseSubscript() throws Exception { + Selector<String> selector = factory.get( + "!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri") + .withResultType(String.class); + assertThat(selector, selector( + traversal(MHS_NS, "isIssueOf") + .inverse() + .withSortOrder(selectorComparator(selector(traversal(MHS_NS, "publicationDate")) + .withAdaptation(comparableLVAdaptation())).reversed()) + .withSubscript(0), + traversal(MHS_NS, "coverThumbnail")) + .withAdaptation(uriAdaptation())); + } + + @Test + public void shouldRecogniseLVAdaptation() throws Exception { + Selector<Object> selector = factory.get("dc:language/lingvoj:iso1#lv").withResultType(Object.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "language"), + traversal("http://www.lingvoj.org/ontology#", "iso1")) + .withAdaptation(lvAdaptation())); + } + + @Test + public void shouldRecogniseTypePredicate() throws Exception { + Selector<RDFNode> selector = factory.get("!dc:creator[type=mhs:Review]").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "creator").inverse().withPredicate(typePredicate(MHS_NS, "Review")))); + } + + @Test + public void shouldRecogniseAndCombinationOfPredicates() throws Exception { + Selector<RDFNode> selector = factory.get("!dc:creator[type=mhs:Review and uri-prefix='http://miskinhill.com.au/journals/']").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "creator").inverse() + .withPredicate(booleanAndPredicate( + typePredicate(MHS_NS, "Review"), + uriPrefixPredicate("http://miskinhill.com.au/journals/"))))); + } + + @Test + public void shouldRecogniseUnion() throws Exception { + Selector<RDFNode> selector = factory.get("!dc:creator | !mhs:translator").withResultType(RDFNode.class); + assertThat((UnionSelector<RDFNode>) selector, unionSelector( + selector(traversal(DCTerms.NS, "creator").inverse()), + selector(traversal(MHS_NS, "translator").inverse()))); + } + + @Test + public void shouldRecogniseMultipleSortSelectors() throws Exception { + Selector<RDFNode> selector = factory.get("!dc:creator(~dc:isPartOf/mhs:publicationDate#comparable-lv,mhs:startPage#comparable-lv)").withResultType(RDFNode.class); + assertThat(selector, selector( + traversal(DCTerms.NS, "creator").inverse() + .withSortOrder( + selectorComparator(selector(traversal(DCTerms.NS, "isPartOf"), traversal(MHS_NS, "publicationDate")) + .withAdaptation(comparableLVAdaptation())).reversed(), + selectorComparator(selector(traversal(MHS_NS, "startPage")).withAdaptation(comparableLVAdaptation()))))); + } + + @Test + public void shouldRecogniseFormattedDTAdaptation() throws Exception { + Selector<?> selector = factory.get("dc:created#formatted-dt('d MMMM yyyy')"); + assertThat(selector, selector(traversal(DCTerms.NS, "created")) + .withAdaptation(formattedDTAdaptation("d MMMM yyyy"))); + } + + @Test + public void shouldRecogniseRdfType() throws Exception { + // was broken due to ANTLR being confused about the literal string "type" which was hardcoded to be a predicate + Selector<RDFNode> selector = factory.get("rdf:type").withResultType(RDFNode.class); + assertThat(selector, selector(traversal(RDF.getURI(), "type"))); + } + + @Test(expected = InvalidSelectorSyntaxException.class) + public void shouldThrowForInvalidSyntax() throws Exception { + factory.get("dc:creator]["); // this is a parser error + } + + @Test(expected = InvalidSelectorSyntaxException.class) + public void shouldThrowForUnrecognisedCharacter() throws Exception { + factory.get("dc:cre&ator"); // ... and this is a lexer error + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/TraversalMatcher.java b/src/test/java/au/id/djc/rdftemplate/selector/TraversalMatcher.java @@ -0,0 +1,44 @@ +package au.id.djc.rdftemplate.selector; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.matchers.JUnitMatchers.hasItems; + +import java.util.Comparator; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import org.hamcrest.Matcher; + +public class TraversalMatcher extends BeanPropertyMatcher<Traversal> { + + private TraversalMatcher() { + super(Traversal.class); + } + + public static TraversalMatcher traversal(String propertyNamespace, String propertyLocalName) { + TraversalMatcher m = new TraversalMatcher(); + m.addRequiredProperty("propertyNamespace", equalTo(propertyNamespace)); + m.addRequiredProperty("propertyLocalName", equalTo(propertyLocalName)); + return m; + } + + public TraversalMatcher inverse() { + addRequiredProperty("inverse", equalTo(true)); + return this; + } + + public TraversalMatcher withPredicate(Matcher<? extends Predicate> predicate) { + addRequiredProperty("predicate", predicate); + return this; + } + + public TraversalMatcher withSortOrder(Matcher<? extends Comparator<RDFNode>>... sortOrder) { + addRequiredProperty("sortOrder", hasItems(sortOrder)); + return this; + } + + public TraversalMatcher withSubscript(int subscript) { + addRequiredProperty("subscript", equalTo(subscript)); + return this; + } + +} diff --git a/src/test/java/au/id/djc/rdftemplate/selector/UriAnchorAdaptationUnitTest.java b/src/test/java/au/id/djc/rdftemplate/selector/UriAnchorAdaptationUnitTest.java @@ -0,0 +1,23 @@ +package au.id.djc.rdftemplate.selector; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import com.hp.hpl.jena.rdf.model.ResourceFactory; +import org.junit.Test; + +public class UriAnchorAdaptationUnitTest { + + @Test + public void shouldReturnAnchor() { + String result = new UriAnchorAdaptation().adapt(ResourceFactory.createResource("http://example.com/#asdf")); + assertThat(result, equalTo("asdf")); + } + + @Test + public void shouldReturnEmptyStringForUriWithoutAnchor() { + String result = new UriAnchorAdaptation().adapt(ResourceFactory.createResource("http://example.com/")); + assertThat(result, equalTo("")); + } + +} diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/conditional.xml b/src/test/resources/au/id/djc/rdftemplate/conditional.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/for-seq.xml b/src/test/resources/au/id/djc/rdftemplate/for-seq.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/for.xml b/src/test/resources/au/id/djc/rdftemplate/for.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/join-seq.xml b/src/test/resources/au/id/djc/rdftemplate/join-seq.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/join.xml b/src/test/resources/au/id/djc/rdftemplate/join.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/namespaces.xml b/src/test/resources/au/id/djc/rdftemplate/namespaces.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/replace-subtree.xml b/src/test/resources/au/id/djc/rdftemplate/replace-subtree.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/replace-xml.xml b/src/test/resources/au/id/djc/rdftemplate/replace-xml.xml diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml b/src/test/resources/au/id/djc/rdftemplate/test-data.xml