rdftemplate

Library for generating XML documents from RDF data using templates
git clone https://code.djc.id.au/git/rdftemplate/
commit baa52718bdb343980370b95a1185ffccae2681fc
parent f81185240e76a6b1a40a661d4cc925043382c992
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun, 11 Oct 2009 17:37:46 +1000

support multiple comma-separate selectors in sort order

Diffstat:
Msrc/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g | 18++++++++++++++----
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java | 47+++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main/java/au/com/miskinhill/rdftemplate/selector/Traversal.java | 49++++++++++++++++++-------------------------------
Asrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorComparatorMatcher.java | 25+++++++++++++++++++++++++
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java | 16++++++++++++++++
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java | 43++++++++++++++++++++++++++-----------------
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/TraversalMatcher.java | 14++++++--------
Msrc/test/resources/au/com/miskinhill/rdftemplate/test-data.xml | 4++++
8 files changed, 156 insertions(+), 60 deletions(-)
diff --git a/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g b/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g
@@ -114,10 +114,10 @@ traversal returns [Traversal result]
       | // optional
       )
       ( '('
-        ( '~' { $result.setReverseSorted(true); }
-        | // optional
-        )
-        s=selector { $result.setSortOrder(s.withResultType(Comparable.class)); }
+        so=sortOrder { $result.addSortOrderComparator(so); }
+        ( ','
+          so=sortOrder { $result.addSortOrderComparator(so); }
+        )*
         ')'
       | // optional
       )
@@ -128,6 +128,16 @@ traversal returns [Traversal result]
       )
     ;
 
+sortOrder returns [SelectorComparator<? extends Comparable<?>> result]
+@init {
+    result = new SelectorComparator();
+}
+    : ( '~' { $result.setReversed(true); }
+      | // optional
+      )
+      s=selector { $result.setSelector((Selector) s.withResultType(Comparable.class)); }
+    ;
+
 booleanPredicate returns [Predicate result]
     : ( p=predicate { result = p; }
       | left=predicate
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java
@@ -0,0 +1,46 @@
+/**
+ * 
+ */
+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 = selector.singleResult(left);
+        T rightKey = selector.singleResult(right);
+        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/Traversal.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/Traversal.java
@@ -22,11 +22,21 @@ public class Traversal {
     private String propertyLocalName;
     private boolean inverse = false;
     private Predicate predicate;
-    private Selector<? extends Comparable<?>> sortOrder;
-    private Comparator<RDFNode> _sortComparator;
-    private boolean reverseSorted = false;
+    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);
@@ -46,8 +56,8 @@ public class Traversal {
             }
         }
         CollectionUtils.filter(destinations, predicate);
-        if (_sortComparator != null)
-            Collections.sort(destinations, reverseSorted ? Collections.reverseOrder(_sortComparator) : _sortComparator);
+        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);
@@ -65,7 +75,6 @@ public class Traversal {
                 .append("inverse", inverse)
                 .append("predicate", predicate)
                 .append("sortOrder", sortOrder)
-                .append("reverseSorted", reverseSorted)
                 .append("subscript", subscript)
                 .toString();
     }
@@ -102,34 +111,12 @@ public class Traversal {
         this.predicate = predicate;
     }
     
