All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jena.ontapi.impl.UnionGraphImpl Maven / Gradle / Ivy

The newest version!
/*
 * 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.ontapi.impl;

import org.apache.jena.ontapi.UnionGraph;
import org.apache.jena.ontapi.utils.Graphs;
import org.apache.jena.ontapi.utils.Iterators;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.GraphEventManager;
import org.apache.jena.graph.GraphEvents;
import org.apache.jena.graph.GraphListener;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.graph.compose.CompositionBase;
import org.apache.jena.graph.impl.SimpleEventManager;
import org.apache.jena.shared.PrefixMapping;
import org.apache.jena.util.iterator.ExtendedIterator;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Stream;

/**
 * UnionGraph.
 * 

* It consists of two parts: a {@link #base base graph} and an {@link UnionGraphImpl.SubGraphs sub-graphs} collection. * Unlike {@link org.apache.jena.graph.compose.MultiUnion MultiUnion} this implementation explicitly requires primary (base) graph. * Underlying sub-graphs are only used for searching; modify operations are performed only on the base graph. * This graph allows building graph hierarchy which can be used to link different models. * Also, it allows recursion, that is, it can contain itself somewhere in the hierarchy. * The {@link PrefixMapping} of this graph is taken from the base graph, * and, therefore, any changes in it reflect both the base and this graph. */ @SuppressWarnings({"WeakerAccess"}) public class UnionGraphImpl extends CompositionBase implements UnionGraph { protected final Graph base; protected final SubGraphs subGraphs; protected final boolean distinct; /** * A set of parents, used when collecting cache {@link #descendantBases}, * which is used in read operations. * This allows the {@code UnionGraphImpl} instance * to know when the structure has changed to rebuild the cache. * Items of this {@code Set} are removed automatically by GC * if there are no more strong references (a graph/model is removed, i.e. there is no usage anymore). */ protected final Set parents = Collections.newSetFromMap(new WeakHashMap<>()); /** * Internal cache to hold all data graphs with except of {@link #base}, * used while {@link Graph#find(Triple) #find(..)}. * This {@code Set} cannot contain {@link UnionGraph}s. */ protected Set descendantBases; /** * Creates an instance with default settings. *

* Note: it results a distinct graph (i.e. its parameter {@link #distinct} is {@code true}). * This means that the method {@link #find(Triple)} does not produce duplicates. * The additional duplicate checking may lead to temporary writing * the whole graph or some its part into memory in the form of {@code Set}, * and for huge ontologies it is unacceptable. * This checking is not performed if the graph is single (the underlying part is empty). *

* Also notice, a top-level ontology view of in-memory graph is not sensitive to the distinct parameter * since it uses only base graphs to collect axiomatic data. * * @param base {@link Graph}, not {@code null} */ public UnionGraphImpl(Graph base) { this(base, true); } /** * Creates a graph for the given {@code base}, * which will be either distinct or non-distinct, depending on the second parameter. * Other settings are default. * * @param base {@link Graph}, not {@code null} * @param distinct if {@code true} a distinct graph is created */ public UnionGraphImpl(Graph base, boolean distinct) { this(base, new EventManagerImpl(), distinct); } /** * Creates a graph for the given {@code base}. * * @param base {@link Graph}, not {@code null} * @param eventManager {@link UnionGraph.EventManager} or {@code null} to use default fresh event manager * @param distinct if {@code true} a distinct graph is created */ @SuppressWarnings("javadoc") public UnionGraphImpl(Graph base, EventManager eventManager, boolean distinct) { this(base, new SubGraphs(), eventManager, distinct); } /** * The base constructor. * A well-formed ontology {@link UnionGraph} is expected to * have a plain (non-union) graph as a {@link #getBaseGraph() root} * and {@code UnionGraph} as {@link #getSubGraphs() leaves}. * * @param base {@link Graph}, not {@code null} * @param subGraphs {@link SubGraphs} or {@code null} to use default empty sub-graph container * @param eventManager {@link EventManager} or {@code null} to use default fresh event manager * @param distinct if {@code true}, the method {@link #find(Triple)} returns an iterator avoiding duplicates * @throws NullPointerException if base graph is {@code null} */ protected UnionGraphImpl(Graph base, SubGraphs subGraphs, EventManager eventManager, boolean distinct) { this.base = Objects.requireNonNull(base, "Null base graph."); this.subGraphs = Objects.requireNonNull(subGraphs, "Null SubGraphs"); this.gem = Objects.requireNonNull(eventManager, "Null EventManager"); this.distinct = distinct; } @Override public PrefixMapping getPrefixMapping() { return getBaseGraph().getPrefixMapping(); } /** * Answers the ont event manager for this graph. * Override to use in {@link org.apache.jena.graph.impl.GraphBase#add(Triple)}. * * @return {@link GraphEventManager}, not {@code null} */ @Override public EventManager getEventManager() { return (EventManager) gem; } /** * Answers {@code true} iff this graph is distinct. * See {@link #UnionGraphImpl(Graph)} description. * * @return boolean */ @Override public boolean isDistinct() { return distinct; } /** * Returns the base (primary) graph. * * @return {@link Graph}, not {@code null} */ @Override public Graph getBaseGraph() { return base; } /** * Returns the underlying graph, possible empty. * * @return {@link SubGraphs}, not {@code null} */ public SubGraphs getSubGraphs() { return subGraphs; } @Override public boolean hasSubGraph() { return !getSubGraphs().isEmpty(); } @Override public Stream subGraphs() { return getSubGraphs().graphs(); } @Override public Stream superGraphs() { return parents.stream().map(it -> it); } @Override public void performAdd(Triple t) { getEventManager().onAddTriple(this, t); if (!subGraphs.contains(t)) { base.add(t); } } @Override public void performDelete(Triple t) { getEventManager().onDeleteTriple(this, t); base.delete(t); } @Override public void remove(Node s, Node p, Node o) { checkOpen(); Triple t = Triple.createMatch(s, p, o); UnionGraph.EventManager em = getEventManager(); em.onDeleteTriple(this, t); super.remove(s, p, o); em.notifyEvent(this, GraphEvents.remove(s, p, o)); } @Override public void clear() { checkOpen(); UnionGraph.EventManager em = getEventManager(); em.onClear(this); base.clear(); em.notifyEvent(this, GraphEvents.removeAll); } /** * Adds the specified graph to the underlying graph collection. * Note: for a well-formed ontological {@code UnionGraph} * the input {@code graph} must be also a {@code UnionGraph}, even it has no hierarchy structure. * * @param graph {@link Graph}, not {@code null} * @return this instance */ @Override public UnionGraph addSubGraph(Graph graph) { Objects.requireNonNull(graph); checkOpen(); EventManager eventManager = getEventManager(); eventManager.onAddSubGraph(this, graph); getSubGraphs().add(graph); addParent(graph); resetGraphsCache(); eventManager.notifySubGraphAdded(this, graph); if (graph instanceof UnionGraph subGraph) { subGraph.getEventManager().notifySuperGraphAdded(subGraph, this); } return this; } protected void addParent(Graph graph) { if (!(graph instanceof UnionGraphImpl)) { return; } ((UnionGraphImpl) graph).parents.add(this); } /** * Removes the specified graph from the underlying graph collection. * * @param graph {@link Graph}, not {@code null} * @return this instance */ @Override public UnionGraph removeSubGraph(Graph graph) { Objects.requireNonNull(graph); checkOpen(); EventManager eventManager = getEventManager(); eventManager.onRemoveSubGraph(this, graph); getSubGraphs().remove(graph); removeUnion(graph); resetGraphsCache(); eventManager.notifySubGraphRemoved(this, graph); return this; } protected void removeUnion(Graph graph) { if (!(graph instanceof UnionGraphImpl)) { return; } ((UnionGraphImpl) graph).parents.remove(this); } /** * Clears the {@link #descendantBases cache}. */ protected void resetGraphsCache() { getAllLinkedUnionGraphs().forEach(x -> x.descendantBases = null); } /** * Lists all indivisible (base) data {@code Graph}s * that are encapsulated either in the hierarchy * or (which is possible) inside the {@link #getBaseGraph() base} (root) graph itself. * * @return distinct {@link ExtendedIterator} of {@link Graph}s, including the base graph * @see UnionGraph#getBaseGraph() */ public ExtendedIterator listSubGraphBases() { return Iterators.create(descendantBases == null ? descendantBases = getAllBaseGraphs() : descendantBases); } /** * Performs the find operation. * Override {@code graphBaseFind} to return an iterator that will report when a deletion occurs. * * @param m {@link Triple} the matcher to match against, not {@code null} * @return an {@link ExtendedIterator iterator} of all triples matching {@code m} in the union of the graphs * @see org.apache.jena.graph.compose.MultiUnion#graphBaseFind(Triple) */ @Override protected final ExtendedIterator graphBaseFind(Triple m) { return SimpleEventManager.notifyingRemove(this, createFindIterator(m)); } /** * Answers {@code true} if the graph contains any triple matching {@code t}. * * @param t {@link Triple}, not {@code null} * @return boolean * @see org.apache.jena.graph.compose.MultiUnion#graphBaseContains(Triple) */ @Override public boolean graphBaseContains(Triple t) { if (base.contains(t)) { return true; } if (subGraphs.isEmpty()) { return false; } Iterator graphs = listSubGraphBases(); while (graphs.hasNext()) { Graph g = graphs.next(); if (g == base) { continue; } if (g.contains(t)) { return true; } } return false; } @Override public int graphBaseSize() { if (subGraphs.isEmpty()) { return base.size(); } return super.graphBaseSize(); } @Override public boolean isEmpty() { if (subGraphs.isEmpty()) { return base.isEmpty(); } return Iterators.findFirst(find()).isEmpty(); } /** * Creates an extended iterator to be used in {@link Graph#find(Triple)}. * * @param m {@link Triple} pattern, not {@code null} * @return {@link ExtendedIterator} of {@link Triple}s * @see org.apache.jena.graph.compose.Union#_graphBaseFind(Triple) * @see org.apache.jena.graph.compose.MultiUnion#multiGraphFind(Triple) */ @SuppressWarnings("JavadocReference") protected ExtendedIterator createFindIterator(Triple m) { if (subGraphs.isEmpty()) { return base.find(m); } if (!distinct) { return Iterators.flatMap(listSubGraphBases(), x -> x.find(m)); } Set seen = createSet(); return Iterators.flatMap(listSubGraphBases(), x -> CompositionBase.recording(rejecting(x.find(m), seen), seen)); } /** * Creates a {@code Set} to be used while {@link Graph#find()}. * The returned set may contain a huge number of items. * And that's why this method has protected access - * implementations are allowed to override it for better performance. * * @return Set of {@link Triple}s */ protected Set createSet() { return new HashSet<>(); } /** * Closes the graph including all related graphs. * Caution: this is an irreversible operation, * once closed, a graph cannot be reopened. */ @Override public void close() { listSubGraphBases().forEachRemaining(Graph::close); getAllUnderlyingUnionGraphs().forEach(x -> x.closed = true); } /** * Generic dependsOn, returns {@code true} iff this graph or any sub-graphs depend on the specified graph. * * @param other {@link Graph} * @return boolean */ @Override public boolean dependsOn(Graph other) { return (other instanceof UnionGraphImpl && getAllUnderlyingUnionGraphs().contains(other)) || Iterators.anyMatch(listSubGraphBases(), x -> Graphs.dependsOn(x, other)); } /** * Calculates and returns a {@code Set} of all indivisible {@link Graph graph}s * that are placed somewhere lower in the hierarchy. * The method also includes in consideration a rare possibility * when the {@link #getBaseGraph() base} graph is also an {@code UnionGraph}. * The primary indivisible part of the base graph (which is usually a base graph itself) * is added to the beginning of the returned collection. * * @return a {@code Set} (ordered) of {@link Graph}s */ protected Set getAllBaseGraphs() { Set res = new LinkedHashSet<>(); Set visited = new HashSet<>(); Deque queue = new ArrayDeque<>(); queue.add(this); while (!queue.isEmpty()) { Graph next = queue.removeFirst(); if (next instanceof UnionGraphImpl u) { if (visited.add(u)) { queue.add(u.base); queue.addAll(u.subGraphs.graphs); } } else { res.add(next); } } return res; } /** * Recursively collects a {@code Set} of all {@link UnionGraph UnionGraph}s * that are related to this instance somehow, * i.e. are present in the hierarchy lower or higher. * This union graph instance is also included in the returned {@code Set}. * * @return Set of {@link UnionGraph}s */ protected Set getAllLinkedUnionGraphs() { Set res = new LinkedHashSet<>(); Deque queue = new ArrayDeque<>(); queue.add(this); while (!queue.isEmpty()) { UnionGraphImpl next = queue.removeFirst(); if (res.add(next)) { next.parents.stream().filter(res::add).forEach(queue::add); next.getAllUnderlyingUnionGraphs().stream().filter(res::add).forEach(queue::add); } } return res; } /** * Recursively collects all {@link UnionGraph}s that underlies this instance, inclusive. * * @return Set of {@link UnionGraph}s */ protected Set getAllUnderlyingUnionGraphs() { Set res = new LinkedHashSet<>(); Deque queue = new ArrayDeque<>(); queue.add(this); while (!queue.isEmpty()) { UnionGraphImpl next = queue.removeFirst(); if (res.add(next)) { next.unionSubGraphs().forEach(queue::add); } } return res; } private Stream unionSubGraphs() { return getSubGraphs().graphs() .filter(g -> g instanceof UnionGraphImpl) .map(u -> (UnionGraphImpl) u); } @Override public String toString() { // do not print the whole graph since it is expensive return "UnionGraph{@" + hashCode() + "}"; } /** * A container to hold all sub-graphs, that make up the hierarchy. * Such a representation of sub-graphs collection in the form of separate class allows * sharing its instance among different {@code UnionGraph} instances * to impart whole hierarchy structure when it is needed. */ public static class SubGraphs { protected final Collection graphs; protected SubGraphs() { this(new ArrayList<>()); } protected SubGraphs(Collection graphs) { this.graphs = Objects.requireNonNull(graphs); } /** * Lists all sub-graphs. * * @return {@link ExtendedIterator} of sub-{@link Graph graph}s */ public ExtendedIterator listGraphs() { return Iterators.create(graphs); } /** * Lists all sub-graphs. * * @return {@code Stream} of sub-{@link Graph graph}s */ public Stream graphs() { return graphs.stream(); } /** * Answers {@code true} iff this container is empty. * * @return boolean */ public boolean isEmpty() { return graphs.isEmpty(); } /** * Removes the given graph from the underlying collection. * Maybe overridden to produce corresponding event. * * @param graph {@link Graph} */ public void remove(Graph graph) { graphs.remove(graph); } /** * Adds the given graph into the underlying collection. * Maybe overridden to produce corresponding event. * * @param graph {@link Graph} */ public void add(Graph graph) { graphs.add(Objects.requireNonNull(graph)); } /** * Tests if the given triple belongs to any of the sub-graphs. * * @param t {@link Triple} to test * @return boolean * @see Graph#contains(Triple) */ protected boolean contains(Triple t) { if (graphs.isEmpty()) { return false; } for (Graph g : graphs) { if (g.contains(t)) { return true; } } return false; } } /** * An extended {@link org.apache.jena.graph.GraphEventManager Jena Graph Event Manager}, * a holder for {@link org.apache.jena.graph.GraphListener}s. */ public static class EventManagerImpl extends SimpleEventManager implements EventManager { private final List inactive = new ArrayList<>(); @Override public void onAddTriple(UnionGraph graph, Triple triple) { listeners(Listener.class).forEach(it -> it.onAddTriple(graph, triple)); } @Override public void onDeleteTriple(UnionGraph graph, Triple triple) { listeners(Listener.class).forEach(it -> it.onDeleteTriple(graph, triple)); } @Override public void onAddSubGraph(UnionGraph graph, Graph subGraph) { listeners(Listener.class).forEach(it -> it.onAddSubGraph(graph, subGraph)); } @Override public void onClear(UnionGraph graph) { listeners(Listener.class).forEach(it -> it.onClear(graph)); } @Override public void notifySubGraphAdded(UnionGraph graph, Graph subGraph) { listeners(Listener.class).forEach(it -> it.notifySubGraphAdded(graph, subGraph)); } @Override public void notifySuperGraphAdded(UnionGraph graph, UnionGraph superGraph) { listeners(Listener.class).forEach(it -> it.notifySuperGraphAdded(graph, superGraph)); } @Override public void onRemoveSubGraph(UnionGraph graph, Graph subGraph) { listeners(Listener.class).forEach(it -> it.onRemoveSubGraph(graph, subGraph)); } @Override public void notifySubGraphRemoved(UnionGraph graph, Graph subGraph) { listeners(Listener.class).forEach(it -> it.notifySubGraphRemoved(graph, subGraph)); } @Override public void off() { inactive.addAll(listeners); listeners.clear(); } @Override public void on() { listeners.addAll(inactive); inactive.clear(); } /** * Lists all encapsulated listeners. * * @return Stream of {@link GraphListener}s */ @Override public Stream listeners() { return listeners.stream(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy