/* * The contents of this file are subject to the Open Software License * Version 3.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.opensource.org/licenses/osl-3.0.txt * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. */ package org.mulgara.content.rlog; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.mulgara.krule.rlog.parser.NSUtils; import org.mulgara.query.TuplesException; import org.mulgara.query.rdf.Krule; import org.mulgara.query.rdf.URIReferenceImpl; import org.mulgara.resolver.spi.GlobalizeException; import org.mulgara.resolver.spi.LocalizeException; import org.mulgara.resolver.spi.ResolverSession; import org.mulgara.resolver.spi.Statements; import org.mulgara.util.functional.Pair; import org.apache.log4j.Logger; import org.jrdf.graph.Literal; import org.jrdf.graph.Node; import org.jrdf.graph.URIReference; import org.jrdf.vocabulary.RDF; /** * This class constructs an RLog structure out of a set of Statements. * * @created Mar 18, 2009 * @author Paula Gearon * @copyright © 2009 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class RlogStructure { /** Logger. */ private static final Logger logger = Logger.getLogger(RlogStructure.class.getName()); /** The rdf:type URI. */ private static final URIReference TYPE = new URIReferenceImpl(RDF.TYPE); /** The rdf:value URI. */ private static final URIReference RDF_VALUE = new URIReferenceImpl(RDF.VALUE); /** The rdf:Seq URI. */ private static final URIReference RDF_SEQ = new URIReferenceImpl(RDF.SEQ); /** The domain for system graphs. */ private static final String SYS = "sys"; /** The session to use for localizing and globalizing. */ ResolverSession session; /** The graph to store the statements in. */ Map<Node,Map<URIReference,Set<Node>>> graph = new HashMap<Node,Map<URIReference,Set<Node>>>(); /** Stores all the nodes of a particular type in a single set, one for each type. */ Map<Node,Set<Node>> nodesByType = new HashMap<Node,Set<Node>>(); /** Maps nodes to their type */ Map<Node,Node> typeByNode = new HashMap<Node,Node>(); /** Maps variables to their names */ Map<Node,String> nameByVar = new HashMap<Node,String>(); /** Maps namesto their variables */ Map<String,Node> varByName = new HashMap<String,Node>(); /** Maps all the in-use namespaces to their shorthand prefixes. */ Map<String,String> namespaces; /** Default map of namespaces to their shorthand prefixes. */ Map<String,String> defaultNamespaces; /** A generator for new namespace names. */ NamespaceGenerator namespaceGen = new NamespaceGenerator(); /** The current constraint context when writing a rule. */ Node currentRootConstraint = null; /** A list of the rdf:_x IDs used for sequences. */ List<URIReference> listIds = new ArrayList<URIReference>(); /** The local node for the rdf:type URI. */ final long rdfType; /** * Constructs the structure, along with the session used to help build it. * @param session The mechanism for convergins local nodes to global, and back. * @throws LocalizeException If a global node could not be localized to a long. */ public RlogStructure(ResolverSession session) throws LocalizeException { this.session = session; rdfType = session.localize(TYPE); initNamespaces(); } /** * Initialized the namespaces with the registered domains. */ private void initNamespaces() { Map<String,String> ns = new HashMap<String,String>(); for (Map.Entry<String,String> e: NSUtils.getRegisteredDomains()) { ns.put(e.getValue(), e.getKey()); } defaultNamespaces = Collections.unmodifiableMap(ns); namespaces = new HashMap<String,String>(); // add in sys: so it does not get mapped namespaces.put(SYS + ":", SYS); } /** * Build a structure in memory to represent the Krule data structure. * @param statements The statements to load. * @throws TuplesException If there was a problem accessing the statements. * @throws GlobalizeException If there was a problem converting the statements to references. */ public void load(Statements statements) throws TuplesException, GlobalizeException { statements.beforeFirst(); while (statements.next()) { long subject = statements.getSubject(); long predicate = statements.getPredicate(); long obj = statements.getObject(); if (predicate == rdfType) { addType(subject, obj); } else { addToGraph(subject, predicate, obj); } } nameVariables(); } /** * For every variable that was found, map it to its name. */ void nameVariables() { Set<Node> vars = nodesByType.get(Krule.VARIABLE); if (vars == null) return; for (Node v: vars) { Node name = getPropertyValue(v, Krule.NAME); if (!(name instanceof Literal)) throw new IllegalArgumentException("Bad Krule structure. Variable name is not a string."); String sName = getValidVariableName((Literal)name); nameByVar.put(v, sName); varByName.put(sName, v); } } /** * Write a set of statements representing RLog to a given writer. * @param writer The output to send the RLog to. * @throws IOException If the data could not be converted or written out. */ public void write(Writer writer) throws IOException { if (writer == null) throw new IllegalArgumentException("Writer cannot be null."); StringWriter out = new StringWriter(); writeAxioms(out); writeChecks(out); writeRules(out); out.close(); writePrefixes(writer); writer.append(out.getBuffer()); } /** * Sets the type for an object. * @param s The subject to be typed. * @param t The type of the subject. * @throws GlobalizeException If the subject or type could not be globalized. */ void addType(long s, long t) throws GlobalizeException { Node subj = session.globalize(s); Node type = session.globalize(t); // map the type to the node addValue(nodesByType, type, subj); // map the node to its type typeByNode.put(subj, type); } /** * Add a triple to the graph. * @param s The subject of the triple. * @param p The predicate of the triple. * @param o The object of the triple. * @throws GlobalizeException If one of the elements of the triple could not be * converted to a global node. */ void addToGraph(long s, long p, long o) throws GlobalizeException { Node subj = session.globalize(s); URIReference pred = (URIReference)session.globalize(p); Node obj = session.globalize(o); addPropertyValue(graph, subj, pred, obj); } /** * Writes all the axioms to the output as horn clauses. * @param out The writer to send the RLog to. * @throws IOException If there was an error writing to the output. */ void writePrefixes(Writer out) throws IOException { for (Map.Entry<String,String> ns: namespaces.entrySet()) { String k = ns.getKey(); String v = ns.getValue() + ":"; if (v.equals(k)) continue; out.append("@prefix "); out.append(v); out.append(" <"); out.append(k); out.append("> .\n"); } out.append("\n"); } /** * Writes all the axioms to the output as horn clauses. * @param out The writer to send the RLog to. * @throws IOException If there was an error writing to the output. */ void writeAxioms(Writer out) throws IOException { Set<Node> axioms = nodesByType.get(Krule.AXIOM); if (axioms == null) return; for (Node axiom: axioms) { Map<URIReference,Set<Node>> properties = graph.get(axiom); Node s = getSingle(properties, Krule.AXIOM_SUBJECT); Node p = getSingle(properties, Krule.AXIOM_PREDICATE); Node o = getSingle(properties, Krule.AXIOM_OBJECT); out.append(toPredicate(s, p, o)); out.append(".\n"); } out.append("\n"); } /** * Writes all the consistency checks to the output as horn clauses. * @param out The writer to send the RLog to. * @throws IOException If there was an error writing to the output. */ void writeChecks(Writer out) throws IOException { Set<Node> checks = nodesByType.get(Krule.CHECK); if (checks == null) return; for (Node check: checks) { Map<URIReference,Set<Node>> properties = graph.get(check); Node q = getSingle(properties, Krule.HAS_QUERY); Node qType = typeByNode.get(q); if (!qType.equals(Krule.QUERY)) { throw new IllegalArgumentException("Bad Krule structure. Consistency check has a query that has a non-query type: " + qType); } out.append(":- "); writeBody(out, q); out.append(".\n"); } out.append("\n"); } /** * Writes all the rules to the output as horn clauses. * @param out The writer to send the RLog to. * @throws IOException If there was an error writing to the output. */ void writeRules(Writer out) throws IOException { Set<Node> rules = nodesByType.get(Krule.RULE); if (rules == null) return; for (Node rule: rules) { Node q = getPropertyValue(rule, Krule.HAS_QUERY); Node qType = typeByNode.get(q); if (!qType.equals(Krule.QUERY)) { throw new IllegalArgumentException("Bad Krule structure. Consistency check has a query that has a non-query type: " + qType); } writeHead(out, q); out.append(" :- "); writeBody(out, q); out.append(".\n"); } out.append("\n"); } /** * Writes out the head of a query associated with a rule. * @param out The writer output. * @param query The node representing the query. */ void writeHead(Writer out, Node query) throws IOException { Node seq = getPropertyValue(query, Krule.SELECTION_VARS); Node sType = typeByNode.get(seq); if (!sType.equals(RDF_SEQ)) { throw new IllegalArgumentException("Bad Krule structure. Query selection sequence is not a sequence type: " + sType); } // loop over multiple triples int seqNr = 1; Node s; while (null != (s = getPropertyValue(seq, getListId(seqNr++)))) { Node p = getPropertyValue(seq, getListId(seqNr++)); Node o = getPropertyValue(seq, getListId(seqNr++)); if (p == null || o == null) throw new IllegalArgumentException("Bad Krule structure. Query selection sequence is not set of triples. " + seqNr + " elements"); // separate triples with commas if (seqNr > 4) out.append(", "); out.append(toPredicate(s, p, o)); } } /** * Writes out the body (WHERE clause) of a query associated with a rule. * @param out The writer output. * @param query The node representing the query. */ void writeBody(Writer out, Node query) throws IOException { Node constraint = getPropertyValue(query, Krule.HAS_WHERE_CLAUSE); // remember the current context in case it is needed currentRootConstraint = constraint; writeConstraint(out, constraint, false); } /** * Writes a general constraint type that may be inverted. This method is recursive. * @param out The writer output. * @param constraint The constraint to be written. * Accepts conjunctions, differences, transitive and simple constraints. * @param inv Indicates that the constraint is inverted. * @throws IOException Due to a write error on the writer. */ void writeConstraint(Writer out, Node constraint, boolean inv) throws IOException { Node cType = typeByNode.get(constraint); if (cType.equals(Krule.CONSTRAINT_CONJUNCTION)) { writeConstraintConjunction(out, constraint, inv); } else if (cType.equals(Krule.SIMPLE_CONSTRAINT)) { writeConstraintSimple(out, constraint, inv); } else if (cType.equals(Krule.DIFFERENCE)) { writeConstraintDifference(out, constraint, inv); } else if (cType.equals(Krule.TRANSITIVE_CONSTRAINT)) { writeConstraintTransitive(out, constraint, inv); } else { throw new IllegalArgumentException("Bad Krule structure. Unsupported Constraint type: " + cType); } } /** * Writes the simple constraint type. This is just a triple. * @param out The writer output. * @param constraint The constraint to be written. * @param inv Indicates that the constraint is inverted. * @throws IOException Due to a write error on the writer. */ void writeConstraintSimple(Writer out, Node constraint, boolean inv) throws IOException { Node s = getPropertyValue(constraint, Krule.HAS_SUBJECT); Node p = getPropertyValue(constraint, Krule.HAS_PREDICATE); Node o = getPropertyValue(constraint, Krule.HAS_OBJECT); // throw away the graph, as this is autodetected from the predicate in RLog parsing if (s == null || p == null || o == null) { throw new IllegalArgumentException("Bad Krule structure. Incomplete constraint."); } if (inv) out.append("~"); out.append(toPredicate(s, p, o)); } /** * Writes a constraint conjunction. * @param out The writer output. * @param constraint The constraint to be written. * @param inv Indicates that the constraint is inverted. * @throws IOException Due to a write error on the writer. */ void writeConstraintConjunction(Writer out, Node constraint, boolean inv) throws IOException { Map<URIReference,Set<Node>> constraintProps = graph.get(constraint); if (constraintProps == null) throw new IllegalArgumentException("Bad Krule structure. Missing arguments for a conjunction."); Set<Node> args = constraintProps.get(Krule.ARGUMENT); // go through all the constraints, separating them with commas boolean first = true; for (Node c: args) { if (first) first = false; else out.append(", "); writeConstraint(out, c, inv); } } /** * Writes a difference constraint. * @param out The writer output. * @param constraint The constraint to be written. * @param inv Indicates that the constraint is inverted. * @throws IOException Due to a write error on the writer. */ void writeConstraintDifference(Writer out, Node constraint, boolean inv) throws IOException { Node minuend = getPropertyValue(constraint, Krule.MINUEND); Node subtrahend = getPropertyValue(constraint, Krule.SUBTRAHEND); if (minuend == null) throw new IllegalArgumentException("Bad Krule structure. Missing minuend on a Difference."); if (subtrahend == null) throw new IllegalArgumentException("Bad Krule structure. Missing subtrahend on a Difference."); writeConstraint(out, minuend, inv); out.append(", "); writeConstraint(out, subtrahend, !inv); } /** * Writes a transitive constraint. * @param out The writer output. * @param constraint The constraint to be written. * @param inv Indicates that the constraint is inverted. * @throws IOException Due to a write error on the writer. */ void writeConstraintTransitive(Writer out, Node constraint, boolean inv) throws IOException { Node arg = getPropertyValue(constraint, Krule.TRANSITIVE_ARGUMENT); if (arg == null || !arg.equals(Krule.SIMPLE_CONSTRAINT)) { throw new IllegalArgumentException("Bad Krule structure. Transitive constraints must operate on simple arguments."); } Node s = getPropertyValue(arg, Krule.HAS_SUBJECT); Node p = getPropertyValue(arg, Krule.HAS_PREDICATE); Node o = getPropertyValue(arg, Krule.HAS_OBJECT); // throw away the graph, as this is autodetected from the predicate in RLog parsing if (s == null || p == null || o == null) { throw new IllegalArgumentException("Bad Krule structure. Incomplete constraint in transitive constraint."); } Node newVar = getUnusedVar(); out.append(toPredicate(s, p, newVar)); out.append(", "); out.append(toPredicate(newVar, p, o)); } /** * Finds a variable that is not in use in the current constraint. * @return A variable this is not being used in the current rule. */ Node getUnusedVar() { for (char v = 'A'; v <= 'Z'; v++) { Node var = varByName.get(Character.toString(v)); if (var != null) return var; } // every variable is in use somewhere. Have to search the current rule. Set<Node> currentVariables = getVariables(currentRootConstraint); for (Node var: nameByVar.keySet()) { if (!currentVariables.contains(var)) return var; } throw new IllegalStateException("Rule is too complex. It contains too many variables."); } /** * Accumulates all of the variables under a given node. * @param constraint The constraint node to find variables under. * @return A Set of nodes which represent variables. */ Set<Node> getVariables(Node constraint) { Node type = typeByNode.get(constraint); if (type.equals(Krule.SIMPLE_CONSTRAINT)) { return getVariablesSimple(constraint); } else if (type.equals(Krule.CONSTRAINT_CONJUNCTION)) { return getVariablesConjunction(constraint); } else if (type.equals(Krule.DIFFERENCE)) { return getVariablesDifference(constraint); } else if (type.equals(Krule.TRANSITIVE_CONSTRAINT)) { return getVariablesTransitive(constraint); } else { throw new IllegalArgumentException("Bad Krule structure. Unsupported Constraint type: " + type); } } /** * Accumulates all of the variables in a simple constraint. * @param constraint The constraint node to find variables under. * @return A Set of nodes which represent variables. */ Set<Node> getVariablesSimple(Node constraint) { Node s = getPropertyValue(constraint, Krule.HAS_SUBJECT); Node p = getPropertyValue(constraint, Krule.HAS_PREDICATE); Node o = getPropertyValue(constraint, Krule.HAS_OBJECT); if (s == null || p == null || o == null) { throw new IllegalArgumentException("Bad Krule structure. Incomplete constraint."); } Set<Node> vars = new HashSet<Node>(); if (nameByVar.containsKey(s)) vars.add(s); if (nameByVar.containsKey(p)) vars.add(p); if (nameByVar.containsKey(o)) vars.add(o); return vars; } /** * Accumulates all of the variables in a conjunction. * @param constraint The constraint node to find variables under. * @return A Set of nodes which represent variables. */ Set<Node> getVariablesConjunction(Node constraint) { // Recursively get the variables from the arguments Map<URIReference,Set<Node>> constraintProps = graph.get(constraint); if (constraintProps == null) throw new IllegalArgumentException("Bad Krule structure. Missing arguments for a conjunction."); Set<Node> args = constraintProps.get(Krule.ARGUMENT); // accumulate the variables into the first result. This is not functional, but more efficient. Set<Node> vars = null; for (Node c: args) { Set<Node> tmp = getVariables(c); if (vars == null) vars = tmp; else vars.addAll(tmp); } return vars; } /** * Accumulates all of the variables in a difference. * @param constraint The constraint node to find variables under. * @return A Set of nodes which represent variables. */ Set<Node> getVariablesDifference(Node constraint) { // Recursively get the variables from the arguments Node minuend = getPropertyValue(constraint, Krule.MINUEND); Node subtrahend = getPropertyValue(constraint, Krule.SUBTRAHEND); if (minuend == null) throw new IllegalArgumentException("Bad Krule structure. Missing minuend on a Difference."); if (subtrahend == null) throw new IllegalArgumentException("Bad Krule structure. Missing subtrahend on a Difference."); Set<Node> vars = getVariables(minuend); vars.addAll(getVariables(subtrahend)); // not quite functional, but more efficient return vars; } /** * Accumulates all of the variables in a transitive constraint. * @param constraint The constraint node to find variables under. * @return A Set of nodes which represent variables. */ Set<Node> getVariablesTransitive(Node constraint) { // Recursively get the variables from the arguments Node arg = getPropertyValue(constraint, Krule.TRANSITIVE_ARGUMENT); if (arg == null || !arg.equals(Krule.SIMPLE_CONSTRAINT)) { throw new IllegalArgumentException("Bad Krule structure. Transitive constraints must operate on simple arguments."); } return getVariables(arg); } /** * Converts a triple to a string containing a DL predicate. * @param s The subject of the triple. * @param p The predicate of the triple. * @param o The object of the triple. * @return A string with the DL version of the predicate. */ String toPredicate(Node s, Node p, Node o) { if (isType(p)) return toTypePredicate(s, o); return toBinaryPredicate(s, p, o); } /** * Convert a subject and type into a Type predicate. * @param s The subject to be typed. * @param type The type of the subject. * @return A string with the subject and type. */ String toTypePredicate(Node s, Node type) { StringBuilder sb = new StringBuilder(); sb.append(toString(type)); sb.append("("); sb.append(toString(s)); sb.append(")"); return sb.toString(); } /** * Convert a triple into a binary predicate. * @param s The subject of the triple. * @param p The predicate of the triple. * @param o The object of the triple. * @return A string with the DL version of the binary predicate. */ String toBinaryPredicate(Node s, Node p, Node o) { StringBuilder sb = new StringBuilder(); sb.append(toString(p)); sb.append("("); sb.append(toString(s)); sb.append(","); sb.append(toString(o)); sb.append(")"); return sb.toString(); } /** * Gets the string representation of an object referenced by URIReference. * @param n The node to get the value of. * @return A string representation of the object. */ String toString(Node n) { Node refType = typeByNode.get(n); // Variable, URIReference or Literal if (refType == null) throw new IllegalArgumentException("Bad Krule structure. No type for node: " + n); if (!(refType instanceof URIReference)) throw new IllegalArgumentException("Bad Krule structure. Expected a reference for type, but got: " + refType); // Get the referenced data if (refType.equals(Krule.URI_REF)) { // writing a URI Node value = getPropertyValue(n, RDF_VALUE); if (value == null) throw new IllegalArgumentException("Bad Krule structure. null value for: " + n); if (!(value instanceof URIReference)) throw new IllegalArgumentException("Bad Krule structure. URIReference came back with the wrong type: " + value + "(" + value.getClass().getName() + ")"); return namespaceString((URIReference)value); } else if (refType.equals(Krule.VARIABLE)) { return nameByVar.get(n); } else if (refType.equals(Krule.LITERAL)) { // writing a literal Node value = getPropertyValue(n, RDF_VALUE); if (!(value instanceof Literal)) throw new IllegalArgumentException("Bad Krule structure. Literal came back with the wrong type: " + value + "(" + value.getClass().getName() + ")"); return value.toString(); } else throw new IllegalArgumentException("Bad Krule structure. Output node is not a URI Reference, a variable, or a literal: " + refType); } /** * Tests if a node is a reference to the rdf:type URI. * @param n The node to check as a reference. * @return <code>true</code> only if the node is a reference and it refers to rdf:type */ boolean isType(Node n) { Node refType = typeByNode.get(n); // Variable, URIReference or Literal if (refType == null) throw new IllegalArgumentException("Bad Krule structure. No type for node: " + n); if (!(refType instanceof URIReference)) throw new IllegalArgumentException("Bad Krule structure. Expected a reference for type, but got: " + refType); // Get the referenced data if (refType.equals(Krule.URI_REF)) { // writing a URI Node value = getPropertyValue(n, RDF_VALUE); if (value == null) throw new IllegalArgumentException("Bad Krule structure. Null value for: " + n); if (!(value instanceof URIReference)) throw new IllegalArgumentException("Bad Krule structure. URIReference came back with the wrong type: " + value + "(" + value.getClass().getName() + ")"); return value.equals(TYPE); } return false; } /** * Get the namespace version of a URI. * e.g. rdf:value instead of http://www.w3.org/1999/02/22-rdf-syntax-ns#value * @param r The URI reference to convert to namespace form. * @return The namespaced version of the URI. */ String namespaceString(URIReference r) { Pair<String,String> nsPair = addToNamespace(r); String dom = nsPair.first(); String val = nsPair.second(); // Krule is the default namespace if (dom.equals(Krule.KRULE)) return val; return ((dom.endsWith(":")) ? dom : dom + ":") + val; } /** * Scans nodes that are URIReferences, and adds them to the list of namespaces if not already there. * @param n A node that may be a reference to be scanned. */ void addToNamespace(Node n) { if (!(n instanceof URIReference)) return; addToNamespace((URIReference)n); } /** * Scans a URIReference, and adds it to the list of namespaces if not already there. * @param r The reference to be scanned. * @return The domain abbreviation to use for this reference, and the value in the domain as a Pair. */ Pair<String,String> addToNamespace(URIReference r) { Pair<String,String> nsPair = splitUri(r.getURI()); String ns = nsPair.first(); String val = nsPair.second(); if (val.length() == 0) throw new IllegalArgumentException("Bad RLog data. URI needs a value in a domain to be serialized in RLog: " + r); // determine the domain to use for this namespace String dom = defaultNamespaces.get(ns); if (dom == null) { dom = namespaces.get(ns); if (dom == null) { namespaces.put(ns, dom = namespaceGen.newNamespace()); } } else { // domain is in the default list, add it to the in-use list if (!namespaces.containsKey(ns)) namespaces.put(ns, dom); } return new Pair<String,String>(dom, val); } /** * Gets the reference for a sequence member predicate. * @param n The sequence index. * @return The URI reference for <em>rdf:_n</em>. */ URIReference getListId(int n) { if (listIds.size() < n) { for (int i = listIds.size() + 1; i <= n; i++) { URI u = URI.create(RDF.BASE_URI + "_" + i); listIds.add(new URIReferenceImpl(u)); } } return listIds.get(n - 1); } /** * Convert a literal into a valid variable name. Variable names are a single upper case character. * @param value The literal to convert. * @return The validated, and converted name of the variable. */ String getValidVariableName(Literal value) { String var = ((Literal)value).getLexicalForm(); if (var.length() != 1) { String v = var.substring(0, 1).toUpperCase(); logger.warn("Krule structure uses long variable names. Truncating: " + var + " -> " + v); var = v; } if (!Character.isLetter(var.charAt(0))) throw new IllegalArgumentException("Bad Krule structure. Variable must be a letter: " + var); if (!Character.isUpperCase(var.charAt(0))) { logger.warn("Variable names must have upper case letters: " + var); var = var.toUpperCase(); } return var; } /** * Gets the value of a property on an object. * @param s The object in the graph. * @param p The property being looked for. * @return The value of the required property. */ Node getPropertyValue(Node s, URIReference p) { Map<URIReference,Set<Node>> values = graph.get(s); if (values == null) throw new IllegalArgumentException("Bad Krule structure. Missing property <" + p + "> on object <" + s + ">"); return getSingle(values, p); } /** * Map an object to a property value pair. * @param subj The subject to set a property and value for. * @param property The property for the subject. * @param value The value for the subject's property. */ static final void addPropertyValue(Map<Node,Map<URIReference,Set<Node>>> graph, Node subj, URIReference property, Node value) { Map<URIReference,Set<Node>> propVal = graph.get(subj); if (propVal == null) { propVal = new HashMap<URIReference,Set<Node>>(); graph.put(subj, propVal); } addValue(propVal, property, value); } /** * Map a property to a value. * @param propertyValues The full set of properties and values for the current object. * @param property The property to set on the current object. * @param value The value to set the property to. */ static final <T> void addValue(Map<T,Set<Node>> propertyValues, T property, Node value) { Set<Node> values = propertyValues.get(property); if (values == null) { values = new HashSet<Node>(); propertyValues.put(property, values); } values.add(value); } /** * Retrieves a single value from a multimap. * @param <K> The type of the keys in the multimap. * @param <V> The type of the values in the multimap. * @param map The map to get the value from. * @param key The key to find the value with. * @return The single value found associated with the key, or null if not found. * @throws IllegalArgumentException If the key maps to a set with more than one value. */ static final <K,V> V getSingle(Map<K,Set<V>> map, K key) { Set<V> vals = map.get(key); if (vals == null || vals.size() == 0) return null; if (vals.size() != 1) throw new IllegalArgumentException("Expecting singleton from a set of size: " + vals.size()); return vals.iterator().next(); } /** * Splits a URI into two sections. The second section contains the trailing identifier * that includes only letters, digits and underscores. The first section is everything else. * @param uri The URI to split. * @return A pair of strings containing the two parts of the original URI. */ static final Pair<String,String> splitUri(URI uri) { String u = uri.toString(); for (int p = u.length() - 1; p >= 0; p--) { char ch = u.charAt(p); if (!Character.isLetterOrDigit(ch) && ch != '_' && ch != '-') { p = p + 1; return new Pair<String,String>(u.substring(0, p), u.substring(p)); } } return new Pair<String,String>("", u); } /** * This class is a utility to generate new namespace names. */ class NamespaceGenerator { /** The namespace counter */ int n = 1; /** * Create a new name for a namespace. * @return A new name. */ String newNamespace() { return "ns" + n++; } } }