--- /dev/null
+/*******************************************************************************\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