1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.db.common;
\r
14 import gnu.trove.map.TLongObjectMap;
\r
15 import gnu.trove.map.hash.TLongObjectHashMap;
\r
17 import java.lang.ref.WeakReference;
\r
18 import java.util.ArrayDeque;
\r
19 import java.util.ArrayList;
\r
20 import java.util.Collection;
\r
21 import java.util.Collections;
\r
22 import java.util.Deque;
\r
23 import java.util.HashMap;
\r
24 import java.util.Iterator;
\r
25 import java.util.List;
\r
26 import java.util.Map;
\r
27 import java.util.concurrent.CopyOnWriteArrayList;
\r
29 import org.simantics.db.Operation;
\r
30 import org.simantics.db.UndoContext;
\r
31 import org.simantics.db.common.utils.Logger;
\r
32 import org.simantics.db.exception.DatabaseException;
\r
33 import org.simantics.db.exception.NoHistoryException;
\r
34 import org.simantics.db.service.ExternalOperation;
\r
35 import org.simantics.db.service.UndoRedoSupport;
\r
36 import org.simantics.db.service.UndoRedoSupport.ChangeListener;
\r
37 import org.simantics.utils.threads.ThreadUtils;
\r
39 public class UndoContextEx implements UndoContext {
\r
40 private static class Weak {
\r
41 private final WeakReference<UndoContext> reference;
\r
42 Weak(UndoContext context) {
\r
43 reference = new WeakReference<UndoContext>(context);
\r
46 return reference.get();
\r
49 private static class Operations {
\r
50 private final Deque<Operation> operationsQue = new ArrayDeque<Operation>();
\r
51 private final TLongObjectMap<Operation> operationsMap = new TLongObjectHashMap<Operation>();
\r
52 void addLast(Operation op) {
\r
53 long id = op.getId();
\r
54 Operation old = operationsMap.put(id, op);
\r
56 operationsQue.remove(old);
\r
59 operationsQue.addLast(op);
\r
62 Operation getLast() {
\r
63 if (operationsQue.size() < 1)
\r
65 Operation op = operationsQue.getLast();
\r
67 System.out.println("DEBUG: Get last id=" + op.getId() + ".");
\r
70 Collection<Operation> getAll() {
\r
71 return operationsQue;
\r
73 Operation getCombined(long id) {
\r
74 return operationsMap.get(id);
\r
76 Operation removeLast() {
\r
77 Operation op = operationsQue.pollLast();
\r
79 operationsMap.remove(op.getId());
\r
83 Operation remove(long id) {
\r
84 Operation op = operationsMap.remove(id);
\r
86 operationsQue.remove(op);
\r
91 operationsQue.clear();
\r
92 operationsMap.clear();
\r
95 private final CopyOnWriteArrayList<ChangeListener> changeListeners = new CopyOnWriteArrayList<ChangeListener>();
\r
96 private void fireChangeEvent() {
\r
97 final Iterator<ChangeListener> it = changeListeners.iterator();
\r
98 ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {
\r
100 public void run() {
\r
101 while (it.hasNext()) {
\r
102 ChangeListener l = it.next();
\r
105 } catch (Throwable t) {
\r
106 Logger.defaultLogError(t);
\r
112 void addChangeListener(ChangeListener cl) {
\r
113 changeListeners.add(cl);
\r
115 void removeChangeListener(ChangeListener cl) {
\r
116 changeListeners.remove(cl);
\r
119 private static HashMap<String, Weak> contexts = new HashMap<String, Weak>();
\r
120 protected static final boolean DEBUG = false;
\r
121 protected final boolean DISABLED = false;
\r
122 private final Operations operations = new Operations();
\r
123 private final Deque<Operation> redos = new ArrayDeque<Operation>();
\r
124 private final ArrayList<ExternalOperation> pendingExternals = new ArrayList<ExternalOperation>();
\r
125 private final String name;
\r
126 private boolean undoRedoOn = false;
\r
127 private final String id;
\r
128 private boolean commitOkDisabled = false;
\r
130 public UndoContextEx() {
\r
131 this.id = getUniqueId(this.toString());
\r
135 public UndoContextEx(String id) {
\r
136 this.id = getUniqueId(id);
\r
140 public UndoContextEx(String id, String name) {
\r
141 this.id = getUniqueId(id);
\r
145 private String getUniqueId(String id) {
\r
146 String uniqueId = id;
\r
148 while (contexts.containsKey(uniqueId)) {
\r
149 uniqueId += "_" + ++i;
\r
153 private void init() {
\r
154 synchronized (this) {
\r
155 Weak old = contexts.get(id);
\r
156 assert(null == old);
\r
157 contexts.put(id, new Weak(this));
\r
160 public static synchronized UndoContext getUndoContext(String id) {
\r
161 Weak w = contexts.get(id);
\r
162 UndoContext c = null == w ? null : w.get();
\r
165 public static synchronized Collection<UndoContext> getUndoContexts() {
\r
166 Collection<UndoContext> ucs = new ArrayList<UndoContext>();
\r
167 List<Weak> weaks = new ArrayList<Weak>();
\r
168 List<String> stale = new ArrayList<String>();
\r
169 for (Map.Entry<String, Weak> e : contexts.entrySet()) {
\r
170 UndoContext c = e.getValue().get();
\r
173 weaks.add(new Weak(c));
\r
175 stale.add(e.getKey());
\r
177 for (String id : stale)
\r
178 contexts.remove(id);
\r
182 public String toString() {
\r
183 return name + "@" + Integer.toHexString(hashCode());
\r
186 public void commitOk(Operation op) throws DatabaseException {
\r
189 if (commitOkDisabled) {
\r
191 System.out.println("DEBUG: Commit ok disabled.");
\r
195 System.out.println("DEBUG: Commit ok id=" + op.getId() + " cs=" + op.getCSId()
\r
196 + " context=" + this + ".");
\r
198 undoRedoOn = false;
\r
201 // Check if operation is new.
\r
202 if (op.getId() < 1 || op.getId() == op.getCSId()) {
\r
203 Operation last = getLast();
\r
204 if (null != last && last.getCSId() == op.getCSId()) {
\r
205 Logger.defaultLogError("Duplicate operation for new operation id=" + op.getId() + ".");
\r
207 System.out.println("DEBUG: New operation already in undo list id=" + op.getId() + ".");
\r
211 System.out.println("DEBUG: New operation added to undo list id=" + op.getId() + ".");
\r
212 } else { // Operation is old i.e. part of combined operation.
\r
213 Operation last = operations.getCombined(op.getId());
\r
214 if (null == last) {
\r
215 Logger.defaultLogError("Missing operation for combined operation id=" + op.getId() + " cs=" + op.getCSId() + ".");
\r
217 System.out.println("DEBUG: Missing old combined operation id=" + op.getId() + ".");
\r
220 operations.addLast(op);
\r
221 pendingExternals.clear();
\r
223 public void externalCommit(Operation op) {
\r
224 operations.addLast(op);
\r
225 pendingExternals.clear();
\r
227 public void cancelCommit() {
\r
228 pendingExternals.clear();
\r
232 public Operation getLast() throws DatabaseException {
\r
233 return operations.getLast();
\r
235 protected Operation getLastRedo() throws DatabaseException {
\r
236 if (redos.size() < 1)
\r
238 Operation op = redos.getLast();
\r
240 System.out.println("DEBUG: Get last redo id=" + op.getId() + ".");
\r
244 public Collection<Operation> getAll() throws DatabaseException {
\r
245 return operations.getAll();
\r
248 public Collection<Operation> getRedoList() throws DatabaseException {
\r
252 public List<Operation> undo(UndoRedoSupport support, int count) throws DatabaseException {
\r
253 List<Operation> ops = new ArrayList<Operation>(count);
\r
254 for (int i=0; i<count; ++i) {
\r
255 Operation op = operations.removeLast();
\r
258 for (Operation o : op.getOperations())
\r
262 throw new NoHistoryException("Illegal call, undo list is empty.");
\r
263 commitOkDisabled = true;
\r
264 Operation redo = null;
\r
266 redo = support.undo(ops);
\r
268 commitOkDisabled = false;
\r
271 return Collections.emptyList();
\r
277 public List<Operation> redo(UndoRedoSupport support, int count) throws DatabaseException {
\r
278 List<Operation> ops = new ArrayList<Operation>(count);
\r
279 for (int i=0; i<count; ++i) {
\r
280 Operation op = removeLastRedo();
\r
283 for (Operation o : op.getOperations())
\r
286 if (ops.size() < 1)
\r
287 throw new NoHistoryException("Illegal call, redo list is empty.");
\r
288 Operation undo = null;
\r
290 commitOkDisabled = true;
\r
291 undo = support.undo(ops);
\r
293 commitOkDisabled = false;
\r
296 return Collections.emptyList();
\r
298 System.out.println("New operation added to undo list id=" + undo.getId() + ".");
\r
299 operations.addLast(undo);
\r
304 public void clear() {
\r
305 operations.clear();
\r
307 pendingExternals.clear();
\r
309 Operation removeLastRedo() {
\r
310 if (redos.size() < 1)
\r
312 Operation op = redos.removeLast();
\r
314 System.out.println("Remove last redo id=" + op.getId() + ".");
\r
317 void undoPair(Operation undo, Operation redo) {
\r
318 operations.remove(undo.getId());
\r
319 redos.addLast(redo);
\r
321 void redoPair(Operation redo, Operation undo) {
\r
322 redos.remove(redo);
\r
323 operations.addLast(undo);
\r
325 public void addChangeListener(ChangeListener cl) {
\r
326 operations.addChangeListener(cl);
\r
328 public void removeChangeListener(ChangeListener cl) {
\r
329 operations.removeChangeListener(cl);
\r
331 public void addExternalOperation(ExternalOperation op) {
\r
332 pendingExternals.add(op);
\r
334 public List<ExternalOperation> getPendingExternals() {
\r
335 if(pendingExternals.isEmpty()) return Collections.emptyList();
\r
336 if(!pendingExternals.isEmpty())
\r
337 System.err.println("pending externals: " + pendingExternals);
\r
338 return new ArrayList<ExternalOperation>(pendingExternals);
\r