-/*******************************************************************************\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
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 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;
+
+import gnu.trove.map.TLongObjectMap;
+import gnu.trove.map.hash.TLongObjectHashMap;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.simantics.db.Operation;
+import org.simantics.db.UndoContext;
+import org.simantics.db.common.utils.Logger;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.NoHistoryException;
+import org.simantics.db.service.ExternalOperation;
+import org.simantics.db.service.UndoRedoSupport;
+import org.simantics.db.service.UndoRedoSupport.ChangeListener;
+import org.simantics.utils.threads.ThreadUtils;
+
+public class UndoContextEx implements UndoContext {
+ private static class Weak {
+ private final WeakReference<UndoContext> reference;
+ Weak(UndoContext context) {
+ reference = new WeakReference<UndoContext>(context);
+ }
+ UndoContext get() {
+ return reference.get();
+ }
+ }
+ private static class Operations {
+ private final Deque<Operation> operationsQue = new ArrayDeque<Operation>();
+ private final TLongObjectMap<Operation> operationsMap = new TLongObjectHashMap<Operation>();
+ void addLast(Operation op) {
+ long id = op.getId();
+ Operation old = operationsMap.put(id, op);
+ if (old != null) {
+ operationsQue.remove(old);
+ op.combine(old);
+ }
+ operationsQue.addLast(op);
+ fireChangeEvent();
+ }
+ Operation getLast() {
+ if (operationsQue.size() < 1)
+ return null;
+ Operation op = operationsQue.getLast();
+ if (DEBUG)
+ System.out.println("DEBUG: Get last id=" + op.getId() + ".");
+ return op;
+ }
+ Collection<Operation> getAll() {
+ return operationsQue;
+ }
+ Operation getCombined(long id) {
+ return operationsMap.get(id);
+ }
+ Operation removeLast() {
+ Operation op = operationsQue.pollLast();
+ if (null != op)
+ operationsMap.remove(op.getId());
+ fireChangeEvent();
+ return op;
+ }
+ Operation remove(long id) {
+ Operation op = operationsMap.remove(id);
+ if (null != op)
+ operationsQue.remove(op);
+ fireChangeEvent();
+ return op;
+ }
+ void clear() {
+ operationsQue.clear();
+ operationsMap.clear();
+ fireChangeEvent();
+ }
+ private final CopyOnWriteArrayList<ChangeListener> changeListeners = new CopyOnWriteArrayList<ChangeListener>();
+ private void fireChangeEvent() {
+ final Iterator<ChangeListener> it = changeListeners.iterator();
+ ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ while (it.hasNext()) {
+ ChangeListener l = it.next();
+ try {
+ l.onChanged();
+ } catch (Throwable t) {
+ Logger.defaultLogError(t);
+ }
+ }
+ }
+ });
+ }
+ void addChangeListener(ChangeListener cl) {
+ changeListeners.add(cl);
+ }
+ void removeChangeListener(ChangeListener cl) {
+ changeListeners.remove(cl);
+ }
+ }
+ private static HashMap<String, Weak> contexts = new HashMap<String, Weak>();
+ protected static final boolean DEBUG = false;
+ protected final boolean DISABLED = false;
+ private final Operations operations = new Operations();
+ private final Deque<Operation> redos = new ArrayDeque<Operation>();
+ private final ArrayList<ExternalOperation> pendingExternals = new ArrayList<ExternalOperation>();
+ private final String name;
+ private boolean undoRedoOn = false;
+ private final String id;
+ private boolean commitOkDisabled = false;
+
+ public UndoContextEx() {
+ this.id = getUniqueId(this.toString());
+ this.name = id;
+ init();
+ }
+ public UndoContextEx(String id) {
+ this.id = getUniqueId(id);
+ this.name = id;
+ init();
+ }
+ public UndoContextEx(String id, String name) {
+ this.id = getUniqueId(id);
+ this.name = name;
+ init();
+ }
+ private String getUniqueId(String id) {
+ String uniqueId = id;
+ int i=0;
+ while (contexts.containsKey(uniqueId)) {
+ uniqueId += "_" + ++i;
+ }
+ return uniqueId;
+ }
+ private void init() {
+ synchronized (this) {
+ Weak old = contexts.get(id);
+ assert(null == old);
+ contexts.put(id, new Weak(this));
+ }
+ }
+ public static synchronized UndoContext getUndoContext(String id) {
+ Weak w = contexts.get(id);
+ UndoContext c = null == w ? null : w.get();
+ return c;
+ }
+ public static synchronized Collection<UndoContext> getUndoContexts() {
+ Collection<UndoContext> ucs = new ArrayList<UndoContext>();
+ List<Weak> weaks = new ArrayList<Weak>();
+ List<String> stale = new ArrayList<String>();
+ for (Map.Entry<String, Weak> e : contexts.entrySet()) {
+ UndoContext c = e.getValue().get();
+ if (null != c) {
+ ucs.add(c);
+ weaks.add(new Weak(c));
+ } else
+ stale.add(e.getKey());
+ }
+ for (String id : stale)
+ contexts.remove(id);
+ return ucs;
+ }
+ @Override
+ public String toString() {
+ return name + "@" + Integer.toHexString(hashCode());
+ }
+ @Override
+ public void commitOk(Operation op) throws DatabaseException {
+ if (DISABLED)
+ return;
+ if (commitOkDisabled) {
+ if (DEBUG)
+ System.out.println("DEBUG: Commit ok disabled.");
+ return;
+ }
+ if (DEBUG)
+ System.out.println("DEBUG: Commit ok id=" + op.getId() + " cs=" + op.getCSId()
+ + " context=" + this + ".");
+ if (undoRedoOn) {
+ undoRedoOn = false;
+ redos.clear();
+ }
+ // Check if operation is new.
+ if (op.getId() < 1 || op.getId() == op.getCSId()) {
+ Operation last = getLast();
+ if (null != last && last.getCSId() == op.getCSId()) {
+ Logger.defaultLogError("Duplicate operation for new operation id=" + op.getId() + ".");
+ if (DEBUG)
+ System.out.println("DEBUG: New operation already in undo list id=" + op.getId() + ".");
+ return;
+ }
+ if (DEBUG)
+ System.out.println("DEBUG: New operation added to undo list id=" + op.getId() + ".");
+ } else { // Operation is old i.e. part of combined operation.
+ Operation last = operations.getCombined(op.getId());
+ if (null == last) {
+ Logger.defaultLogError("Missing operation for combined operation id=" + op.getId() + " cs=" + op.getCSId() + ".");
+ if (DEBUG)
+ System.out.println("DEBUG: Missing old combined operation id=" + op.getId() + ".");
+ }
+ }
+ operations.addLast(op);
+ pendingExternals.clear();
+ }
+ public void externalCommit(Operation op) {
+ operations.addLast(op);
+ pendingExternals.clear();
+ }
+ public void cancelCommit() {
+ pendingExternals.clear();
+ }
+
+ @Override
+ public Operation getLast() throws DatabaseException {
+ return operations.getLast();
+ }
+ protected Operation getLastRedo() throws DatabaseException {
+ if (redos.size() < 1)
+ return null;
+ Operation op = redos.getLast();
+ if (DEBUG)
+ System.out.println("DEBUG: Get last redo id=" + op.getId() + ".");
+ return op;
+ }
+ @Override
+ public Collection<Operation> getAll() throws DatabaseException {
+ return operations.getAll();
+ }
+ @Override
+ public Collection<Operation> getRedoList() throws DatabaseException {
+ return redos;
+ }
+ @Override
+ public List<Operation> undo(UndoRedoSupport support, int count) throws DatabaseException {
+ List<Operation> ops = new ArrayList<Operation>(count);
+ for (int i=0; i<count; ++i) {
+ Operation op = operations.removeLast();
+ if (null == op)
+ break;
+ for (Operation o : op.getOperations())
+ ops.add(o);
+ }
+ if (ops.size()< 1)
+ throw new NoHistoryException("Illegal call, undo list is empty.");
+ commitOkDisabled = true;
+ Operation redo = null;
+ try {
+ redo = support.undo(ops);
+ } finally {
+ commitOkDisabled = false;
+ }
+ if (null == redo)
+ return Collections.emptyList();
+ redos.add(redo);
+ undoRedoOn = true;
+ return ops;
+ }
+ @Override
+ public List<Operation> redo(UndoRedoSupport support, int count) throws DatabaseException {
+ List<Operation> ops = new ArrayList<Operation>(count);
+ for (int i=0; i<count; ++i) {
+ Operation op = removeLastRedo();
+ if (null == op)
+ break;
+ for (Operation o : op.getOperations())
+ ops.add(o);
+ }
+ if (ops.size() < 1)
+ throw new NoHistoryException("Illegal call, redo list is empty.");
+ Operation undo = null;
+ try {
+ commitOkDisabled = true;
+ undo = support.undo(ops);
+ } finally {
+ commitOkDisabled = false;
+ }
+ if (null == undo)
+ return Collections.emptyList();
+ if (DEBUG)
+ System.out.println("New operation added to undo list id=" + undo.getId() + ".");
+ operations.addLast(undo);
+ undoRedoOn = true;
+ return ops;
+ }
+ @Override
+ public void clear() {
+ operations.clear();
+ redos.clear();
+ pendingExternals.clear();
+ }
+ Operation removeLastRedo() {
+ if (redos.size() < 1)
+ return null;
+ Operation op = redos.removeLast();
+ if (DEBUG)
+ System.out.println("Remove last redo id=" + op.getId() + ".");
+ return op;
+ }
+ void undoPair(Operation undo, Operation redo) {
+ operations.remove(undo.getId());
+ redos.addLast(redo);
+ }
+ void redoPair(Operation redo, Operation undo) {
+ redos.remove(redo);
+ operations.addLast(undo);
+ }
+ public void addChangeListener(ChangeListener cl) {
+ operations.addChangeListener(cl);
+ }
+ public void removeChangeListener(ChangeListener cl) {
+ operations.removeChangeListener(cl);
+ }
+ public void addExternalOperation(ExternalOperation op) {
+ pendingExternals.add(op);
+ }
+ public List<ExternalOperation> getPendingExternals() {
+ if(pendingExternals.isEmpty()) return Collections.emptyList();
+ if(!pendingExternals.isEmpty())
+ System.err.println("pending externals: " + pendingExternals);
+ return new ArrayList<ExternalOperation>(pendingExternals);
+ }
+
+}