X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.db.common%2Fsrc%2Forg%2Fsimantics%2Fdb%2Fcommon%2Futils%2FNearestOwnerFinder.java;fp=bundles%2Forg.simantics.db.common%2Fsrc%2Forg%2Fsimantics%2Fdb%2Fcommon%2Futils%2FNearestOwnerFinder.java;h=062d1c88a6d3ffbcbeee4066d4e3dc3390721a5b;hb=68a9ec79344f44499f9a92c95ee81b8b052a22e7;hp=0000000000000000000000000000000000000000;hpb=ac990aa4625c02006f1bc56fbfd106cf7e1c8d84;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/NearestOwnerFinder.java b/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/NearestOwnerFinder.java new file mode 100644 index 000000000..062d1c88a --- /dev/null +++ b/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/NearestOwnerFinder.java @@ -0,0 +1,207 @@ +package org.simantics.db.common.utils; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; + +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Statement; +import org.simantics.db.exception.DatabaseException; +import org.simantics.layer0.Layer0; + +import gnu.trove.map.hash.TObjectIntHashMap; +import gnu.trove.set.hash.THashSet; + +/** + *

A utility to finds the nearest owner of a real or imaginary resource.

+ * + *

The nearest owner + * is found by first finding a path (pilot path) from the input to a root resource and then + * trying to find other paths to the resources in the pilot path such that the other + * paths don't visit other resources of the pilot path. The farthest resource so found + * is then the nearest common owner. See the methods for the exact specification.

+ * + *

The time and space complexity of the algorithm is O(m), + * where m is the number of resources reachable by direct-owners relation from the input.

+ * + * @see {@link #getNearestOwner(ReadGraph, Resource)} + * @see {@link #getNearestOwner(ReadGraph, Collection)}

+ * + */ +public class NearestOwnerFinder { + private static final int FIRST_DISTANCE = 1; + private static final int UNVISITED = 0; + private static final int NOT_INTERESTING_ANYMORE = -1; + + private final ReadGraph graph; + private final Layer0 L0; + + private final TObjectIntHashMap resourceStatus = new TObjectIntHashMap<>(); + + private int nearestOwnerDistance; // = 0 + private Resource nearestOwner; // = null + + private NearestOwnerFinder(ReadGraph graph, Layer0 L0) { + this.graph = graph; + this.L0 = L0; + } + + /** + * Iterates the owners of the input resource. It first + * tries to find the pilot path and when it has been found, + * it browses other owners with {@link #browseNonpilot}. + */ + private void browseOwnersOfInput(Collection owners) throws DatabaseException { + Iterator it = owners.iterator(); + while(it.hasNext()) { + Resource owner = it.next(); + if(browsePilot(owner, FIRST_DISTANCE)) { + // Because we found a path to root, {@code owner} + // becomes our first candidate for nearestOwner. + nearestOwner = owner; + nearestOwnerDistance = FIRST_DISTANCE; + + // Tries to find other paths to the resource in the pilot path. + while(it.hasNext()) + browseNonpilot(it.next()); + return; + } + } + } + + /** + * Tries to find a first path (=pilot path) to a root with DFS. + */ + private boolean browsePilot(Resource resource, int distance) throws DatabaseException { + if(resourceStatus.putIfAbsent(resource, distance) != UNVISITED) + // We recursed back to the current path or encountered previously failed path. + return false; + + Collection owners = getDirectOwners(graph, L0, resource); + if(owners.isEmpty()) + // We found a root + return true; + + for(Resource owner : owners) + if(browsePilot(owner, distance+1)) { + // We found a path to a root + return true; + } + + // Failed to find a path to a root + resourceStatus.put(resource, NOT_INTERESTING_ANYMORE); + return false; + } + + /** + * Tries to find some path to a resource on a pilot path using DFS. + * Marks all visited resource by {@code NOT_INTERESTING_ANYMORE} + * to prevent visiting the same resources more than once. + */ + private void browseNonpilot(Resource resource) throws DatabaseException { + // In the next statement, we may overwrite also information about resource being on pilot path, + // but that is ok, because we don't care resources that are nearer than nearestOwnerDistance. + int status = resourceStatus.put(resource, NOT_INTERESTING_ANYMORE); + if(status == UNVISITED) { + Collection owners = getDirectOwners(graph, L0, resource); + if(owners.isEmpty()) { + // This is a root that differs from the root at the end of the pilot path. + // Therefore there cannot be a nearest owner. + nearestOwnerDistance = Integer.MAX_VALUE; + nearestOwner = null; + return; + } + for(Resource owner : owners) + browseNonpilot(owner); + } + else if(status > nearestOwnerDistance) { + nearestOwnerDistance = status; + nearestOwner = resource; + } + } + + private static Collection getDirectOwners(ReadGraph graph, Layer0 L0, Resource resource) throws DatabaseException { + Collection owners = graph.getObjects(resource, L0.IsOwnedBy); + if(owners.isEmpty()) { + if(resource.equals(graph.getRootLibrary())) + return Collections.emptyList(); + + owners = new THashSet(); + + // If there are no owners, collect resources referring to this resource by IsRelatedTo + for(Statement statement : graph.getStatements(resource, L0.IsWeaklyRelatedTo)) { + Resource inverse = graph.getPossibleInverse(statement.getPredicate()); + if(inverse != null) { + if(graph.isSubrelationOf(inverse, L0.IsRelatedTo)) { + // Filter away tags + if(resource.equals(statement.getObject())) + continue; + owners.add(statement.getObject()); + } + } + } + } + else if(owners.size() > 1) { + // TODO: getObjects returns duplicate entries (https://www.simantics.org/redmine/issues/4885) and therefore direct is Set. + // Fix getObjects to not return duplicate entries + owners = new HashSet(owners); + } + + return owners; + } + + /** + *

Finds the nearest owner of a resource. Nearest owner is the + * immediate dominator + * of the resource in a graph defined in the following way: + * Each IsComposedOf statement is an edge of the graph. + * Additionally an IsRelatedTo statement in an edge of graph if + * no IsComposedOf statement points to its object.

+ * + *

The root of the graph is any resource without owners or the root resource + * of the database. If the search encounters two distinct roots, there + * cannot be a nearest owner.

+ * + * @return The nearest owner or {@code null} if no roots or more than one roots are found. + */ + public static Resource getNearestOwner(ReadGraph graph, Resource resource) throws DatabaseException { + // Handle cases of one or zero direct owners + Layer0 L0 = Layer0.getInstance(graph); + Collection owners = getDirectOwners(graph, L0, resource); + if(owners.size() == 1) + return owners.iterator().next(); + if(owners.isEmpty()) + return null; + + // There are two or more direct owners + NearestOwnerFinder finder = new NearestOwnerFinder(graph, L0); + finder.resourceStatus.put(resource, NOT_INTERESTING_ANYMORE); + finder.browseOwnersOfInput(owners); + return finder.nearestOwner; + } + + /** + *

Finds the nearest owner of a real or imaginary resource whose direct owners are known. This works in the same + * way as the method for one resource {@link #getNearestOwner(ReadGraph, Resource)} + * but we add one extra node to the graph that has the input resources as direct owners and + * find the immediate dominator of that extra node.

+ * + *

Note that this method may return one of the input resources if it is dominator of all other + * resources in the collection. Returns {@code null} if the input collection is empty.

+ */ + public static Resource getNearestOwnerFromDirectOwners(ReadGraph graph, Collection owners) throws DatabaseException { + // Handle the cases of one or zero direct owners + if(owners.size() == 1) + return owners.iterator().next(); + if(owners.isEmpty()) + return null; + + // There are two or more direct owners + Layer0 L0 = Layer0.getInstance(graph); + NearestOwnerFinder finder = new NearestOwnerFinder(graph, L0); + finder.browseOwnersOfInput(owners); + return finder.nearestOwner; + } +}