/* * Copyright 2008 Fedora Commons, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mulgara.krule.rlog.ast.output; import org.jrdf.vocabulary.RDF; import org.mulgara.krule.rlog.ParseException; import org.mulgara.krule.rlog.ast.CheckRule; import org.mulgara.krule.rlog.ast.Predicate; import org.mulgara.krule.rlog.ast.Rule; import org.mulgara.krule.rlog.parser.URIParseException; import org.mulgara.krule.rlog.rdf.Literal; import org.mulgara.krule.rlog.rdf.RDFNode; import org.mulgara.krule.rlog.rdf.Var; import org.mulgara.query.rdf.LiteralImpl; import org.mulgara.query.rdf.URIReferenceImpl; import org.mulgara.resolver.spi.LocalizeException; import org.mulgara.resolver.spi.ResolverSession; import org.mulgara.store.nodepool.NodePoolException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import static org.mulgara.query.rdf.Krule.ARGUMENT; import static org.mulgara.query.rdf.Krule.CHECK; import static org.mulgara.query.rdf.Krule.CONSTRAINT_CONJUNCTION; import static org.mulgara.query.rdf.Krule.DIFFERENCE; import static org.mulgara.query.rdf.Krule.HAS_QUERY; import static org.mulgara.query.rdf.Krule.HAS_WHERE_CLAUSE; import static org.mulgara.query.rdf.Krule.HAS_SUBJECT; import static org.mulgara.query.rdf.Krule.HAS_PREDICATE; import static org.mulgara.query.rdf.Krule.HAS_OBJECT; import static org.mulgara.query.rdf.Krule.HAS_GRAPH; import static org.mulgara.query.rdf.Krule.MINUEND; import static org.mulgara.query.rdf.Krule.NAME; import static org.mulgara.query.rdf.Krule.QUERY; import static org.mulgara.query.rdf.Krule.RULE; import static org.mulgara.query.rdf.Krule.SELECTION_VARS; import static org.mulgara.query.rdf.Krule.SIMPLE_CONSTRAINT; import static org.mulgara.query.rdf.Krule.SUBTRAHEND; import static org.mulgara.query.rdf.Krule.TRIGGERS; import static org.mulgara.query.rdf.Krule.VARIABLE; /** * Writes rules to a list of triples. * * @created May 16, 2008 * @author Paula Gearon * @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class RuleGenerator extends TripleGenerator { /** The collection of rules that this class emits. */ public Collection<Rule> rules; /** The node for the rdf:Sequence type */ private final long rdfSeq; /** The node for the krule:Rule type */ private final long kruleRule; /** The node for the krule:Check type */ private final long kruleCheck; /** The node for the krule:Query type */ private final long kruleQuery; /** The node for the krule:Difference type */ private final long kruleDifference; /** The node for the krule:ConstraintConjunction type */ private final long kruleConjunction; /** The node for the krule:SimpleConstraint type */ private final long kruleSimple; /** The node for the krule:Variable type */ private final long kruleVariable; /** The node for the krule:hasQuery predicate */ private final long kruleHasQuery; /** The node for the krule:triggers predicate */ private final long kruleTriggers; /** The node for the krule:selectionVariables predicate */ private final long kruleSelVars; /** The node for the krule:hasWhereClause predicate */ private final long kruleHasWhereClause; /** The node for the krule:hasSubject predicate */ private final long kruleHasSubject; /** The node for the krule:hasPredicate predicate */ private final long kruleHasPredicate; /** The node for the krule:hasObject predicate */ private final long kruleHasObject; /** The node for the krule:hasModel predicate */ private final long kruleHasGraph; /** The node for the krule:argument predicate */ private final long kruleArgument; /** The node for the krule:minuend predicate */ private final long kruleMinuend; /** The node for the krule:subtrahend predicate */ private final long kruleSubtrahend; /** The node for the krule:name predicate */ private final long kruleName; /** The nodes for RDF sequence membership */ private final List<Long> cachedSeq = new ArrayList<Long>(); /** * Creates a new writer for a collection of rules. * @param rules The rules to be written. * @throws LocalizeException If localized nodes could not be accessed. */ public RuleGenerator(Collection<Rule> rules, ResolverSession resolverSession) throws LocalizeException { super(resolverSession); this.rules = rules; rdfSeq = resolverSession.localize(new URIReferenceImpl(RDF.SEQ)); kruleRule = resolverSession.localize(RULE); kruleCheck = resolverSession.localize(CHECK); kruleQuery = resolverSession.localize(QUERY); kruleDifference = resolverSession.localize(DIFFERENCE); kruleConjunction = resolverSession.localize(CONSTRAINT_CONJUNCTION); kruleSimple = resolverSession.localize(SIMPLE_CONSTRAINT); kruleVariable = resolverSession.localize(VARIABLE); kruleHasQuery = resolverSession.localize(HAS_QUERY); kruleHasSubject = resolverSession.localize(HAS_SUBJECT); kruleHasPredicate = resolverSession.localize(HAS_PREDICATE); kruleHasObject = resolverSession.localize(HAS_OBJECT); kruleHasGraph = resolverSession.localize(HAS_GRAPH); kruleTriggers = resolverSession.localize(TRIGGERS); kruleSelVars = resolverSession.localize(SELECTION_VARS); kruleHasWhereClause = resolverSession.localize(HAS_WHERE_CLAUSE); kruleArgument = resolverSession.localize(ARGUMENT); kruleMinuend = resolverSession.localize(MINUEND); kruleSubtrahend = resolverSession.localize(SUBTRAHEND); kruleName = resolverSession.localize(NAME); initSeqTo(3); } /** * {@inheritDoc} * @throws ParseException Constructing URIs for the output resulted in an invalid URI. * @throws NodePoolException If blank nodes could not be created. */ public List<long[]> emit(List<long[]> triples) throws ParseException, NodePoolException { try { for (Rule r: rules) emitRule(triples, r); } catch (LocalizeException e) { throw new NodePoolException("Unable to localize a node", e); } catch (URISyntaxException e) { throw new ParseException("Malformed URI in rules: " + e.getMessage()); } catch (URIParseException e) { throw new ParseException("Malformed URI in rules: " + e.getMessage()); } return triples; } /** * Create the the triple representation of a rule and append it. * @param triples the List to append the triples to. * @param rule The rule to emit. * @throws NodePoolException If blank nodes could not be created. * @throws ParseException If any bad URIs or non-object literals are encountered. * @throws URISyntaxException URIs in the rule are not formed correctly. * @throws LocalizeException Due to an error localizing a URI. * @throws URIParseException URIs in the rule are not formed correctly. */ private List<long[]> emitRule(List<long[]> triples, Rule rule) throws NodePoolException, ParseException, LocalizeException, URISyntaxException, URIParseException { long ruleNode = toKruleNode(rule.getName()); if (rule instanceof CheckRule) { // rule rdf:type kruleCheck add(triples, ruleNode, rdfType, (rule instanceof CheckRule) ? kruleCheck : kruleRule); } else { // rule rdf:type kruleRule add(triples, ruleNode, rdfType, kruleRule); emitTriggers(triples, ruleNode, rule.getTriggers()); } // query rdf:type krule:Query // rule krule:hasQuery query long query = newBlankNode(); add(triples, query, rdfType, kruleQuery); add(triples, ruleNode, kruleHasQuery, query); if (rule instanceof CheckRule) emitSelection(triples, query, rule.getVariables()); else emitSelection(triples, query, rule.getHead()); emitWhereClause(triples, query, rule.getBody(), rule.getBodySubtractions()); return triples; } /** * Adds the triggers for a rule to the triples. * @param triples The list of triples to append to. * @param triggers The rules that get triggered by the current rule. * @throws URISyntaxException IF a trigger rule has a malformed URI. * @throws LocalizeException If the URI in a trigger rule cannot be localized. */ private void emitTriggers(List<long[]> triples, long rule, Collection<Rule> triggers) throws LocalizeException, URISyntaxException { for (Rule r: triggers) add(triples, rule, kruleTriggers, toKruleNode(r.getName())); } /** * Adds the head of a rule to the triples. * @param triples The list of triples to append to. * @param selection The selection that makes up the head of a rule. * @throws LocalizeException Unable to create a new blank node. * @throws URISyntaxException If a selected URI is incorrectly formed. */ private void emitSelection(List<long[]> triples, long query, Predicate selection) throws URIParseException, LocalizeException, URISyntaxException { emitSelection(triples, query, Collections.singletonList(selection)); } /** * Adds the head of a rule to the triples. * @param triples The list of triples to append to. * @param selection The list of predicates that makes up the head of a rule. * @throws LocalizeException Unable to create a new blank node. * @throws URISyntaxException If a selected URI is incorrectly formed. */ private void emitSelection(List<long[]> triples, long query, List<Predicate> selection) throws URIParseException, LocalizeException, URISyntaxException { List<RDFNode> sel = new ArrayList<RDFNode>(); for (Predicate p: selection) { sel.add(p.getSubject()); sel.add(p.getPredicate()); sel.add(p.getObject()); } emitSelection(triples, query, sel); } /** * Adds the selection elements of a rule to the triples. * @param triples The list of triples to append to. * @param selection The selection that makes up the variables or the head of a rule. * @throws LocalizeException Unable to create a new blank node. * @throws URISyntaxException If a selected URI is incorrectly formed. */ private void emitSelection(List<long[]> triples, long query, Collection<? extends RDFNode> selection) throws URIParseException, LocalizeException, URISyntaxException { // seq rdf:type rdf:Seq // query krule:selectionVariables seq long seq = newBlankNode(); add(triples, seq, rdfType, rdfSeq); add(triples, query, kruleSelVars, seq); // seq rdf:_n selection(n) ... int n = 1; for (RDFNode s: selection) { add(triples, seq, getSeq(n++), toKruleNode(s.getRdfLabel())); } } /** * Prints the body for a rule to a PrintStream. * @param triples The list of triples to append to. * @param query The node that represents the query that makes up the body. * @param body The list of predicates that makes up the body. * @param subs The part of a where clause to be removed from the body. * @throws URIParseException If one of the URIs in the constraint expression has an invalid syntax. * @throws LocalizeException Unable to localize some of the URIs in the constraints. * @throws URISyntaxException If one of the URIs in the constraint has an invalid syntax. */ private void emitWhereClause(List<long[]> triples, long query, List<Predicate> body, List<Predicate> subs) throws URIParseException, LocalizeException, URISyntaxException { long constraintExpr = newBlankNode(); // query krule:hasWhereClause constraintExpr add(triples, query, kruleHasWhereClause, constraintExpr); if (subs.isEmpty()) emitConjunction(triples, constraintExpr, body); else emitSubtractions(triples, constraintExpr, body, subs); } /** * Emit the operation for the difference between the body and the subtractions as a set of triples. * @param triples The list of triples to append to. * @param body The main body to be selected (the minuend). * @param subs The constraints to be subtracted from the body (the subtrahends). * @throws URIParseException The constraints contain URIs that are invalid. * @throws LocalizeException Unable to localize some of the URIs in the constraint. * @throws URISyntaxException If one of the URIs in the constraint has an invalid syntax. */ private void emitSubtractions(List<long[]> triples, long diff, List<Predicate> body, List<Predicate> subs) throws URIParseException, LocalizeException, URISyntaxException { // diff rdf:type krule:Difference add(triples, diff, rdfType, kruleDifference); long argument = newBlankNode(); // diff krule:minuend argument add(triples, diff, kruleMinuend, argument); int lastElt = subs.size() - 1; if (lastElt == 0) emitConjunction(triples, argument, body); else emitSubtractions(triples, argument, body, subs.subList(0, lastElt)); // last argument in subtraction // diff krule:subtrahend argument argument = newBlankNode(); add(triples, diff, kruleSubtrahend, argument); emitSimpleConstraint(triples, argument, subs.get(lastElt)); } /** * Adds a list of constraints as the arguments to a single ConstraintConjunction. * @param triples The list of triples to add the statements to. * @param conjNode The node representing the conjunction being emitted. * @param conjunction The list of constraints that form the conjunction to be added. * @throws URIParseException If one of the URIs in the constraint has an invalid syntax. * @throws LocalizeException Unable to localize a URI in the expression. * @throws URISyntaxException If one of the URIs in the constraint has an invalid syntax. */ private void emitConjunction(List<long[]> triples, long conjNode, List<Predicate> conjunction) throws URIParseException, LocalizeException, URISyntaxException { if (conjunction.size() == 1) emitSimpleConstraint(triples, conjNode, conjunction.get(0)); else { // conj rdf:type krule:ConstraintConjunction add(triples, conjNode, rdfType, kruleConjunction); for (Predicate op: conjunction) { long argument = newBlankNode(); // conj krule:argument argument add(triples, conjNode, kruleArgument, argument); emitSimpleConstraint(triples, argument, op); } } } /** * Adds a single constraint to the generated triples. * @param triples The triples to add to. * @param constraintNode The local node representing the constraint. * @param constraint The constraint to write. * @throws URIParseException If one of the URIs in the constraint has an invalid syntax. * @throws LocalizeException Unable to localize a URI in the expression. * @throws URISyntaxException If one of the URIs in the constraint has an invalid syntax. */ private void emitSimpleConstraint(List<long[]> triples, long constraintNode, Predicate constraint) throws URIParseException, LocalizeException, URISyntaxException { // constraintNode rdf:type krule:SimpleConstraint add(triples, constraintNode, rdfType, kruleSimple); // constraintNode krule:hasSubject subjNode // constraintNode krule:hasPredicate predNode // constraintNode krule:hasObject objNode // constraintNode krule:hasModel graphNode [OPTIONAL] long node = emitNode(triples, constraint.getSubject()); add(triples, constraintNode, kruleHasSubject, node); node = emitNode(triples, constraint.getPredicate()); add(triples, constraintNode, kruleHasPredicate, node); node = emitNode(triples, constraint.getObject()); add(triples, constraintNode, kruleHasObject, node); if (constraint.hasGraphAnnotation()) { node = emitNode(triples, constraint.getGraphAnnotation()); add(triples, constraintNode, kruleHasGraph, node); } } /** * Add the details for a node to the list of triples. * @param triples The triples to add the node for. * @param node The structured data for the node being emitted. * @return The localized node created for this node. * @throws LocalizeException Unable to localize the URI for the node, or structure describing the node. * @throws URISyntaxException The node URI, or a URI associated with this node is malformed. */ private long emitNode(List<long[]> triples, RDFNode node) throws LocalizeException, URISyntaxException { if (node.isReference()) { // node rdf:type krule:URIReference long r = toKruleNode(node.getRdfLabel()); add(triples, r, rdfType, kruleUriReference); return r; } else if (node.isVariable()) { // node rdf:type krule:Variable long r = toKruleNode(node.getRdfLabel()); add(triples, r, rdfType, kruleVariable); add(triples, r, kruleName, localizeString(((Var)node).getName())); return r; } else { // node rdf:type krule:Literal // node rdf:value "......" long r = newBlankNode(); add(triples, r, rdfType, getKruleLiteral()); add(triples, r, getRdfValue(), toLiteral(node)); return r; } } /** * Converts a node representing a literal to a localized literal. * If krule Literals start to support data types, then so should this method. * @param node The RDFNode that can be cast to a literal. * @return A localized gNode for the literal. * @throws LocalizeException The string could not be written to or accessed from the string pool. */ private long toLiteral(RDFNode node) throws LocalizeException { Literal l = (Literal)node; return resolverSession.localize(new LiteralImpl(l.getLexical())); } /** * Localizes a string. * @param str The string to localize. * @return The gNode which represents the string in local form. * @throws LocalizeException The string could not be created in the string pool. */ private long localizeString(String str) throws LocalizeException { return resolverSession.localize(new LiteralImpl(str)); } /** * Get a cached sequence number. This will get more if they have not been cached yet. * @param i The sequence number to retrieve. Starts at 1. * @return The localized value of the URI rdf:_i * @throws LocalizeException If an uncached URI could not be localized. */ private long getSeq(int i) throws LocalizeException { assert i >= 1; if (i > cachedSeq.size()) initSeqTo(i); // retrieving a 1-based sequence from a 0-based list return cachedSeq.get(i - 1); } /** * Fill the sequence array to the element given. * @param largest The largest sequence element to initialize. * @throws LocalizeException If a URI cannot be localized. */ private void initSeqTo(int largest) throws LocalizeException { String seqBase = RDF.BASE_URI.toString() + "_"; for (int i = cachedSeq.size() + 1; i <= largest; i++) { URI s = URI.create(seqBase + i); cachedSeq.add(resolverSession.localize(new URIReferenceImpl(s))); } } }