/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.jena.reasoner.test; import java.util.* ; import org.apache.jena.graph.Graph ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.NodeFactory ; import org.apache.jena.graph.Triple ; import org.apache.jena.shared.JenaException ; import org.slf4j.Logger ; import org.slf4j.LoggerFactory ; public class Matcher { private static Logger log = LoggerFactory.getLogger("Matcher") ; static boolean logging = false ; private static Allocator anyizer = new AllocatorAny() ; /** * Subgraph isomorphism. * Like a BGP match except you can only bind to bNodes. */ public static boolean subgraphIsomorphism(Graph subgraph, Graph graph) { // BNode subgraph isomorphism. For small graphs. List<Triple> pattern = bnodes2vars(subgraph) ; return match(pattern, graph, false).hasNext(); } /** * Subgraph inferred by . * Like a BGP match except you can only bind to bNodes. */ public static boolean subgraphInferred(Graph subgraph, Graph graph) { // BNode subgraph isomorphism. For small graphs. List<Triple> pattern = bnodes2vars(subgraph) ; return match(pattern, graph, true).hasNext(); } /*package*/ static List<Triple> bnodes2vars(Graph graph) { Map<Node, Node> bnodeMapping = new HashMap<>(); Allocator allocator = new AllocatorBlankVar() ; List<Triple> pattern = remap(bnodeMapping, graph, allocator) ; return pattern ; } private static Iterator<Map<Node, Node>> match(List<Triple> pattern, Graph graph, boolean bindAny) { List<Map<Node, Node>> solutions = new ArrayList<>() ; solutions.add(new HashMap<Node, Node>()) ; // Root binding. return solve(solutions, pattern, graph, bindAny) ; } private static Iterator<Map<Node, Node>> solve(List<Map<Node, Node>> solutions , List<Triple> pattern, Graph graph, boolean bindAny) { log("Solve: %s", pattern) ; if ( pattern.size() == 0 ) { log("Solve: Result: %s", solutions) ; return solutions.iterator() ; } Triple step = pattern.get(0) ; List<Map<Node, Node>> solutions2 = new ArrayList<>() ; for ( Map<Node, Node> binding : solutions ) { Triple gStep = remap(binding, step, anyizer) ; log("Solve: %s => %s", step, gStep) ; Iterator<Triple> iter = graph.find(gStep) ; while ( iter.hasNext() ) { Triple t = iter.next() ; log("Solve: %s -> %s", step, t) ; Map<Node, Node> newBinding = bind(step, t, binding, bindAny) ; if ( newBinding == null ) continue ; log("Solve: soln: %s", newBinding) ; solutions2.add(newBinding) ; } } List<Triple> nextPattern = pattern.subList(1, pattern.size()) ; return solve(solutions2, nextPattern, graph, bindAny) ; } private static Map<Node, Node> bind(Triple step, Triple t, Map<Node, Node> bindings, boolean bindAny) { log("Bind: %s :: %s",step,t) ; HashMap<Node, Node> newBinding = new HashMap<>() ; newBinding.putAll(bindings) ; if ( ! process(newBinding, t.getSubject(), step.getSubject(), bindAny )) return null ; if ( ! process(newBinding, t.getPredicate(), step.getPredicate(), bindAny )) return null ; if ( ! process(newBinding, t.getObject(), step.getObject(), bindAny )) return null ; log("Bind: %s",newBinding) ; return newBinding ; } private static boolean process(Map<Node, Node> results, Node dataNode, Node varNode, boolean bindAny) { if ( ! varNode.isVariable() ) { if ( ! dataNode.sameValueAs(varNode) ) throw new JenaException("Internal error in Matcher") ; return true ; } Node x = results.get(varNode) ; if ( x != null ) // Bound already. Match? return dataNode.equals(x) ; // Isomorphism - must be a bNode. // Infered, can be anything. if ( !bindAny && ! dataNode.isBlank() ) return false ; results.put(varNode, dataNode) ; return true ; } private static List<Triple> remap(Map<Node, Node> bnodeMapping, Graph g, Allocator alloc) { List<Triple> triples = g.find(Node.ANY, Node.ANY, Node.ANY).toList() ; return remap(bnodeMapping, triples, alloc) ; } private static List<Triple> remap(Map<Node, Node> bnodeMapping, List<Triple>triples, Allocator alloc) { List<Triple> pattern = new ArrayList<>() ; for ( Triple t : triples ) { Triple t2 = remap(bnodeMapping, t, alloc) ; pattern.add(t2) ; } return pattern ; } private static Triple remap(Map<Node, Node> bnodeMapping, Triple t, Allocator alloc) { Node s = t.getSubject() ; Node p = t.getPredicate() ; Node o = t.getObject() ; return new Triple(remap(bnodeMapping,s,alloc), remap(bnodeMapping,p,alloc), remap(bnodeMapping,o,alloc) ) ; } private static Node remap(Map<Node, Node> mapping, Node n, Allocator alloc) { // caution if ( ! n.isBlank() && ! n.isVariable() ) return n ; if ( mapping.containsKey(n) ) return mapping.get(n) ; Node n2 = alloc.allocate() ; alloc.update(mapping, n, n2) ; return n2 ; } private static void log(String fmt, Object... args) { if ( logging && log.isInfoEnabled() ) { String x = String.format(fmt, args) ; log.info(x) ; } } private interface Allocator { boolean test(Node n) ; Node allocate() ; void update(Map<Node, Node> mapping, Node inNode, Node allocNode) ; } private static class AllocatorBlankVar implements Allocator { int counter = 0 ; @Override public Node allocate() { return NodeFactory.createVariable("v"+(counter++)) ; } @Override public boolean test(Node n) { return n.isBlank() ; } @Override public void update(Map<Node, Node> mapping, Node inNode, Node allocNode) { mapping.put(inNode, allocNode) ; } } private static class AllocatorAny implements Allocator { int counter = 0 ; @Override public Node allocate() { return Node.ANY ; } @Override public boolean test(Node n) { return n.isVariable() ; } @Override public void update(Map<Node, Node> mapping, Node inNode, Node allocNode) { } } }