/******************************************************************************* * Copyright (c) 2007, 2018 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.db.common.utils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.simantics.databoard.Bindings; import org.simantics.db.AsyncReadGraph; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.WriteOnlyGraph; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.RuntimeDatabaseException; import org.simantics.db.exception.ValidationException; import org.simantics.db.function.DbFunction; import org.simantics.db.procedure.AsyncMultiProcedure; import org.simantics.layer0.Layer0; import org.simantics.utils.datastructures.Pair; public class OrderedSetUtils { /** * Returns true, if l contains the element el. */ public static boolean contains(ReadGraph g, Resource l, Resource el) throws DatabaseException { return g.hasStatement(el, l) && !l.equals(el); } /** * Returns the next element in the ordered set or l, * if el is the last element. Called for el, returns * the first element in the set. */ public static Resource next(ReadGraph g, Resource l, Resource el) throws DatabaseException { Collection nexts = g.getObjects(el, l); if(nexts.size() != 1) throw new InvalidOrderedSetException("Invalid list element: " + l + " " + el + " " + nexts.size() + " successors."); for(Resource r : nexts) return r; return null; } public static void forNext(AsyncReadGraph g, final Resource l, Resource el, final AsyncMultiProcedure procedure) { g.forEachObject(el, l, new AsyncMultiProcedure() { @Override public void exception(AsyncReadGraph graph, Throwable t) { procedure.exception(graph, t); } @Override public void finished(AsyncReadGraph graph) { } @Override public void execute(AsyncReadGraph graph, Resource nel) { if(!nel.equals(l)) { procedure.execute(graph, nel); forNext(graph, l, nel, procedure); } } }); // Collection nexts = g.getObjects(el, l); // if(nexts.size() != 1) // throw new NoSingleResultException("Invalid list element: " + nexts.size() + " successors."); // for(Resource r : nexts) // return r; // return null; } /** * Returns the previouse element in the ordered set or l, * if el is the first element. Called for el, returns * the last element in the set. */ public static Resource prev(ReadGraph g, Resource l, Resource el) throws DatabaseException { Collection prevs = g.getObjects(el, g.getInverse(l)); if(prevs.size() != 1) { throw new InvalidOrderedSetException("Invalid list element, " + prevs.size() + " predecessors."); } for(Resource r : prevs) return r; return null; } /** * Adds a given elements el to the list l. Returns * true, if the element was really added. */ public static boolean add(WriteGraph g, Resource l, Resource el) throws DatabaseException { if(g.hasStatement(el, l)) return false; Resource back = prev(g, l, l); g.denyStatement(back, l, l); g.claim(back, l, el); g.claim(el, l, l); return true; } /** * Adds a given elements el to the list l. Returns * true, if the element was really added. */ public static boolean addFirst(WriteGraph g, Resource l, Resource el) throws DatabaseException { return addAfter(g, l, l, el); } public static boolean addAll(WriteGraph g, Resource l, Iterable els) throws DatabaseException { for(Resource r : els) if(g.hasStatement(r, l)) return false; Resource cur = prev(g, l, l); g.denyStatement(cur, l, l); for(Resource r : els) { g.claim(cur, l, r); cur = r; } g.claim(cur, l, l); return true; } public static boolean addAllNew(WriteGraph g, Resource l, Iterable els) throws DatabaseException { Resource cur = prev(g, l, l); Resource inv = g.getInverse(l); g.deny(cur, l, inv, l); for(Resource r : els) { g.claim(cur, l, inv, r); cur = r; } g.claim(cur, l, inv, l); return true; } public static boolean addAfter(WriteGraph g, Resource l, Resource pos, Resource el) throws DatabaseException { if(g.hasStatement(el, l)) return false; Resource next = next(g, l, pos); g.denyStatement(pos, l, next); g.claim(pos, l, el); g.claim(el, l, next); return true; } public static boolean addBefore(WriteGraph g, Resource l, Resource pos, Resource el) throws DatabaseException { if(g.hasStatement(el, l)) return false; Resource prev = prev(g, l, pos); g.denyStatement(prev, l, pos); g.claim(prev, l, el); g.claim(el, l, pos); return true; } /** * Removes a given elements el from the list l. Returns * true, if the element was really removed. */ public static boolean remove(WriteGraph g, Resource l, Resource el) throws DatabaseException { Collection nexts = g.getObjects(el, l); if(nexts.size() == 0) return false; if(nexts.size() != 1) throw new InvalidOrderedSetException("Invalid list element."); Collection prevs = g.getObjects(el, g.getInverse(l)); if(prevs.size() != 1) throw new InvalidOrderedSetException("Invalid list element."); for(Resource p : prevs) for(Resource n : nexts) { g.denyStatement(p, l, el); g.denyStatement(el, l, n); g.claim(p, l, n); return true; } return true; } public static List removeList(WriteGraph g, Resource l) throws DatabaseException { List els = toList(g, l); for(Resource el : els) g.deny(el, l); g.deny(l, l); Resource inv = g.getInverse(l); g.deny(l); g.deny(inv); return els; } public static void replace(WriteGraph g, Resource l, Resource oldEl, Resource newEl) throws DatabaseException { Collection nexts = g.getObjects(oldEl, l); if(nexts.size() != 1) throw new InvalidOrderedSetException("Invalid list element."); Collection prevs = g.getObjects(oldEl, g.getInverse(l)); if(prevs.size() != 1) throw new InvalidOrderedSetException("Invalid list element."); for(Resource p : prevs) for(Resource n : nexts) { g.denyStatement(p, l, oldEl); g.denyStatement(oldEl, l, n); g.claim(p, l, newEl); g.claim(newEl, l, n); return; } } /** * Reorders elements with a minimum number of writes. The set of elements must remain the same. */ public static void reorder(WriteGraph g, Resource l, Iterable order) throws DatabaseException { Resource newPrev = l; for (Resource r : order) { Resource prev = OrderedSetUtils.prev(g, l, r); if (!prev.equals(newPrev)) { g.deny(prev, l, r); g.claim(newPrev, l, r); } newPrev = r; } Resource newLast = newPrev; Resource last = OrderedSetUtils.prev(g, l, l); if (!last.equals(newLast)) { g.deny(last, l, l); g.claim(newLast, l, l); } } /** * Converts ordered set into a list. */ public static List toList(ReadGraph g, Resource l) throws DatabaseException { ArrayList ret = new ArrayList(); Resource cur = l; while(true) { cur = next(g, l, cur); if(cur.equals(l)) return ret; ret.add(cur); } } /** * Creates an empty ordered set. */ public static Resource create(WriteOnlyGraph g, Resource type) throws DatabaseException { Layer0 l0 = g.getService(Layer0.class); Resource l = g.newResource(); g.claim(l, l0.InstanceOf, null, type); g.claim(l, l0.SubrelationOf, null, l0.HasNext); Resource invL = g.newResource(); g.claim(invL, l0.SubrelationOf, null, l0.HasPrevious); g.claim(l, l0.InverseOf, l0.InverseOf, invL); g.claim(l, l, invL, l); return l; } /** * Creates an ordered set containing the given elements. * It is assumed that the elements do not contain duplicates. */ @Deprecated public static Resource create(WriteOnlyGraph g, Resource type, Iterable c) throws DatabaseException { Layer0 l0 = g.getService(Layer0.class); Resource l = g.newResource(); g.claim(l, l0.InstanceOf, null, type); g.claim(l, l0.SubrelationOf, null, l0.HasNext); Resource invL = g.newResource(); g.claim(invL, l0.SubrelationOf, null, l0.HasPrevious); g.claim(l, l0.InverseOf, l0.InverseOf, invL); Resource cur = l; for(Resource r : c) { g.claim(cur, l, invL, r); cur = r; } g.claim(cur, l, invL, l); return l; } public static Resource create(WriteOnlyGraph g, Resource type, String name, Iterable c) throws DatabaseException { return createExisting(g, g.newResource(), type, name, c); } public static Resource createExisting(WriteOnlyGraph g, Resource l, Resource type, String name, Iterable c) throws DatabaseException { Layer0 l0 = g.getService(Layer0.class); g.claim(l, l0.InstanceOf, null, type); g.claim(l, l0.SubrelationOf, null, l0.HasNext); g.addLiteral(l, l0.HasName, l0.NameOf, l0.String, name, Bindings.STRING); Resource invL = g.newResource(); g.claim(invL, l0.SubrelationOf, null, l0.HasPrevious); g.addLiteral(invL, l0.HasName, l0.NameOf, l0.String, "Inverse", Bindings.STRING); g.claim(l, l0.InverseOf, l0.InverseOf, invL); g.claim(l, l0.ConsistsOf, l0.PartOf, invL); Resource cur = l; for(Resource r : c) { g.claim(cur, l, invL, r); cur = r; } g.claim(cur, l, invL, l); return l; } public static Resource create(WriteOnlyGraph g, Resource type, Resource ... c) throws DatabaseException { Layer0 l0 = g.getService(Layer0.class); Resource l = g.newResource(); g.claim(l, l0.InstanceOf, null, type); g.claim(l, l0.SubrelationOf, null, l0.HasNext); Resource invL = g.newResource(); g.claim(invL, l0.SubrelationOf, null, l0.HasPrevious); g.claim(l, l0.InverseOf, l0.InverseOf, invL); Resource cur = l; for(Resource r : c) { g.claim(cur, l, invL, r); cur = r; } g.claim(cur, l, invL, l); return l; } public static Resource createExisting(WriteGraph g, Resource l, Resource ... c) throws DatabaseException { Layer0 l0 = Layer0.getInstance(g); g.claim(l, l0.SubrelationOf, null, l0.HasNext); Resource invL = g.newResource(); g.claim(invL, l0.SubrelationOf, null, l0.HasPrevious); g.claim(l, l0.InverseOf, l0.InverseOf, invL); Resource cur = l; for(Resource r : c) { g.claim(cur, l, invL, r); cur = r; } g.claim(cur, l, invL, l); return l; } // public static Resource create(GraphWriter graph, Resource type, Iterable c) throws DatabaseException { // ReadGraph g = w.getGraph(); // Builtins b = g.getBuiltins(); // Resource l = w.create(type).get(); // w.let(b.SubrelationOf, b.IsRelatedTo); // w.createInverse(l).let(b.SubrelationOf, g.getInverse(b.IsRelatedTo)); // // Resource cur = l; // for(Resource r : c) { // w.stat(cur, l, r); // cur = r; // } // w.stat(cur, l, l); // return l; // } public static class OrderedSetIterator implements ListIterator { ReadGraph g; Resource l; Resource prev; Resource next; int index; boolean direction; WriteGraph getWriteGraph() { if (!(g instanceof WriteGraph)) throw new UnsupportedOperationException(""); return (WriteGraph) g; } public OrderedSetIterator(ReadGraph g, Resource l) throws DatabaseException { this.g = g; this.l = l; this.prev = l; this.next = OrderedSetUtils.next(g, l, l); this.index = 0; } @Override public boolean hasNext() { return !next.equals(l); } @Override public Resource next() { prev = next; try { next = OrderedSetUtils.next(g, l, next); } catch (DatabaseException e) { throw new RuntimeDatabaseException(e); } ++index; direction = true; return prev; } @Override public void remove() { try { WriteGraph wg = getWriteGraph(); if(direction) { OrderedSetUtils.remove(wg, l, prev); prev = OrderedSetUtils.prev(wg, l, next); } else { OrderedSetUtils.remove(wg, l, next); next = OrderedSetUtils.next(wg, l, prev); } } catch (DatabaseException e) { throw new RuntimeDatabaseException(e); } } @Override public void add(Resource e) { try { WriteGraph wg = getWriteGraph(); wg.denyStatement(prev, l, next); wg.claim(prev, l, e); wg.claim(e, l, next); prev = e; } catch (DatabaseException ex) { throw new RuntimeDatabaseException(ex); } } @Override public boolean hasPrevious() { return !prev.equals(l); } @Override public int nextIndex() { return index; } @Override public Resource previous() { try { next = prev; prev = OrderedSetUtils.prev(g, l, prev); --index; direction = false; return next; } catch (DatabaseException ex) { throw new RuntimeDatabaseException(ex); } } @Override public int previousIndex() { return index-1; } @Override public void set(Resource e) { try { WriteGraph wg = getWriteGraph(); if(direction) { OrderedSetUtils.replace(wg, l, prev, e); prev = e; } else { OrderedSetUtils.replace(wg, l, next, e); next = e; } } catch (DatabaseException ex) { throw new RuntimeDatabaseException(ex); } } } public static ListIterator iterator(ReadGraph g, Resource l) throws DatabaseException { return new OrderedSetIterator(g, l); } public static boolean set(WriteGraph g, Resource l, Iterable els) throws DatabaseException { // Compute delta Set> removed = new HashSet>(); Collection> added = new ArrayList>(); Resource cur = l; do { Resource temp = next(g, l, cur); removed.add(new Pair(cur, temp)); cur = temp; } while(!cur.equals(l)); cur = l; for(Resource temp : els) { Pair pair = new Pair(cur, temp); if(!removed.remove(pair)) added.add(pair); cur = temp; } { Pair pair = new Pair(cur, l); if(!removed.remove(pair)) added.add(pair); } // Apply if(added.isEmpty() && removed.isEmpty()) return false; for(Pair pair : removed) g.denyStatement(pair.first, l, pair.second); for(Pair pair : added) g.claim(pair.first, l, pair.second); return true; } /** * Retrieves the owner list the specified list element * * @param g * @param el a possible element of a list of the specified type * @param listBaseRelation a base relation of the list to look for * @return */ public static Resource getSingleOwnerList(ReadGraph g, Resource el, Resource listBaseRelation) throws DatabaseException { Collection result = getOwnerLists(g, el, listBaseRelation); if (result.size() != 1) throw new ValidationException(NameUtils.getSafeName(g, el) + " is part of " + result.size() + " lists of base type " + NameUtils.getSafeName(g, listBaseRelation) + ", expected only one list."); return result.iterator().next(); } public static Resource getSingleOwnerList(ReadGraph g, Resource el) throws DatabaseException { Layer0 l0 = Layer0.getInstance(g); Collection result = getOwnerLists(g, el, l0.OrderedSet); if (result.size() != 1) throw new ValidationException(NameUtils.getSafeName(g, el) + " is part of " + result.size() + " lists of base type L0.OrderedSet, expected only one list."); return result.iterator().next(); } public static Collection getSubjects(ReadGraph g, Resource object) throws DatabaseException { Collection result = new ArrayList(1); Layer0 l0 = Layer0.getInstance(g); for(Resource pred : g.getPredicates(object)) { if(g.isInstanceOf(pred, l0.OrderedSet) && !pred.equals(object)) result.add(pred); } return result; } private static void forSubjects(ReadGraph g, Resource object, DbFunction consumer) throws DatabaseException { for (Resource pred : g.getPredicates(object)) { if (!pred.equals(object)) if (!consumer.apply(pred)) break; } } /** * Retrieves the owner list the specified list element * * @param g * @param el a possible element of a list of the specified type * @param listBaseRelation a base relation of the list to look for * @return */ public static Collection getOwnerLists(ReadGraph g, Resource el, Resource listBaseRelation) throws DatabaseException { Collection result = null; Collection rs = getSubjects(g, el); for (Resource r : rs) { if (g.isInstanceOf(r, listBaseRelation)) { if (result == null) result = new ArrayList(2); result.add(r); } } if (result == null) result = Collections.emptyList(); return result; } private static class PossibleOwnerList implements DbFunction { private ReadGraph graph; private final Resource listBaseRelation; public Layer0 L0; public Resource result; PossibleOwnerList(ReadGraph graph, Resource listBaseRelation) { this.graph = graph; this.listBaseRelation = listBaseRelation; this.L0 = Layer0.getInstance(graph); } @Override public Boolean apply(Resource t) throws DatabaseException { Set types = graph.getTypes(t); if (types.contains(L0.OrderedSet) && types.contains(listBaseRelation)) { if (result != null) { result = null; return false; } result = t; } return true; } } /** * Retrieves a possible single owner list the specified list element * * @param g * @param el * a possible element of a list of the specified type * @param listBaseRelation * a base relation of the list to look for * @return null if there is zero or more than one owner lists * with specified base relation */ public static Resource getPossibleOwnerList(ReadGraph g, Resource el, Resource listBaseRelation) throws DatabaseException { PossibleOwnerList proc = new PossibleOwnerList(g, listBaseRelation); forSubjects(g, el, proc); return proc.result; } }