Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*******************************************************************************
* Copyright (c) 2021 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.model.util;
import static org.eclipse.rdf4j.model.util.Values.bnode;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.DynamicModelFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
/**
* Functions for canonicalizing RDF models and computing isomorphism.
*
* @author Jeen Broekstra
* @implNote The algorithms used in this class are based on the iso-canonical algorithm as described in: Hogan, A.
* (2017). Canonical forms for isomorphic and equivalent RDF graphs: algorithms for leaning and labelling
* blank nodes. ACM Transactions on the Web (TWEB), 11(4), 1-62.
*/
class GraphComparisons {
private static final Logger logger = LoggerFactory.getLogger(GraphComparisons.class);
private static final HashFunction hashFunction = Hashing.sha256();
private static final HashCode initialHashCode = hashFunction.hashString("", StandardCharsets.UTF_8);
private static final HashCode outgoing = hashFunction.hashString("+", StandardCharsets.UTF_8);
private static final HashCode incoming = hashFunction.hashString("-", StandardCharsets.UTF_8);
private static final HashCode distinguisher = hashFunction.hashString("@", StandardCharsets.UTF_8);
/**
* Compares two RDF models, and returns true if they consist of isomorphic graphs and the isomorphic
* graph identifiers map 1:1 to each other. RDF graphs are isomorphic graphs if statements from one graphs can be
* mapped 1:1 on to statements in the other graphs. In this mapping, blank nodes are not considered mapped when
* having an identical internal id, but are mapped from one graph to the other by looking at the statements in which
* the blank nodes occur. A Model can consist of more than one graph (denoted by context identifiers). Two models
* are considered isomorphic if for each of the graphs in one model, an isomorphic graph exists in the other model,
* and the context identifiers of these graphs are identical.
*
* @implNote The algorithm used by this comparison is a depth-first search for an iso-canonical blank node mapping
* for each model, and using that as a basis for comparison. The algorithm is described in detail in:
* Hogan, A. (2017). Canonical forms for isomorphic and equivalent RDF graphs: algorithms for leaning and
* labelling blank nodes. ACM Transactions on the Web (TWEB), 11(4), 1-62.
* @see RDF Concepts & Abstract Syntax, section
* 3.6 (Graph Comparison)
* @see Hogan, A. (2017). Canonical forms for
* isomorphic and equivalent RDF graphs: algorithms for leaning and labelling blank nodes. ACM Transactions on
* the Web (TWEB), 11(4), 1-62. Technical Paper (PDF )
*/
public static boolean isomorphic(Model model1, Model model2) {
if (model1 == model2) {
return true;
}
if (model1.size() != model2.size()) {
return false;
}
if (model1.contexts().size() != model2.contexts().size()) {
return false;
}
if (model1.contexts().size() > 1) {
// model contains more than one context (including the null context). We compare per individual context.
for (Resource context : model1.contexts()) {
Model contextInModel1 = model1.filter(null, null, null, context);
if (context != null && context.isBNode()) {
// context identifier is a blank node. We to find blank node identifiers in the other model that map
// iso-canonically.
Map mapping1 = getIsoCanonicalMapping(model1);
Multimap partitionMapping2 = partitionMapping(getIsoCanonicalMapping(model2));
Collection contextCandidates = partitionMapping2.get(mapping1.get(context));
if (contextCandidates.isEmpty()) {
return false;
}
boolean foundIsomorphicBlankNodeContext = false;
for (BNode context2 : contextCandidates) {
Model contextInModel2 = model2.filter(null, null, null, context2);
if (contextInModel1.size() != contextInModel2.size()) {
continue;
}
if (isomorphicSingleContext(contextInModel1, contextInModel2)) {
foundIsomorphicBlankNodeContext = true;
break;
}
}
if (!foundIsomorphicBlankNodeContext) {
return false;
}
} else {
// context identifier is an iri. Simple per-context check will suffice.
Model contextInModel2 = model2.filter(null, null, null, context);
if (contextInModel1.size() != contextInModel2.size()) {
return false;
}
final Model canonicalizedContext1 = isoCanonicalize(contextInModel1);
final Model canonicalizedContext2 = isoCanonicalize(contextInModel2);
if (!canonicalizedContext1.equals(canonicalizedContext2)) {
return false;
}
}
}
return true;
} else {
// only one context (the null context), so we're dealing with one graph only.
return isomorphicSingleContext(model1, model2);
}
}
private static boolean isomorphicSingleContext(Model model1, Model model2) {
final Map mapping1 = getIsoCanonicalMapping(model1);
if (mapping1.isEmpty()) {
// no blank nodes in model1 - simple collection equality will do
return model1.equals(model2);
}
final Map mapping2 = getIsoCanonicalMapping(model2);
if (mappingsIncompatible(mapping1, mapping2)) {
return false;
}
// Compatible blank node mapping found. We need to check that statements not involving blank nodes are equal in
// both models.
Optional missingInModel2 = model1.stream()
.filter(st -> !(st.getSubject().isBNode() || st.getObject().isBNode()
|| st.getContext() instanceof BNode))
.filter(st -> !model2.contains(st))
.findAny();
// Because we have previously already checked that the models are the same size, we don't have to check both
// ways to establish model equality.
return !missingInModel2.isPresent();
}
private static boolean mappingsIncompatible(Map mapping1, Map mapping2) {
if (mapping1.size() != mapping2.size()) {
return true;
}
Set values1 = new HashSet<>(mapping1.values());
Set values2 = new HashSet<>(mapping2.values());
if (!(values1.equals(values2))) {
return true;
}
return false;
}
protected static Model isoCanonicalize(Model m) {
return labelModel(m, getIsoCanonicalMapping(m));
}
protected static Map getIsoCanonicalMapping(Model m) {
Partitioning partitioning = hashBNodes(m);
if (partitioning.isFine()) {
return partitioning.getCurrentNodeMapping();
}
return distinguish(m, partitioning, null, new ArrayList<>(), new ArrayList<>());
}
protected static Set getBlankNodes(Model m) {
final Set blankNodes = new HashSet<>();
m.forEach(st -> {
if (st.getSubject().isBNode()) {
blankNodes.add((BNode) st.getSubject());
}
if (st.getObject().isBNode()) {
blankNodes.add((BNode) st.getObject());
}
if (st.getContext() != null && st.getContext().isBNode()) {
blankNodes.add((BNode) st.getContext());
}
});
return blankNodes;
}
private static Map distinguish(Model m, Partitioning partitioning,
Map lowestFound, List parentFixpoints,
List