rdftemplate

Library for generating XML documents from RDF data using templates
git clone https://code.djc.id.au/git/rdftemplate/
commit 92acd9e1c4cb5b1a7a3807c01284c8507bc7f9bd
parent 4f25607ea77ca8eb8953b99705c85de97ad9d984
Author: Dan Callaghan <djc@djc.id.au>
Date:   Tue,  6 Oct 2009 20:15:36 +1000

allow for user-defined adapatations, caching of selector parsing results

Diffstat:
Msrc/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g | 51+++++++++++++++++++++++++++++----------------------
Msrc/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java | 33++++++++++++++++++---------------
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/AdaptationResolver.java | 7+++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/AntlrSelectorFactory.java | 34++++++++++++++++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java | 21+++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactory.java | 35+++++++++++++++++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/SelectorFactory.java | 7+++++++
Msrc/test/java/au/com/miskinhill/rdftemplate/TemplateInterpolatorUnitTest.java | 13++++++++-----
Asrc/test/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java | 18++++++++++++++++++
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java | 32+++++++++++++++++---------------
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java | 43++++++++++++++++++++++++++-----------------
11 files changed, 220 insertions(+), 74 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
@@ -5,22 +5,18 @@ package au.com.miskinhill.rdftemplate.selector;
 }
 
 @parser::members {
-    public static Selector<?> parse(String expression) {
-        CharStream stream = new ANTLRStringStream(expression);
-        SelectorLexer lexer = new SelectorLexer(stream);
-        CommonTokenStream tokens = new CommonTokenStream(lexer);
-        SelectorParser parser = new SelectorParser(tokens);
-        try {
-            return parser.unionSelector();
-        } catch (RecognitionException e) {
-            throw new InvalidSelectorSyntaxException(e);
-        }
-    }
     
     @Override
     public void reportError(RecognitionException e) {
         throw new InvalidSelectorSyntaxException(e);
     }
+    
+    private AdaptationResolver adaptationResolver;
+    
+    public void setAdaptationResolver(AdaptationResolver adaptationResolver) {
+        this.adaptationResolver = adaptationResolver;
+    }
+    
 }
 
 @lexer::header {
@@ -53,20 +49,35 @@ unionSelector returns [Selector<?> result]
     ;
 
 selector returns [Selector<?> result]
+@init {
+    Class<? extends Adaptation<?>> adaptationClass;
+    Adaptation<?> adaptation = null;
+}
     : ' '*
       ( ts=traversingSelector { result = ts; }
       | { result = new NoopSelector(); }
       )
       ( '#'
-        ( URI_ADAPTATION { result = new SelectorWithAdaptation(result, new UriAdaptation()); }
-        | URI_SLICE_ADAPTATION
-          '('
-          startIndex=INTEGER { result = new SelectorWithAdaptation(result,
-                                       new UriSliceAdaptation(Integer.parseInt($startIndex.text))); }
+        adaptationName=XMLTOKEN { adaptationClass = adaptationResolver.getByName($adaptationName.text); }
+        ( '('
+          startIndex=INTEGER {
+                                try {
+                                    adaptation = adaptationClass.getConstructor(Integer.class)
+                                            .newInstance(Integer.parseInt($startIndex.text));
+                                } catch (Exception e) {
+                                    throw new RuntimeException(e);
+                                }
+                             }
           ')'
-        | COMPARABLE_LV_ADAPTATION { result = new SelectorWithAdaptation(result, new ComparableLiteralValueAdaptation()); }
-        | LV_ADAPTATION { result = new SelectorWithAdaptation(result, new LiteralValueAdaptation()); }
+        | {
+               try {
+                   adaptation = adaptationClass.newInstance();
+               } catch (Exception e) {
+                   throw new RuntimeException(e);
+               }
+          }
         )
+        { $result = new SelectorWithAdaptation(result, adaptation); }
       |
       )
       ' '*
@@ -139,10 +150,6 @@ predicate returns [Predicate result]
 URI_PREFIX_PREDICATE : 'uri-prefix' ;
 TYPE_PREDICATE : 'type' ;
 FIRST_PREDICATE : 'first' ;
-LV_ADAPTATION : 'lv' ;
-COMPARABLE_LV_ADAPTATION : 'comparable-lv' ;
-URI_SLICE_ADAPTATION : 'uri-slice' ;
-URI_ADAPTATION : 'uri' ;
 
 XMLTOKEN : ('a'..'z'|'A'..'Z') ('a'..'z'|'A'..'Z'|'0'..'9'|'_'|'-')* ;
 INTEGER : ('0'..'9')+ ;
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java
@@ -34,7 +34,7 @@ import com.hp.hpl.jena.rdf.model.RDFNode;
 import org.apache.commons.lang.StringUtils;
 
 import au.com.miskinhill.rdftemplate.selector.Selector;
-import au.com.miskinhill.rdftemplate.selector.SelectorParser;
+import au.com.miskinhill.rdftemplate.selector.SelectorFactory;
 
 public class TemplateInterpolator {
     
@@ -55,17 +55,20 @@ public class TemplateInterpolator {
         inputFactory.setProperty("javax.xml.stream.isCoalescing", true);
     }
     
-    private TemplateInterpolator() {
+    private final SelectorFactory selectorFactory;
+    
+    public TemplateInterpolator(SelectorFactory selectorFactory) {
+        this.selectorFactory = selectorFactory;
     }
     
     @SuppressWarnings("unchecked")
-    public static String interpolate(Reader reader, RDFNode node) throws XMLStreamException {
+    public String interpolate(Reader reader, RDFNode node) throws XMLStreamException {
         StringWriter writer = new StringWriter();
         interpolate(inputFactory.createXMLEventReader(reader), node, outputFactory.createXMLEventWriter(writer));
         return writer.toString();
     }
     
-    public static void interpolate(Iterator<XMLEvent> reader, RDFNode node, XMLEventWriter writer)
+    public void interpolate(Iterator<XMLEvent> reader, RDFNode node, XMLEventWriter writer)
             throws XMLStreamException {
         while (reader.hasNext()) {
             XMLEvent event = reader.next();
@@ -76,7 +79,7 @@ public class TemplateInterpolator {
                         Attribute testAttribute = start.getAttributeByName(new QName("test"));
                         if (testAttribute == null)
                             throw new TemplateSyntaxException("rdf:if must have a test attribute");
-                        Selector<?> selector = SelectorParser.parse(testAttribute.getValue());
+                        Selector<?> selector = selectorFactory.get(testAttribute.getValue());
                         if (selector.result(node).isEmpty()) {
                             consumeTree(start, reader);
                             break;
@@ -92,7 +95,7 @@ public class TemplateInterpolator {
                         Attribute contentAttribute = start.getAttributeByName(CONTENT_ACTION_QNAME);
                         Attribute forAttribute = start.getAttributeByName(FOR_ACTION_QNAME);
                         if (ifAttribute != null) {
-                            Selector<?> selector = SelectorParser.parse(ifAttribute.getValue());
+                            Selector<?> selector = selectorFactory.get(ifAttribute.getValue());
                             if (selector.result(node).isEmpty()) {
                                 consumeTree(start, reader);
                                 break;
@@ -104,12 +107,12 @@ public class TemplateInterpolator {
                         } else if (contentAttribute != null) {
                             consumeTree(start, reader);
                             start = interpolateAttributes(start, node);
-                            Selector<?> selector = SelectorParser.parse(contentAttribute.getValue());
+                            Selector<?> selector = selectorFactory.get(contentAttribute.getValue());
                             writeTreeForContent(writer, start, selector.singleResult(node));
                         } else if (forAttribute != null) {
                             start = cloneStartWithAttributes(start, cloneAttributesWithout(start, FOR_ACTION_QNAME));
                             List<XMLEvent> tree = consumeTree(start, reader);
-                            Selector<RDFNode> selector = SelectorParser.parse(forAttribute.getValue()).withResultType(RDFNode.class);
+                            Selector<RDFNode> selector = selectorFactory.get(forAttribute.getValue()).withResultType(RDFNode.class);
                             for (RDFNode subNode : selector.result(node)) {
                                 interpolate(tree.iterator(), subNode, writer);
                             }
@@ -136,7 +139,7 @@ public class TemplateInterpolator {
         }
     }
     
-    private static List<XMLEvent> consumeTree(StartElement start, Iterator<XMLEvent> reader) throws XMLStreamException {
+    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>();
@@ -162,7 +165,7 @@ public class TemplateInterpolator {
     }
     
     @SuppressWarnings("unchecked")
-    private static StartElement interpolateAttributes(StartElement start, RDFNode node) {
+    private StartElement interpolateAttributes(StartElement start, RDFNode node) {
         Set<Attribute> replacementAttributes = new LinkedHashSet<Attribute>();
         for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) {
             Attribute attribute = it.next();
@@ -186,7 +189,7 @@ public class TemplateInterpolator {
     }
     
     private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{([^}]*)\\}");
-    public static String interpolateString(String template, RDFNode node) {
+    public String interpolateString(String template, RDFNode node) {
         if (!SUBSTITUTION_PATTERN.matcher(template).find()) {
             return template; // fast path
         }
@@ -194,7 +197,7 @@ public class TemplateInterpolator {
         Matcher matcher = SUBSTITUTION_PATTERN.matcher(template);
         while (matcher.find()) {
             String expression = matcher.group(1);
-            Object replacement = SelectorParser.parse(expression).singleResult(node);
+            Object replacement = selectorFactory.get(expression).singleResult(node);
             
             String replacementValue;
             if (replacement instanceof RDFNode) {
@@ -217,7 +220,7 @@ public class TemplateInterpolator {
         return substituted.toString();
     }
     
-    private static void writeTreeForContent(XMLEventWriter writer, StartElement start, Object replacement)
+    private void writeTreeForContent(XMLEventWriter writer, StartElement start, Object replacement)
             throws XMLStreamException {
         if (replacement instanceof RDFNode) {
             RDFNode replacementNode = (RDFNode) replacement;
@@ -254,7 +257,7 @@ public class TemplateInterpolator {
     }
 
     @SuppressWarnings("unchecked")
-    private static Set<Attribute> cloneAttributesWithout(StartElement start, QName omit) {
+    private 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(); ) {
@@ -265,7 +268,7 @@ public class TemplateInterpolator {
         return attributes;
     }
     
-    private static void writeXMLLiteral(NamespaceContext nsContext, String literal, XMLEventWriter writer)
+    private void writeXMLLiteral(NamespaceContext nsContext, String literal, XMLEventWriter writer)
             throws XMLStreamException {
         XMLEventReader reader = inputFactory.createXMLEventReader(new StringReader(literal));
         while (reader.hasNext()) {
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/AdaptationResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/AdaptationResolver.java
@@ -0,0 +1,7 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+public interface AdaptationResolver {
+    
+    <T> Class<? extends 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
@@ -0,0 +1,34 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+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 final AdaptationResolver adaptationResolver;
+    
+    public AntlrSelectorFactory() {
+        this.adaptationResolver = new DefaultAdaptationResolver();
+    }
+    
+    public AntlrSelectorFactory(AdaptationResolver adaptationResolver) {
+        this.adaptationResolver = adaptationResolver;
+    }
+    
+    @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.setAdaptationResolver(adaptationResolver);
+        try {
+            return parser.unionSelector();
+        } catch (RecognitionException e) {
+            throw new InvalidSelectorSyntaxException(e);
+        }
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java
@@ -0,0 +1,21 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultAdaptationResolver implements AdaptationResolver {
+    
+    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("lv", LiteralValueAdaptation.class);
+        ADAPTATIONS.put("comparable-lv", ComparableLiteralValueAdaptation.class);
+    }
+
+    @Override
+    public Class<? extends Adaptation<?>> getByName(String name) {
+        return ADAPTATIONS.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
@@ -0,0 +1,35 @@
+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/SelectorFactory.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorFactory.java
@@ -0,0 +1,7 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+public interface SelectorFactory {
+    
+    Selector<?> get(String expression);
+
+}
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/TemplateInterpolatorUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/TemplateInterpolatorUnitTest.java
@@ -22,6 +22,7 @@ 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 {
     
@@ -31,6 +32,7 @@ public class TemplateInterpolatorUnitTest {
     }
     
     private Model model;
+    private TemplateInterpolator templateInterpolator;
     
     @Before
     public void setUp() {
@@ -38,12 +40,13 @@ public class TemplateInterpolatorUnitTest {
         InputStream stream = this.getClass().getResourceAsStream(
                 "/au/com/miskinhill/rdftemplate/test-data.xml");
         model.read(stream, "");
+        templateInterpolator = new TemplateInterpolator(new AntlrSelectorFactory());
     }
     
     @Test
     public void shouldReplaceSubtreesWithContent() throws Exception {
         Resource journal = model.getResource("http://miskinhill.com.au/journals/test/");
-        String result = TemplateInterpolator.interpolate(
+        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>")));
@@ -52,7 +55,7 @@ public class TemplateInterpolatorUnitTest {
     @Test
     public void shouldHandleXMLLiterals() throws Exception {
         Resource journal = model.getResource("http://miskinhill.com.au/journals/test/");
-        String result = TemplateInterpolator.interpolate(
+        String result = templateInterpolator.interpolate(
                 new InputStreamReader(this.getClass().getResourceAsStream("replace-xml.xml")), journal);
         assertThat(result, containsString(
                 "<div xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\"><p><em>Test Journal</em> is a journal.</p></div>"));
@@ -61,14 +64,14 @@ public class TemplateInterpolatorUnitTest {
     @Test
     public void shouldHandleIfs() throws Exception {
         Resource author = model.getResource("http://miskinhill.com.au/authors/test-author");
-        String result = TemplateInterpolator.interpolate(
+        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")));
         
         Resource authorWithoutNotes = model.getResource("http://miskinhill.com.au/authors/another-author");
-        result = TemplateInterpolator.interpolate(
+        result = templateInterpolator.interpolate(
                 new InputStreamReader(this.getClass().getResourceAsStream("conditional.xml")), authorWithoutNotes);
         assertThat(result, not(containsString("attribute test")));
         assertThat(result, not(containsString("element test")));
@@ -77,7 +80,7 @@ public class TemplateInterpolatorUnitTest {
     @Test
     public void shouldWork() throws Exception {
         Resource journal = model.getResource("http://miskinhill.com.au/journals/test/");
-        String result = TemplateInterpolator.interpolate(
+        String result = templateInterpolator.interpolate(
                 new InputStreamReader(this.getClass().getResourceAsStream("test-template.xml")), journal);
         String expected = exhaust(this.getClass().getResource("test-template.out.xml").toURI());
         assertEquals(expected.trim(), result.trim());
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/EternallyCachingSelectorFactoryUnitTest.java
@@ -0,0 +1,18 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class EternallyCachingSelectorFactoryUnitTest {
+    
+    @Test
+    public void shouldCacheSelectors() {
+        EternallyCachingSelectorFactory factory = new EternallyCachingSelectorFactory(new AntlrSelectorFactory());
+        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/SelectorEvaluationUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java
@@ -22,6 +22,7 @@ public class SelectorEvaluationUnitTest {
     
     private Model m;
     private Resource journal, issue, article, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru;
+    private SelectorFactory selectorFactory;
     
     @BeforeClass
     public static void ensureDatatypesRegistered() {
@@ -45,24 +46,25 @@ public class SelectorEvaluationUnitTest {
         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");
+        selectorFactory = new AntlrSelectorFactory();
     }
     
     @Test
     public void shouldEvaluateTraversal() {
-        RDFNode result = SelectorParser.parse("dc:creator").withResultType(RDFNode.class).singleResult(article);
+        RDFNode result = selectorFactory.get("dc:creator").withResultType(RDFNode.class).singleResult(article);
         assertThat(result, equalTo((RDFNode) author));
     }
     
     @Test
     public void shouldEvaluateMultipleTraversals() throws Exception {
-        RDFNode result = SelectorParser.parse("dc:creator/foaf:name")
+        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 = SelectorParser.parse("!mhs:isIssueOf/!dc:isPartOf")
+        List<RDFNode> results = selectorFactory.get("!mhs:isIssueOf/!dc:isPartOf")
                 .withResultType(RDFNode.class).result(journal);
         assertThat(results.size(), equalTo(4));
         assertThat(results, hasItems((RDFNode) article, (RDFNode) review, (RDFNode) anotherReview, (RDFNode) obituary));
@@ -70,7 +72,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateSortOrder() throws Exception {
-        List<RDFNode> results = SelectorParser.parse("dc:language(lingvoj:iso1#comparable-lv)")
+        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));
@@ -79,7 +81,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateReverseSortOrder() throws Exception {
-        List<RDFNode> results = SelectorParser.parse("dc:language(~lingvoj:iso1#comparable-lv)")
+        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));
@@ -88,7 +90,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateComplexSortOrder() throws Exception {
-        List<RDFNode> results = SelectorParser.parse("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)")
+        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));
@@ -97,31 +99,31 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateUriAdaptation() throws Exception {
-        String result = SelectorParser.parse("mhs:coverThumbnail#uri")
+        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 = SelectorParser.parse("#uri").withResultType(String.class).singleResult(journal);
+        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 = SelectorParser.parse("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)")
+        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 shouldEvaluateSubscript() throws Exception {
-        String result = SelectorParser.parse(
+        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 = SelectorParser.parse(
+        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"));
@@ -129,7 +131,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateLVAdaptation() throws Exception {
-        List<Object> results = SelectorParser.parse("dc:language/lingvoj:iso1#lv")
+        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"));
@@ -137,7 +139,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateTypePredicate() throws Exception {
-        List<RDFNode> results = SelectorParser.parse("!dc:creator[type=mhs:Review]")
+        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));
@@ -145,7 +147,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateAndCombinationOfPredicates() throws Exception {
-        List<RDFNode> results = SelectorParser.parse("!dc:creator[type=mhs:Article and uri-prefix='http://miskinhill.com.au/journals/']")
+        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(1));
         assertThat(results, hasItems((RDFNode) article));
@@ -153,7 +155,7 @@ public class SelectorEvaluationUnitTest {
     
     @Test
     public void shouldEvaluateUnion() throws Exception {
-        List<RDFNode> results = SelectorParser.parse("!dc:creator | !mhs:translator")
+        List<RDFNode> results = selectorFactory.get("!dc:creator | !mhs:translator")
                 .withResultType(RDFNode.class).result(anotherAuthor);
         assertThat(results.size(), equalTo(3));
         assertThat(results, hasItems((RDFNode) article, (RDFNode) citedArticle, (RDFNode) anotherReview));
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,5 +1,7 @@
 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.*;
@@ -11,15 +13,22 @@ import org.junit.Test;
 
 public class SelectorParserUnitTest {
     
+    private SelectorFactory factory;
+    
+    @Before
+    public void setUp() {
+        factory = new AntlrSelectorFactory();
+    }
+    
     @Test
     public void shouldRecogniseSingleTraversal() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("dc:creator").withResultType(RDFNode.class);
+        Selector<RDFNode> selector = factory.get("dc:creator").withResultType(RDFNode.class);
         assertThat(selector, selector(traversal("dc", "creator")));
     }
     
     @Test
     public void shouldRecogniseMultipleTraversals() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("dc:creator/foaf:name").withResultType(RDFNode.class);
+        Selector<RDFNode> selector = factory.get("dc:creator/foaf:name").withResultType(RDFNode.class);
         assertThat(selector, selector(
                 traversal("dc", "creator"),
                 traversal("foaf", "name")));
@@ -27,7 +36,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseInverseTraversal() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!dc:isPartOf/!dc:isPartOf").withResultType(RDFNode.class);
+        Selector<RDFNode> selector = factory.get("!dc:isPartOf/!dc:isPartOf").withResultType(RDFNode.class);
         assertThat(selector, selector(
                 traversal("dc", "isPartOf").inverse(),
                 traversal("dc", "isPartOf").inverse()));
@@ -35,7 +44,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseSortOrder() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!mhs:isIssueOf(mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class);
+        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"))
@@ -44,7 +53,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseReverseSortOrder() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class);
+        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"))
@@ -54,7 +63,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseComplexSortOrder() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!mhs:reviews(dc:isPartOf/mhs:publicationDate#comparable-lv)").withResultType(RDFNode.class);
+        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"))
@@ -63,7 +72,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseUriAdaptation() throws Exception {
-        Selector<?> selector = SelectorParser.parse("mhs:coverThumbnail#uri");
+        Selector<?> selector = factory.get("mhs:coverThumbnail#uri");
         assertThat(selector, selector(
                 traversal("mhs", "coverThumbnail"))
                 .withAdaptation(uriAdaptation()));
@@ -71,13 +80,13 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseBareUriAdaptation() throws Exception {
-        Selector<?> selector = SelectorParser.parse("#uri");
+        Selector<?> selector = factory.get("#uri");
         assertThat(selector, selector().withAdaptation(uriAdaptation()));
     }
     
     @Test
     public void shouldRecogniseUriSliceAdaptation() throws Exception {
-        Selector<?> selector = SelectorParser.parse("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)");
+        Selector<?> selector = factory.get("dc:identifier[uri-prefix='urn:issn:']#uri-slice(9)");
         assertThat(selector, selector(
                 traversal("dc", "identifier")
                     .withPredicate(uriPrefixPredicate("urn:issn:")))
@@ -86,7 +95,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseUriPrefixPredicate() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse(
+        Selector<RDFNode> selector = factory.get(
                 "!mhs:isIssueOf[uri-prefix='http://miskinhill.com.au/journals/'](~mhs:publicationDate#comparable-lv)")
                 .withResultType(RDFNode.class);
         assertThat(selector, selector(
@@ -100,7 +109,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseSubscript() throws Exception {
-        Selector<String> selector = SelectorParser.parse(
+        Selector<String> selector = factory.get(
                 "!mhs:isIssueOf(~mhs:publicationDate#comparable-lv)[0]/mhs:coverThumbnail#uri")
                 .withResultType(String.class);
         assertThat(selector, selector(
@@ -116,7 +125,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseLVAdaptation() throws Exception {
-        Selector<Object> selector = SelectorParser.parse("dc:language/lingvoj:iso1#lv").withResultType(Object.class);
+        Selector<Object> selector = factory.get("dc:language/lingvoj:iso1#lv").withResultType(Object.class);
         assertThat(selector, selector(
                 traversal("dc", "language"),
                 traversal("lingvoj", "iso1"))
@@ -125,14 +134,14 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseTypePredicate() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!dc:creator[type=mhs:Review]").withResultType(RDFNode.class);
+        Selector<RDFNode> selector = factory.get("!dc:creator[type=mhs:Review]").withResultType(RDFNode.class);
         assertThat(selector, selector(
                 traversal("dc", "creator").inverse().withPredicate(typePredicate("mhs", "Review"))));
     }
     
     @Test
     public void shouldRecogniseAndCombinationOfPredicates() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!dc:creator[type=mhs:Review and uri-prefix='http://miskinhill.com.au/journals/']").withResultType(RDFNode.class);
+        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("dc", "creator").inverse()
                 .withPredicate(booleanAndPredicate(
@@ -142,7 +151,7 @@ public class SelectorParserUnitTest {
     
     @Test
     public void shouldRecogniseUnion() throws Exception {
-        Selector<RDFNode> selector = SelectorParser.parse("!dc:creator | !mhs:translator").withResultType(RDFNode.class);
+        Selector<RDFNode> selector = factory.get("!dc:creator | !mhs:translator").withResultType(RDFNode.class);
         assertThat((UnionSelector<RDFNode>) selector, unionSelector(
                 selector(traversal("dc", "creator").inverse()),
                 selector(traversal("mhs", "translator").inverse())));
@@ -150,12 +159,12 @@ public class SelectorParserUnitTest {
     
     @Test(expected = InvalidSelectorSyntaxException.class)
     public void shouldThrowForInvalidSyntax() throws Exception {
-        SelectorParser.parse("dc:creator]["); // this is a parser error
+        factory.get("dc:creator]["); // this is a parser error
     }
     
     @Test(expected = InvalidSelectorSyntaxException.class)
     public void shouldThrowForUnrecognisedCharacter() throws Exception {
-        SelectorParser.parse("dc:cre&ator"); // ... and this is a lexer error
+        factory.get("dc:cre&ator"); // ... and this is a lexer error
     }
     
 }