]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.common/src/org/simantics/db/common/UndoContextEx.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.db.common / src / org / simantics / db / common / UndoContextEx.java
diff --git a/bundles/org.simantics.db.common/src/org/simantics/db/common/UndoContextEx.java b/bundles/org.simantics.db.common/src/org/simantics/db/common/UndoContextEx.java
new file mode 100644 (file)
index 0000000..516426d
--- /dev/null
@@ -0,0 +1,341 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.db.common;\r
+\r
+import gnu.trove.map.TLongObjectMap;\r
+import gnu.trove.map.hash.TLongObjectHashMap;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.ArrayDeque;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Deque;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+\r
+import org.simantics.db.Operation;\r
+import org.simantics.db.UndoContext;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.NoHistoryException;\r
+import org.simantics.db.service.ExternalOperation;\r
+import org.simantics.db.service.UndoRedoSupport;\r
+import org.simantics.db.service.UndoRedoSupport.ChangeListener;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+\r
+public class UndoContextEx implements UndoContext {\r
+    private static class Weak {\r
+        private final WeakReference<UndoContext> reference;\r
+        Weak(UndoContext context) {\r
+            reference = new WeakReference<UndoContext>(context);\r
+        }\r
+        UndoContext get() {\r
+            return reference.get();\r
+        }\r
+    }\r
+    private static class Operations {\r
+        private final Deque<Operation> operationsQue = new ArrayDeque<Operation>();\r
+        private final TLongObjectMap<Operation> operationsMap = new TLongObjectHashMap<Operation>();\r
+        void addLast(Operation op) {\r
+            long id = op.getId();\r
+            Operation old = operationsMap.put(id, op);\r
+            if (old != null) {\r
+                operationsQue.remove(old);\r
+                op.combine(old);\r
+            }\r
+            operationsQue.addLast(op);\r
+            fireChangeEvent();\r
+        }\r
+        Operation getLast() {\r
+            if (operationsQue.size() < 1)\r
+                return null;\r
+            Operation op = operationsQue.getLast();\r
+            if (DEBUG)\r
+                System.out.println("DEBUG: Get last id=" + op.getId() + ".");\r
+            return op;\r
+        }\r
+        Collection<Operation> getAll() {\r
+            return operationsQue;\r
+        }\r
+        Operation getCombined(long id) {\r
+            return operationsMap.get(id);\r
+        }\r
+        Operation removeLast() {\r
+            Operation op = operationsQue.pollLast();\r
+            if (null != op)\r
+                operationsMap.remove(op.getId());\r
+            fireChangeEvent();\r
+            return op;\r
+        }\r
+        Operation remove(long id) {\r
+            Operation op = operationsMap.remove(id);\r
+            if (null != op)\r
+                operationsQue.remove(op);\r
+            fireChangeEvent();\r
+            return op;\r
+        }\r
+        void clear() {\r
+            operationsQue.clear();\r
+            operationsMap.clear();\r
+            fireChangeEvent();\r
+        }\r
+        private final CopyOnWriteArrayList<ChangeListener> changeListeners = new CopyOnWriteArrayList<ChangeListener>(); \r
+        private void fireChangeEvent() {\r
+            final Iterator<ChangeListener> it = changeListeners.iterator();\r
+            ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                    while (it.hasNext()) {\r
+                        ChangeListener l = it.next();\r
+                        try {\r
+                            l.onChanged();\r
+                        } catch (Throwable t) {\r
+                            Logger.defaultLogError(t);\r
+                        }\r
+                    }\r
+                }\r
+            });\r
+        }\r
+        void addChangeListener(ChangeListener cl) {\r
+            changeListeners.add(cl);\r
+        }\r
+        void removeChangeListener(ChangeListener cl) {\r
+            changeListeners.remove(cl);\r
+        }\r
+    }\r
+    private static HashMap<String, Weak> contexts = new HashMap<String, Weak>(); \r
+    protected static final boolean DEBUG = false;\r
+    protected final boolean DISABLED = false;\r
+    private final Operations operations = new Operations();\r
+    private final Deque<Operation> redos = new ArrayDeque<Operation>();\r
+    private final ArrayList<ExternalOperation> pendingExternals = new ArrayList<ExternalOperation>();\r
+    private final String name;\r
+    private boolean undoRedoOn = false;\r
+    private final String id;\r
+    private boolean commitOkDisabled = false;\r
+\r
+    public UndoContextEx() {\r
+        this.id = getUniqueId(this.toString());\r
+        this.name = id;\r
+        init();\r
+    }\r
+    public UndoContextEx(String id) {\r
+        this.id = getUniqueId(id);\r
+        this.name = id;\r
+        init();\r
+    }\r
+    public UndoContextEx(String id, String name) {\r
+        this.id = getUniqueId(id);\r
+        this.name = name;\r
+        init();\r
+    }\r
+    private String getUniqueId(String id) {\r
+        String uniqueId = id;\r
+        int i=0;\r
+        while (contexts.containsKey(uniqueId)) {\r
+            uniqueId += "_" + ++i;\r
+        }\r
+        return uniqueId;\r
+    }\r
+    private void init() {\r
+        synchronized (this) {\r
+            Weak old = contexts.get(id);\r
+            assert(null == old);\r
+            contexts.put(id, new Weak(this));\r
+        }\r
+    }\r
+    public static synchronized UndoContext getUndoContext(String id) {\r
+        Weak w = contexts.get(id);\r
+        UndoContext c = null == w ? null : w.get();\r
+        return c; \r
+    }\r
+    public static synchronized Collection<UndoContext> getUndoContexts() {\r
+        Collection<UndoContext> ucs = new ArrayList<UndoContext>();\r
+        List<Weak> weaks = new ArrayList<Weak>();\r
+        List<String> stale = new ArrayList<String>();\r
+        for (Map.Entry<String, Weak> e : contexts.entrySet()) {\r
+            UndoContext c = e.getValue().get();\r
+            if (null != c) {\r
+                ucs.add(c);\r
+                weaks.add(new Weak(c));\r
+            } else\r
+                stale.add(e.getKey());\r
+        }\r
+        for (String id : stale)\r
+            contexts.remove(id);\r
+        return ucs;\r
+    }\r
+    @Override\r
+    public String toString() {\r
+        return name + "@" + Integer.toHexString(hashCode());\r
+    }\r
+    @Override\r
+    public void commitOk(Operation op) throws DatabaseException {\r
+       if (DISABLED)\r
+           return;\r
+       if (commitOkDisabled) {\r
+            if (DEBUG)\r
+                System.out.println("DEBUG: Commit ok disabled.");\r
+           return;\r
+       }\r
+        if (DEBUG)\r
+            System.out.println("DEBUG: Commit ok id=" + op.getId() + " cs=" + op.getCSId()\r
+                + " context=" + this + ".");\r
+        if (undoRedoOn) {\r
+            undoRedoOn = false;\r
+            redos.clear();\r
+        }\r
+        // Check if operation is new. \r
+        if (op.getId() < 1 || op.getId() == op.getCSId()) {\r
+            Operation last = getLast();\r
+            if (null != last && last.getCSId() == op.getCSId()) {\r
+                Logger.defaultLogError("Duplicate operation for new operation id=" + op.getId() + ".");\r
+                if (DEBUG)\r
+                    System.out.println("DEBUG: New operation already in undo list id=" + op.getId() + ".");\r
+                return;\r
+            }\r
+            if (DEBUG)\r
+                System.out.println("DEBUG: New operation added to undo list id=" + op.getId() + ".");\r
+        } else { // Operation is old i.e. part of combined operation.\r
+            Operation last = operations.getCombined(op.getId());\r
+            if (null == last) {\r
+                Logger.defaultLogError("Missing operation for combined operation id=" + op.getId() + " cs=" + op.getCSId() + ".");\r
+                if (DEBUG)\r
+                    System.out.println("DEBUG: Missing old combined operation id=" + op.getId() + ".");\r
+            }\r
+        }\r
+        operations.addLast(op);\r
+        pendingExternals.clear();\r
+    }\r
+    public void externalCommit(Operation op) {\r
+        operations.addLast(op);\r
+       pendingExternals.clear();\r
+    }\r
+    public void cancelCommit() {\r
+       pendingExternals.clear();\r
+    }\r
+    \r
+    @Override\r
+    public Operation getLast() throws DatabaseException {\r
+        return operations.getLast();\r
+    }\r
+    protected Operation getLastRedo() throws DatabaseException {\r
+        if (redos.size() < 1)\r
+            return null;\r
+        Operation op = redos.getLast();\r
+        if (DEBUG)\r
+            System.out.println("DEBUG: Get last redo id=" + op.getId() + ".");\r
+        return op;\r
+    }\r
+    @Override\r
+    public Collection<Operation> getAll() throws DatabaseException {\r
+        return operations.getAll();\r
+    }\r
+    @Override\r
+    public Collection<Operation> getRedoList() throws DatabaseException {\r
+        return redos;\r
+    }\r
+    @Override\r
+    public List<Operation> undo(UndoRedoSupport support, int count) throws DatabaseException {\r
+        List<Operation> ops = new ArrayList<Operation>(count);\r
+        for (int i=0; i<count; ++i) {\r
+            Operation op = operations.removeLast();\r
+            if (null == op)\r
+                break;\r
+            for (Operation o : op.getOperations())\r
+                ops.add(o);\r
+        }\r
+        if (ops.size()< 1)\r
+            throw new NoHistoryException("Illegal call, undo list is empty.");\r
+        commitOkDisabled = true;\r
+        Operation redo = null;\r
+        try {\r
+            redo = support.undo(ops);\r
+        } finally {\r
+            commitOkDisabled = false;\r
+        }\r
+        if (null == redo)\r
+            return Collections.emptyList();\r
+        redos.add(redo);\r
+        undoRedoOn = true;\r
+        return ops;\r
+    }\r
+    @Override\r
+    public List<Operation> redo(UndoRedoSupport support, int count) throws DatabaseException {\r
+        List<Operation> ops = new ArrayList<Operation>(count);\r
+        for (int i=0; i<count; ++i) {\r
+            Operation op = removeLastRedo();\r
+            if (null == op)\r
+                break;\r
+            for (Operation o : op.getOperations())\r
+                ops.add(o);\r
+        }\r
+        if (ops.size() < 1)\r
+            throw new NoHistoryException("Illegal call, redo list is empty.");\r
+        Operation undo = null;\r
+        try {\r
+            commitOkDisabled = true;\r
+            undo = support.undo(ops);\r
+        } finally {\r
+            commitOkDisabled = false;\r
+        }\r
+        if (null == undo)\r
+            return Collections.emptyList();\r
+        if (DEBUG)\r
+            System.out.println("New operation added to undo list id=" + undo.getId() + ".");\r
+        operations.addLast(undo);\r
+        undoRedoOn = true;\r
+        return ops;\r
+    }\r
+    @Override\r
+    public void clear() {\r
+        operations.clear();\r
+        redos.clear();\r
+        pendingExternals.clear();\r
+    }\r
+    Operation removeLastRedo() {\r
+        if (redos.size() < 1)\r
+            return null;\r
+        Operation op = redos.removeLast();\r
+        if (DEBUG)\r
+            System.out.println("Remove last redo id=" + op.getId() + ".");\r
+        return op;\r
+    }\r
+    void undoPair(Operation undo, Operation redo) {\r
+        operations.remove(undo.getId());\r
+        redos.addLast(redo);\r
+    }\r
+    void redoPair(Operation redo, Operation undo) {\r
+        redos.remove(redo);\r
+        operations.addLast(undo);\r
+    }\r
+    public void addChangeListener(ChangeListener cl) {\r
+        operations.addChangeListener(cl);\r
+    }\r
+    public void removeChangeListener(ChangeListener cl) {\r
+        operations.removeChangeListener(cl);\r
+    }\r
+    public void addExternalOperation(ExternalOperation op) {\r
+       pendingExternals.add(op);\r
+    }\r
+    public List<ExternalOperation> getPendingExternals() {\r
+       if(pendingExternals.isEmpty()) return Collections.emptyList();\r
+       if(!pendingExternals.isEmpty())\r
+               System.err.println("pending externals: " + pendingExternals);\r
+       return new ArrayList<ExternalOperation>(pendingExternals);\r
+    }\r
+    \r
+}\r