-    public Selector<?> getSortOrder() {
+    public List<Comparator<RDFNode>> getSortOrder() {
         return sortOrder;
     }
     
-    private static final class SelectorComparator<T extends Comparable<T>> implements Comparator<RDFNode> {
-        private final Selector<T> selector;
-        public SelectorComparator(Selector<T> selector) {
-            this.selector = selector;
-        }
-        @Override
-        public int compare(RDFNode left, RDFNode right) {
-            T leftKey = selector.singleResult(left);
-            T rightKey = selector.singleResult(right);
-            return leftKey.compareTo(rightKey);
-        }
-    }
-    
-    public <T extends Comparable<T>> void setSortOrder(Selector<T> sortOrder) {
-        this.sortOrder = sortOrder;
-        this._sortComparator = new SelectorComparator<T>(sortOrder);
-    }
-    
-    public boolean isReverseSorted() {
-        return reverseSorted;
-    }
-    
-    public void setReverseSorted(boolean reverseSorted) {
-        this.reverseSorted = reverseSorted;
+    public void addSortOrderComparator(Comparator<RDFNode> selector) {
+        this.sortOrder.add(selector);
     }
     
     public Integer getSubscript() {
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorComparatorMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorComparatorMatcher.java
@@ -0,0 +1,25 @@
+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<? extends SelectorComparator<T>>) 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,5 +1,10 @@
 package au.com.miskinhill.rdftemplate.selector;
 
+import static au.com.miskinhill.rdftemplate.selector.AdaptationMatcher.comparableLVAdaptation;
+import static au.com.miskinhill.rdftemplate.selector.SelectorComparatorMatcher.selectorComparator;
+import static au.com.miskinhill.rdftemplate.selector.SelectorMatcher.selector;
+import static au.com.miskinhill.rdftemplate.selector.TraversalMatcher.traversal;
+
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertThat;
 import static org.junit.matchers.JUnitMatchers.hasItems;
@@ -161,4 +166,15 @@ public class SelectorEvaluationUnitTest {
         assertThat(results, hasItems((RDFNode) article, (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(3));
+        assertThat(results.get(0), equalTo((RDFNode) obituary));
+        assertThat(results.get(1), equalTo((RDFNode) article));
+        assertThat(results.get(2), equalTo((RDFNode) review));
+    }
+    
 }
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,12 +1,13 @@
 package au.com.miskinhill.rdftemplate.selector;
 
-import org.junit.Before;
-
 import static au.com.miskinhill.rdftemplate.selector.AdaptationMatcher.*;
 import static au.com.miskinhill.rdftemplate.selector.PredicateMatcher.*;
 import static au.com.miskinhill.rdftemplate.selector.SelectorMatcher.*;
-import static au.com.miskinhill.rdftemplate.selector.TraversalMatcher.traversal;
-import static org.junit.Assert.assertThat;
+import static au.com.miskinhill.rdftemplate.selector.SelectorComparatorMatcher.*;
+import static au.com.miskinhill.rdftemplate.selector.TraversalMatcher.*;
+import static org.junit.Assert.*;
+
+import org.junit.Before;
 
 import com.hp.hpl.jena.rdf.model.RDFNode;
 import org.junit.Test;
@@ -47,8 +48,8 @@ public class SelectorParserUnitTest {
         Selector<RDFNode> selector = factory.get("!mhs:isIssueOf(mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class);
         assertThat(selector, selector(
                 traversal("mhs", "isIssueOf").inverse()
-                .withSortOrder(selector(traversal("mhs", "publicationDate"))
-                    .withAdaptation(comparableLVAdaptation()))));
+                .withSortOrder(selectorComparator(selector(traversal("mhs", "publicationDate"))
+                    .withAdaptation(comparableLVAdaptation())))));
     }
     
     @Test
@@ -56,9 +57,8 @@ public class SelectorParserUnitTest {
         Selector<RDFNode> selector = factory.get("!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class);
         assertThat(selector, selector(
                 traversal("mhs", "isIssueOf").inverse()
-                .withSortOrder(selector(traversal("mhs", "publicationDate"))
-                    .withAdaptation(comparableLVAdaptation()))
-                .reverseSorted()));
+                .withSortOrder(selectorComparator(selector(traversal("mhs", "publicationDate"))
+                    .withAdaptation(comparableLVAdaptation())).reversed())));
     }
     
     @Test
@@ -66,8 +66,8 @@ public class SelectorParserUnitTest {
         Selector<RDFNode> selector = factory.get("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class);
         assertThat(selector, selector(
                 traversal("mhs", "reviews")
-                .withSortOrder(selector(traversal("dc", "isPartOf"), traversal("mhs", "publicationDate"))
-                        .withAdaptation(comparableLVAdaptation()))));
+                .withSortOrder(selectorComparator(selector(traversal("dc", "isPartOf"), traversal("mhs", "publicationDate"))
+                        .withAdaptation(comparableLVAdaptation())))));
     }
     
     @Test
