/*******************************************************************************
* 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;
}
}