--- /dev/null
+package org.simantics.db.common.utils;
+
+import java.util.AbstractSequentialList;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.layer0.Layer0;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Java List interface to linked lists in Simantics DB.
+ */
+public class GraphList extends AbstractSequentialList<Resource> {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(GraphList.class);
+
+ private final ReadGraph graph;
+ private final Layer0 L0;
+ private final Resource root;
+ private final Resource elementRelation;
+
+ public GraphList(ReadGraph graph, Resource root, Resource elementRelation) {
+ this.graph = graph;
+ this.L0 = Layer0.getInstance(graph);
+ this.root = root;
+ this.elementRelation = elementRelation;
+ }
+
+ public GraphList(ReadGraph graph, Resource root) throws DatabaseException {
+ this.graph = graph;
+ this.L0 = Layer0.getInstance(graph);
+ this.root = root;
+ this.elementRelation = graph.getSingleObject(root, L0.List_ElementPredicate);
+ }
+
+ class GraphListIterator implements ListIterator<Resource> {
+ private Resource prev;
+ private Resource next;
+ private int id;
+ private int lastOp;
+
+ public GraphListIterator(Resource prev, Resource next, int id) {
+ LOGGER.info("GraphListIterator root="+root+" prev=" + prev + " next=" + next);
+ this.prev = prev;
+ this.next = next;
+ this.id = id;
+ this.lastOp = 0;
+ }
+
+ @Override
+ public boolean hasNext() {
+ LOGGER.info("hasNext root="+root+" prev=" + prev + " next=" + next);
+ return next != root;
+ }
+
+ void goNext() {
+ LOGGER.info("goNext prev=" + prev + " next=" + next);
+ if(next == root) {
+ lastOp = 0;
+ throw new NoSuchElementException();
+ }
+ prev = next;
+ next = browseNext(next);
+ ++id;
+ lastOp = 1;
+ LOGGER.info(" prev=" + prev + " next=" + next);
+ }
+
+ void goPrev() {
+ if(prev == root) {
+ lastOp = 0;
+ throw new NoSuchElementException();
+ }
+ next = prev;
+ prev = browsePrev(prev);
+ --id;
+ lastOp = -1;
+ }
+
+ @Override
+ public Resource next() {
+ goNext();
+ return browseElement(prev);
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return prev != root;
+ }
+
+ @Override
+ public Resource previous() {
+ goPrev();
+ return browseElement(next);
+ }
+
+ @Override
+ public int nextIndex() {
+ return id;
+ }
+
+ @Override
+ public int previousIndex() {
+ return id-1;
+ }
+
+ @Override
+ public void remove() {
+ Resource target;
+ if(lastOp == 1) {
+ target = prev;
+ prev = browsePrev(prev);
+ }
+ else if(lastOp == -1) {
+ target = next;
+ next = browseNext(next);
+ }
+ else
+ throw new UnsupportedOperationException("Cannot remove element, when no previous next/prev call.");
+ doRemove(prev, target, next);
+ lastOp = 0;
+ }
+
+ @Override
+ public void set(Resource e) {
+ Resource target;
+ if(lastOp == 1)
+ target = prev;
+ else if(lastOp == -1)
+ target = next;
+ else
+ throw new UnsupportedOperationException("Cannot remove element, when no previous next/prev call.");
+ doSet(target, e);
+ }
+
+ @Override
+ public void add(Resource e) {
+ addBetween(prev, next, e);
+ lastOp = 0;
+ }
+ }
+
+ @Override
+ public ListIterator<Resource> listIterator(int index) {
+ GraphListIterator it = new GraphListIterator(root, browseNext(root), 0);
+ while(index > 0) {
+ it.goNext();
+ --index;
+ }
+ return it;
+ }
+
+ private Resource browseNext(Resource cur) {
+ try {
+ return graph.getSingleObject(cur, L0.List_Next);
+ } catch(DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Resource browsePrev(Resource cur) {
+ try {
+ return graph.getSingleObject(cur, L0.List_Previous);
+ } catch(DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Resource browseElement(Resource cur) {
+ try {
+ LOGGER.info("browseElement " + cur);
+ return graph.getSingleObject(cur, elementRelation);
+ } catch(DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void addBetween(Resource prev, Resource next, Resource element) {
+ WriteGraph graph;
+ try {
+ graph = (WriteGraph)this.graph;
+ } catch(ClassCastException e) {
+ throw new UnsupportedOperationException("Cannot add element in read transaction.");
+ }
+ try {
+ graph.deny(prev, L0.List_Next, L0.List_Previous, next);
+
+ Resource newEntry = graph.newResource();
+ graph.claim(newEntry, L0.InstanceOf, L0.List_Entry);
+ graph.claim(newEntry, elementRelation, element);
+ graph.claim(newEntry, L0.IsOwnedBy, L0.IsComposedOf, root);
+ graph.claim(prev, L0.List_Next, L0.List_Previous, newEntry);
+ graph.claim(newEntry, L0.List_Next, L0.List_Previous, next);
+ } catch(DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void doSet(Resource target, Resource resource) {
+ WriteGraph graph;
+ try {
+ graph = (WriteGraph)this.graph;
+ } catch(ClassCastException e) {
+ throw new UnsupportedOperationException("Cannot set element in read transaction.");
+ }
+ try {
+ graph.deny(target, elementRelation);
+ graph.claim(target, elementRelation, resource);
+ } catch(DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void doRemove(Resource prev, Resource target, Resource next) {
+ WriteGraph graph;
+ try {
+ graph = (WriteGraph)this.graph;
+ } catch(ClassCastException e) {
+ throw new UnsupportedOperationException("Cannot remove element in read transaction.");
+ }
+ try {
+ graph.deny(target);
+ graph.claim(prev, L0.List_Next, L0.List_Previous, next);
+ } catch(DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int size() {
+ int count = 0;
+ for(Resource cur=browseNext(root);cur!=root;cur=browseNext(cur))
+ ++count;
+ return count;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ try {
+ return graph.hasStatement(root, L0.List_Next, root);
+ } catch (DatabaseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}