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