GraphList: List interface to linked lists in Simantics database 91/1791/1
authorHannu Niemistö <hannu.niemisto@semantum.fi>
Fri, 18 May 2018 09:43:12 +0000 (12:43 +0300)
committerHannu Niemistö <hannu.niemisto@semantum.fi>
Fri, 18 May 2018 09:43:12 +0000 (12:43 +0300)
gitlab #8

Change-Id: I87a1dc12d84c7875db075dec09ad08a60d8e40d1

bundles/org.simantics.db.common/src/org/simantics/db/common/utils/GraphList.java [new file with mode: 0644]

diff --git a/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/GraphList.java b/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/GraphList.java
new file mode 100644 (file)
index 0000000..dc7fab7
--- /dev/null
@@ -0,0 +1,250 @@
+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);
+        }
+    }
+}