@@ -102,9 +102,8 @@ public class SelectorParserUnitTest {
                 traversal("mhs", "isIssueOf")
                     .inverse()
                     .withPredicate(uriPrefixPredicate("http://miskinhill.com.au/journals/"))
-                    .withSortOrder(selector(traversal("mhs", "publicationDate"))
-                            .withAdaptation(comparableLVAdaptation()))
-                    .reverseSorted()));
+                    .withSortOrder(selectorComparator(selector(traversal("mhs", "publicationDate"))
+                            .withAdaptation(comparableLVAdaptation())).reversed())));
     }
     
     @Test
@@ -115,9 +114,8 @@ public class SelectorParserUnitTest {
         assertThat(selector, selector(
                 traversal("mhs", "isIssueOf")
                     .inverse()
-                    .withSortOrder(selector(traversal("mhs", "publicationDate"))
-                            .withAdaptation(comparableLVAdaptation()))
-                    .reverseSorted()
+                    .withSortOrder(selectorComparator(selector(traversal("mhs", "publicationDate"))
+                            .withAdaptation(comparableLVAdaptation())).reversed())
                     .withSubscript(0),
                 traversal("mhs", "coverThumbnail"))
                 .withAdaptation(uriAdaptation()));
@@ -157,6 +155,17 @@ public class SelectorParserUnitTest {
                 selector(traversal("mhs", "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("dc", "creator").inverse()
+                .withSortOrder(
+                    selectorComparator(selector(traversal("dc", "isPartOf"), traversal("mhs", "publicationDate"))
+                        .withAdaptation(comparableLVAdaptation())).reversed(),
+                    selectorComparator(selector(traversal("mhs", "startPage")).withAdaptation(comparableLVAdaptation())))));
+    }
+    
     @Test(expected = InvalidSelectorSyntaxException.class)
     public void shouldThrowForInvalidSyntax() throws Exception {
         factory.get("dc:creator]["); // this is a parser 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,8 +1,11 @@
 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> {
@@ -28,13 +31,8 @@ public class TraversalMatcher extends BeanPropertyMatcher<Traversal> {
         return this;
     }
     
-    public TraversalMatcher withSortOrder(Matcher<Selector<?>> sortOrder) {
-        addRequiredProperty("sortOrder", sortOrder);
-        return this;
-    }
-    
-    public TraversalMatcher reverseSorted() {
-        addRequiredProperty("reverseSorted", equalTo(true));
+    public TraversalMatcher withSortOrder(Matcher<? extends Comparator<RDFNode>>... sortOrder) {
+        addRequiredProperty("sortOrder", hasItems(sortOrder));
         return this;
     }
     
diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml b/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml
@@ -96,6 +96,8 @@
     <dc:isPartOf rdf:resource="http://miskinhill.com.au/journals/test/1:1/"/>
     <mhs:reviews rdf:resource="http://miskinhill.com.au/cited/books/test"/>
     <dc:creator rdf:resource="http://miskinhill.com.au/authors/test-author"/>
+    <mhs:startPage rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">205</mhs:startPage>
+    <mhs:endPage rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">206</mhs:endPage>
   </mhs:Review>
   <mhs:Review rdf:about="http://miskinhill.com.au/journals/test/2:1/reviews/another-review">
     <dc:isPartOf rdf:resource="http://miskinhill.com.au/journals/test/2:1/"/>
@@ -107,6 +109,8 @@
     <dc:isPartOf rdf:resource="http://miskinhill.com.au/journals/test/1:1/"/>
     <dc:creator rdf:resource="http://miskinhill.com.au/authors/test-author"/>
     <mhs:obituaryOf rdf:nodeID="person"/>
+    <mhs:startPage rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">1</mhs:startPage>
+    <mhs:endPage rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">2</mhs:endPage>
   </mhs:Obituary>
   <foaf:Person rdf:nodeID="person">
     <foaf:name>John Doe</foaf:name>