/* Licensed to Aduna under one or more contributor license agreements. * See the NOTICE.txt file distributed with this work for additional * information regarding copyright ownership. Aduna licenses this file * to you under the terms of the Aduna BSD License (the "License"); * you may not use this file except in compliance with the License. See * the LICENSE.txt file distributed with this work for the full License. * * 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.openrdf.model.util; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openrdf.model.BNode; import org.openrdf.model.Resource; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.util.iterators.Iterators; /** * @author Arjohn Kampman */ public class ModelUtil { /** * Compares two models, defined by two statement collections, and returns * <tt>true</tt> if they are equal. Models are equal if they contain the same * set of statements. Blank node IDs are not relevant for model equality, * they are mapped from one model to the other by using the attached * properties. */ public static boolean equals(Iterable<? extends Statement> model1, Iterable<? extends Statement> model2) { // Filter duplicates Set<Statement> set1 = new LinkedHashSet<Statement>(); Iterators.addAll(model1.iterator(), set1); Set<Statement> set2 = new LinkedHashSet<Statement>(); Iterators.addAll(model2.iterator(), set2); return equals(set1, set2); } /** * Compares two models, defined by two statement collections, and returns * <tt>true</tt> if they are equal. Models are equal if they contain the same * set of statements. Blank node IDs are not relevant for model equality, * they are mapped from one model to the other by using the attached * properties. */ public static boolean equals(Set<? extends Statement> model1, Set<? extends Statement> model2) { // Compare the number of statements in both sets if (model1.size() != model2.size()) { return false; } return isSubsetInternal(model1, model2); } /** * Compares two models, defined by two statement collections, and returns * <tt>true</tt> if the first model is a subset of the second model. */ public static boolean isSubset(Iterable<? extends Statement> model1, Iterable<? extends Statement> model2) { // Filter duplicates Set<Statement> set1 = new LinkedHashSet<Statement>(); Iterators.addAll(model1.iterator(), set1); Set<Statement> set2 = new LinkedHashSet<Statement>(); Iterators.addAll(model2.iterator(), set2); return isSubset(set1, set2); } /** * Compares two models, defined by two statement collections, and returns * <tt>true</tt> if the first model is a subset of the second model. */ public static boolean isSubset(Set<? extends Statement> model1, Set<? extends Statement> model2) { // Compare the number of statements in both sets if (model1.size() > model2.size()) { return false; } return isSubsetInternal(model1, model2); } private static boolean isSubsetInternal(Set<? extends Statement> model1, Set<? extends Statement> model2) { // try to create a full blank node mapping return matchModels(model1, model2); } private static boolean matchModels(Set<? extends Statement> model1, Set<? extends Statement> model2) { // Compare statements without blank nodes first, save the rest for later List<Statement> model1BNodes = new ArrayList<Statement>(model1.size()); for (Statement st : model1) { if (st.getSubject() instanceof BNode || st.getObject() instanceof BNode) { model1BNodes.add(st); } else { if (!model2.contains(st)) { return false; } } } return matchModels(model1BNodes, model2, new HashMap<BNode, BNode>(), 0); } /** * A recursive method for finding a complete mapping between blank nodes in * model1 and blank nodes in model2. The algorithm does a depth-first search * trying to establish a mapping for each blank node occurring in model1. * * @param model1 * @param model2 * @param bNodeMapping * @param idx * @return true if a complete mapping has been found, false otherwise. */ private static boolean matchModels(List<? extends Statement> model1, Iterable<? extends Statement> model2, Map<BNode, BNode> bNodeMapping, int idx) { boolean result = false; if (idx < model1.size()) { Statement st1 = model1.get(idx); List<Statement> matchingStats = findMatchingStatements(st1, model2, bNodeMapping); for (Statement st2 : matchingStats) { // Map bNodes in st1 to bNodes in st2 Map<BNode, BNode> newBNodeMapping = new HashMap<BNode, BNode>(bNodeMapping); if (st1.getSubject() instanceof BNode && st2.getSubject() instanceof BNode) { newBNodeMapping.put((BNode)st1.getSubject(), (BNode)st2.getSubject()); } if (st1.getObject() instanceof BNode && st2.getObject() instanceof BNode) { newBNodeMapping.put((BNode)st1.getObject(), (BNode)st2.getObject()); } // FIXME: this recursive implementation has a high risk of // triggering a stack overflow // Enter recursion result = matchModels(model1, model2, newBNodeMapping, idx + 1); if (result == true) { // models match, look no further break; } } } else { // All statements have been mapped successfully result = true; } return result; } private static List<Statement> findMatchingStatements(Statement st, Iterable<? extends Statement> model, Map<BNode, BNode> bNodeMapping) { List<Statement> result = new ArrayList<Statement>(); for (Statement modelSt : model) { if (statementsMatch(st, modelSt, bNodeMapping)) { // All components possibly match result.add(modelSt); } } return result; } private static boolean statementsMatch(Statement st1, Statement st2, Map<BNode, BNode> bNodeMapping) { URI pred1 = st1.getPredicate(); URI pred2 = st2.getPredicate(); if (!pred1.equals(pred2)) { // predicates don't match return false; } Resource subj1 = st1.getSubject(); Resource subj2 = st2.getSubject(); if (subj1 instanceof BNode && subj2 instanceof BNode) { BNode mappedBNode = bNodeMapping.get(subj1); if (mappedBNode != null) { // bNode 'subj1' was already mapped to some other bNode if (!subj2.equals(mappedBNode)) { // 'subj1' and 'subj2' do not match return false; } } else { // 'subj1' was not yet mapped. we need to check if 'subj2' is a // possible mapping candidate if (bNodeMapping.containsValue(subj2)) { // 'subj2' is already mapped to some other value. return false; } } } else { // subjects are not (both) bNodes if (!subj1.equals(subj2)) { return false; } } Value obj1 = st1.getObject(); Value obj2 = st2.getObject(); if (obj1 instanceof BNode && obj2 instanceof BNode) { BNode mappedBNode = bNodeMapping.get(obj1); if (mappedBNode != null) { // bNode 'obj1' was already mapped to some other bNode if (!obj2.equals(mappedBNode)) { // 'obj1' and 'obj2' do not match return false; } } else { // 'obj1' was not yet mapped. we need to check if 'obj2' is a // possible mapping candidate if (bNodeMapping.containsValue(obj2)) { // 'obj2' is already mapped to some other value. return false; } } } else { // objects are not (both) bNodes if (!obj1.equals(obj2)) { return false; } } return true; } }