]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.common/src/org/simantics/db/common/utils/CommonDBUtils.java
Refactoring of simulator toolkit
[simantics/platform.git] / bundles / org.simantics.db.common / src / org / simantics / db / common / utils / CommonDBUtils.java
1 package org.simantics.db.common.utils;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.HashSet;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Set;
10
11 import org.simantics.databoard.Bindings;
12 import org.simantics.db.ReadGraph;
13 import org.simantics.db.Resource;
14 import org.simantics.db.Statement;
15 import org.simantics.db.WriteGraph;
16 import org.simantics.db.common.procedure.adapter.DirectStatementProcedure;
17 import org.simantics.db.common.request.IsParent;
18 import org.simantics.db.common.request.ObjectsWithType;
19 import org.simantics.db.common.request.PossibleObjectWithType;
20 import org.simantics.db.common.request.PossibleOwner;
21 import org.simantics.db.exception.DatabaseException;
22 import org.simantics.db.exception.InvalidResourceReferenceException;
23 import org.simantics.db.service.ClusterUID;
24 import org.simantics.db.service.ClusteringSupport;
25 import org.simantics.db.service.DirectQuerySupport;
26 import org.simantics.db.service.SerialisationSupport;
27 import org.simantics.db.service.XSupport;
28 import org.simantics.layer0.Layer0;
29 import org.simantics.utils.datastructures.collections.CollectionUtils;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import gnu.trove.list.array.TIntArrayList;
34 import gnu.trove.procedure.TIntProcedure;
35 import gnu.trove.set.hash.THashSet;
36 import gnu.trove.set.hash.TIntHashSet;
37
38 public class CommonDBUtils {
39
40         private static final Logger LOGGER = LoggerFactory.getLogger(CommonDBUtils.class);
41
42         public static boolean isParent(ReadGraph graph, Resource possibleParent, Resource possibleChild) throws DatabaseException {
43                 return graph.sync(new IsParent(possibleParent, possibleChild));
44         }
45         
46         public static Resource parent(ReadGraph graph, Resource child) throws DatabaseException {
47                 return graph.getSingleObject(child, Layer0.getInstance(graph).PartOf);
48         }
49         
50         public static String possibleRelatedString(ReadGraph graph, Resource subject, Resource relation) throws DatabaseException {
51                 return graph.getPossibleRelatedValue(subject, relation, Bindings.STRING);
52         }
53
54         public static Integer possibleRelatedInteger(ReadGraph graph, Resource subject, Resource relation) throws DatabaseException {
55                 return graph.getPossibleRelatedValue(subject, relation, Bindings.INTEGER);
56         }
57
58     public static Resource getPossibleOwner(ReadGraph graph, Resource resource) throws DatabaseException {
59         return graph.syncRequest(new PossibleOwner(resource));
60     }
61     
62     /**
63      * Finds a resource that is reachable from both input resources by as short as possible IsOwnedBy chain
64      */
65     public static Resource commonAncestor(ReadGraph graph, Resource r1, Resource r2) throws DatabaseException {
66         if(r1.equals(r2))
67             return r1;
68         
69         Layer0 L0 = Layer0.getInstance(graph);
70         HashSet<Resource> visited = new HashSet<Resource>();
71         visited.add(r1);
72         visited.add(r2);
73         while(true) {
74             if(r1 != null) {
75                 r1 = graph.getPossibleObject(r1, L0.IsOwnedBy);
76                 if(r1 != null && !visited.add(r1))
77                     return r1;
78             }
79             else if(r2 == null)
80                 return null;
81             if(r2 != null) {
82                 r2 = graph.getPossibleObject(r2, L0.IsOwnedBy);
83                 if(r2 != null && !visited.add(r2))
84                     return r2;
85             }
86         }
87     }
88     
89     private static Collection<Resource> getDirectOwners(ReadGraph graph, Resource resource) throws DatabaseException {
90         // TODO is necessary?
91         if(resource.equals(graph.getRootLibrary()))
92             return Collections.emptyList();
93         
94         Layer0 L0 = Layer0.getInstance(graph);
95         
96         Collection<Resource> owners = graph.getObjects(resource, L0.IsOwnedBy);
97         if(owners.isEmpty()) {
98             owners = new THashSet<Resource>();
99             
100             // If there are no owners, collect resources referring to this resource by IsRelatedTo
101             for(Statement statement : graph.getStatements(resource, L0.IsWeaklyRelatedTo)) {
102                 Resource inverse = graph.getPossibleInverse(statement.getPredicate());
103                 if(inverse != null) {
104                     if(graph.isSubrelationOf(inverse, L0.IsRelatedTo)) {
105                         // Filter away tags
106                         if(resource.equals(statement.getObject()))
107                             continue;
108                         owners.add(statement.getObject());
109                     }
110                 }
111             }
112         }
113         else if(owners.size() > 1) {
114             // TODO: getObjects returns duplicate entries (https://www.simantics.org/redmine/issues/4885) and therefore direct is Set<Resource>.
115             // Fix getObjects to not return duplicate entries
116             owners = new HashSet<Resource>(owners);
117         }
118
119         return owners;
120     }
121     
122     public static Resource getNearestOwner(ReadGraph graph, Collection<Resource> resources) throws DatabaseException {
123         return getNearestOwner(graph, resources, null);
124     }
125         
126     private static Resource getNearestOwner(ReadGraph graph, Collection<Resource> resources, THashSet<Resource> infiniteRecursionBreaker) throws DatabaseException {
127
128         Layer0 L0 = Layer0.getInstance(graph);
129         
130         // These are resources that are linked to some of the input resources by IsComposedOf
131         Set<Resource> directOwners = new HashSet<Resource>();
132         
133         // These are resources that refer to some of the input resources which don't have an owner
134         Set<Resource> referringResources = new HashSet<Resource>();
135         
136         for(Resource r : resources) {
137             
138             Collection<Resource> owners = graph.getObjects(r, L0.IsOwnedBy);
139  
140             // TODO: getObjects returns duplicate entries (https://www.simantics.org/redmine/issues/4885) and therefore direct is Set<Resource>. Fix getObjects to not return duplicate entries
141             if (owners.size() > 1) {
142                 owners = new HashSet<Resource>(owners);
143                 if (owners.size() > 1) {
144                     LOGGER.error("Multiple owners for {}, id: {}", graph.getPossibleURI(r), r);
145                     for (Resource r2 : owners)
146                         LOGGER.error("    owner: {}, id: {}", graph.getPossibleURI(r2), r2);
147                     return null;
148                 }
149             }
150             
151             if (owners.isEmpty()) {
152                 // If there are no owners, collect resources referring to this resource by IsRelatedTo
153                 for(Statement stm : graph.getStatements(r, L0.IsWeaklyRelatedTo)) {
154                     Resource inverse = graph.getPossibleInverse(stm.getPredicate());
155                     if(inverse != null) {
156                         if(graph.isSubrelationOf(inverse, L0.IsRelatedTo)) {
157                             // Filter away tags
158                             if(r.equals(stm.getObject()))
159                                 continue;
160                             referringResources.add(stm.getObject());
161                         }
162                     }
163                 }
164             }
165             else
166                 directOwners.addAll(owners);
167         }
168         
169         if(!directOwners.isEmpty()) {
170             Iterator<Resource> iter = directOwners.iterator();
171             Resource common = iter.next();
172             while (iter.hasNext()) {
173                 Resource other = iter.next();
174                 common = commonAncestor(graph, common, other);
175                 if (common == null)
176                     return null; // The
177             }
178             referringResources.add(common);
179         }
180         if(referringResources.isEmpty())
181             return null;
182         
183         if(!Collections.disjoint(referringResources, resources)) {
184             LOGGER.error("Overlapping owners:");
185             for(Resource r : resources)
186                 LOGGER.error("    resource {}", NameUtils.getSafeName(graph, r, true));
187             for(Resource r : referringResources)
188                 LOGGER.error("    owner {}", NameUtils.getSafeName(graph, r, true));
189             return null;
190         }
191         
192         if(referringResources.size() == 1)
193             return referringResources.iterator().next();
194         
195         // Prevent recursion on current input resources
196         if(infiniteRecursionBreaker == null)
197             infiniteRecursionBreaker = new THashSet<>(resources);
198         else {
199             if(!Collections.disjoint(infiniteRecursionBreaker, resources)) {
200                 infiniteRecursionBreaker.retainAll(resources);
201                 LOGGER.error("Resources have a cyclic reference loop preventing the discovery their owner. {}", infiniteRecursionBreaker);
202                 return null;
203             }
204
205             infiniteRecursionBreaker.addAll(resources);
206         }
207         
208         return getNearestOwner(graph, referringResources, infiniteRecursionBreaker);
209         
210     }
211     
212     public static Resource getClusterSetForNewResource(ReadGraph graph, Resource ... resources) throws DatabaseException {
213
214         if(resources.length == 1) return getClusterSetForNewResource(graph, resources[0]);
215         
216         Resource owner = getNearestOwner(graph, CollectionUtils.toList(resources));
217         if(owner == null) return null;
218         return getClusterSetForNewResource(graph, owner, new HashSet<Resource>());
219         
220     }
221     
222     public static Resource getClusterSetForNewResource(ReadGraph graph, Collection<Resource> resources) throws DatabaseException {
223
224         if(resources.size() == 1) return getClusterSetForNewResource(graph, resources.iterator().next());
225
226         Resource owner = getNearestOwner(graph, resources);
227         return getClusterSetForNewResource(graph, owner, new HashSet<Resource>());
228         
229     }
230     
231     public static Resource getClusterSetForNewResource(ReadGraph graph, Resource resource, Set<Resource> visited) throws DatabaseException {
232         
233         ClusteringSupport cs = graph.getService(ClusteringSupport.class);
234         if(cs.isClusterSet(resource)) return resource;
235         
236         Resource owner = getPossibleOwner(graph, resource);
237         
238         if(owner == null || owner == resource) return null;
239         if(!visited.add(owner)) return null;
240
241         return getClusterSetForNewResource(graph, owner, visited);
242         
243     }
244
245     public static Resource getClusterSetForNewResource(ReadGraph graph, Resource r) throws DatabaseException {
246         return getClusterSetForNewResource(graph, r, new HashSet<Resource>());
247     }
248
249
250     public static void selectClusterSet(WriteGraph graph, Collection<Resource> resources) throws DatabaseException {
251         Resource clusterSet = getClusterSetForNewResource(graph, resources);
252         if(clusterSet == null) clusterSet = graph.getRootLibrary();
253         graph.setClusterSet4NewResource(clusterSet);
254     }
255
256     public static void selectClusterSet(WriteGraph graph, Resource ... resources) throws DatabaseException {
257         Resource clusterSet = getClusterSetForNewResource(graph, resources);
258         if(clusterSet == null) clusterSet = graph.getRootLibrary();
259         graph.setClusterSet4NewResource(clusterSet);
260     }
261     
262     public static void selectClusterSet(WriteGraph graph, Resource resource) throws DatabaseException {
263         Resource clusterSet = getClusterSetForNewResource(graph, resource);
264         if(clusterSet == null) clusterSet = graph.getRootLibrary();
265         graph.setClusterSet4NewResource(clusterSet);
266     }
267     
268     public static List<Resource> objectsWithType(ReadGraph graph, Resource subject, Resource relation, Resource type) throws DatabaseException {
269         return new ArrayList<Resource>(graph.syncRequest(new ObjectsWithType(subject, relation, type)));
270     }
271
272     public static Resource possibleObjectWithType(ReadGraph graph, Resource subject, Resource relation, Resource type) throws DatabaseException {
273         return graph.syncRequest(new PossibleObjectWithType(subject, relation, type));
274     }
275
276         public static List<ClusterUID> listClusters(ReadGraph graph) throws DatabaseException {
277         XSupport xs = graph.getService(XSupport.class);
278         ClusterUID uids[] = xs.listClusters();
279         ArrayList<ClusterUID> result = new ArrayList<>(uids.length);
280         for(ClusterUID uid : uids) result.add(uid);
281         return result;
282     }
283         
284         public static List<Resource> resourcesByCluster(ReadGraph graph, ClusterUID uid) throws DatabaseException {
285         SerialisationSupport ss = graph.getService(SerialisationSupport.class);
286         ArrayList<Resource> result = new ArrayList<Resource>();
287         // Index 0 is illegal
288                 for(int i=1;i<1<<12;i++) {
289                         try {
290                                 result.add(ss.getResource(uid.toRID(i)));
291                         } catch (InvalidResourceReferenceException e) {
292                         }
293                 }
294                 return result;
295         }
296         
297         public static List<Statement> directStatements(ReadGraph graph, Resource resource, boolean ignoreVirtual) throws DatabaseException {
298                 
299                 DirectQuerySupport dqs = graph.getService(DirectQuerySupport.class);
300                 DirectStatementProcedure proc = new DirectStatementProcedure();
301
302                 if (ignoreVirtual) {
303                         return dqs.getDirectPersistentStatements(graph, resource);
304                 } else {
305                         return dqs.getDirectStatements(graph, resource);
306                 }
307
308         }
309         
310     public static List<Resource> garbageResources(ReadGraph graph) throws DatabaseException {
311         
312         SerialisationSupport ss = graph.getService(SerialisationSupport.class);
313         
314         TIntArrayList refs = new TIntArrayList();
315         TIntArrayList res = new TIntArrayList();
316         
317         // Find all statements in the database
318         for(ClusterUID uid : listClusters(graph)) {
319                 for(Resource r : resourcesByCluster(graph, uid)) {
320                         int sid = ss.getTransientId(r);
321                         for(Statement stm : directStatements(graph, r, true)) {
322                                 int oid = ss.getTransientId(stm.getObject());
323                                 refs.add(sid);
324                                 refs.add(oid);
325                         }
326                         res.add(sid);
327                 }
328         }
329
330         TIntHashSet reached = new TIntHashSet();
331         
332         // Initialize root
333         int root = ss.getTransientId(graph.getRootLibrary());
334         reached.add(root);
335         
336         int[] refArray = refs.toArray();
337         
338         boolean changes = true;
339         
340         while(changes) {
341                 changes = false;
342                 for(int i=0;i<refArray.length;i+=2) {
343                         int s = refArray[i];
344                         int o = refArray[i+1];
345                         if(reached.contains(s)) {
346                                 if(reached.add(o)) {
347                                         changes = true;
348                                 }
349                         }
350                 }
351                 
352                 System.err.println("Reachability iteration, changes = " + changes);
353         }
354         
355         ArrayList<Resource> result = new ArrayList<>();
356                 for(int i=0;i<refArray.length;i+=2) {
357                         int s = refArray[i];
358                         if(reached.contains(s)) {
359                                 if(reached.add(refArray[i+1]))
360                                         changes = true;
361                         }
362                 }
363
364                 res.forEach(new TIntProcedure() {
365                         
366                         @Override
367                         public boolean execute(int r) {
368                                 if(!reached.contains(r)) {
369                                         try {
370                                                 result.add(ss.getResource(r));
371                                         } catch (DatabaseException e) {
372                                                 LOGGER.error("Unexpected error while resolving garbage resources.", e);
373                                         }
374                                 }
375                                 return true;
376                         }
377                         
378                 });
379                 
380         return result;
381         
382     }
383
384     public static ClusterUID clusterUIDOfResource(ReadGraph graph, Resource resource) throws DatabaseException {
385         SerialisationSupport ss = graph.getService(SerialisationSupport.class);
386         return ss.getUID(resource).asCID();
387     }
388     
389     public static boolean isClusterLoaded(ReadGraph graph, ClusterUID clusterUID) throws DatabaseException {
390         XSupport xs = graph.getService(XSupport.class);
391         return xs.isClusterLoaded(clusterUID);
392     }
393     
394     
395 }