]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.common/src/org/simantics/db/common/UndoContextEx.java
Merge commit 'bf75fd9'
[simantics/platform.git] / bundles / org.simantics.db.common / src / org / simantics / db / common / UndoContextEx.java
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
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.db.common;\r
13 \r
14 import gnu.trove.map.TLongObjectMap;\r
15 import gnu.trove.map.hash.TLongObjectHashMap;\r
16 \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
28 \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
38 \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
44         }\r
45         UndoContext get() {\r
46             return reference.get();\r
47         }\r
48     }\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
55             if (old != null) {\r
56                 operationsQue.remove(old);\r
57                 op.combine(old);\r
58             }\r
59             operationsQue.addLast(op);\r
60             fireChangeEvent();\r
61         }\r
62         Operation getLast() {\r
63             if (operationsQue.size() < 1)\r
64                 return null;\r
65             Operation op = operationsQue.getLast();\r
66             if (DEBUG)\r
67                 System.out.println("DEBUG: Get last id=" + op.getId() + ".");\r
68             return op;\r
69         }\r
70         Collection<Operation> getAll() {\r
71             return operationsQue;\r
72         }\r
73         Operation getCombined(long id) {\r
74             return operationsMap.get(id);\r
75         }\r
76         Operation removeLast() {\r
77             Operation op = operationsQue.pollLast();\r
78             if (null != op)\r
79                 operationsMap.remove(op.getId());\r
80             fireChangeEvent();\r
81             return op;\r
82         }\r
83         Operation remove(long id) {\r
84             Operation op = operationsMap.remove(id);\r
85             if (null != op)\r
86                 operationsQue.remove(op);\r
87             fireChangeEvent();\r
88             return op;\r
89         }\r
90         void clear() {\r
91             operationsQue.clear();\r
92             operationsMap.clear();\r
93             fireChangeEvent();\r
94         }\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
99                 @Override\r
100                 public void run() {\r
101                     while (it.hasNext()) {\r
102                         ChangeListener l = it.next();\r
103                         try {\r
104                             l.onChanged();\r
105                         } catch (Throwable t) {\r
106                             Logger.defaultLogError(t);\r
107                         }\r
108                     }\r
109                 }\r
110             });\r
111         }\r
112         void addChangeListener(ChangeListener cl) {\r
113             changeListeners.add(cl);\r
114         }\r
115         void removeChangeListener(ChangeListener cl) {\r
116             changeListeners.remove(cl);\r
117         }\r
118     }\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
129 \r
130     public UndoContextEx() {\r
131         this.id = getUniqueId(this.toString());\r
132         this.name = id;\r
133         init();\r
134     }\r
135     public UndoContextEx(String id) {\r
136         this.id = getUniqueId(id);\r
137         this.name = id;\r
138         init();\r
139     }\r
140     public UndoContextEx(String id, String name) {\r
141         this.id = getUniqueId(id);\r
142         this.name = name;\r
143         init();\r
144     }\r
145     private String getUniqueId(String id) {\r
146         String uniqueId = id;\r
147         int i=0;\r
148         while (contexts.containsKey(uniqueId)) {\r
149             uniqueId += "_" + ++i;\r
150         }\r
151         return uniqueId;\r
152     }\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
158         }\r
159     }\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
163         return c; \r
164     }\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
171             if (null != c) {\r
172                 ucs.add(c);\r
173                 weaks.add(new Weak(c));\r
174             } else\r
175                 stale.add(e.getKey());\r
176         }\r
177         for (String id : stale)\r
178             contexts.remove(id);\r
179         return ucs;\r
180     }\r
181     @Override\r
182     public String toString() {\r
183         return name + "@" + Integer.toHexString(hashCode());\r
184     }\r
185     @Override\r
186     public void commitOk(Operation op) throws DatabaseException {\r
187         if (DISABLED)\r
188             return;\r
189         if (commitOkDisabled) {\r
190             if (DEBUG)\r
191                 System.out.println("DEBUG: Commit ok disabled.");\r
192             return;\r
193         }\r
194         if (DEBUG)\r
195             System.out.println("DEBUG: Commit ok id=" + op.getId() + " cs=" + op.getCSId()\r
196                 + " context=" + this + ".");\r
197         if (undoRedoOn) {\r
198             undoRedoOn = false;\r
199             redos.clear();\r
200         }\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
206                 if (DEBUG)\r
207                     System.out.println("DEBUG: New operation already in undo list id=" + op.getId() + ".");\r
208                 return;\r
209             }\r
210             if (DEBUG)\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
216                 if (DEBUG)\r
217                     System.out.println("DEBUG: Missing old combined operation id=" + op.getId() + ".");\r
218             }\r
219         }\r
220         operations.addLast(op);\r
221         pendingExternals.clear();\r
222     }\r
223     public void externalCommit(Operation op) {\r
224         operations.addLast(op);\r
225         pendingExternals.clear();\r
226     }\r
227     public void cancelCommit() {\r
228         pendingExternals.clear();\r
229     }\r
230     \r
231     @Override\r
232     public Operation getLast() throws DatabaseException {\r
233         return operations.getLast();\r
234     }\r
235     protected Operation getLastRedo() throws DatabaseException {\r
236         if (redos.size() < 1)\r
237             return null;\r
238         Operation op = redos.getLast();\r
239         if (DEBUG)\r
240             System.out.println("DEBUG: Get last redo id=" + op.getId() + ".");\r
241         return op;\r
242     }\r
243     @Override\r
244     public Collection<Operation> getAll() throws DatabaseException {\r
245         return operations.getAll();\r
246     }\r
247     @Override\r
248     public Collection<Operation> getRedoList() throws DatabaseException {\r
249         return redos;\r
250     }\r
251     @Override\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
256             if (null == op)\r
257                 break;\r
258             for (Operation o : op.getOperations())\r
259                 ops.add(o);\r
260         }\r
261         if (ops.size()< 1)\r
262             throw new NoHistoryException("Illegal call, undo list is empty.");\r
263         commitOkDisabled = true;\r
264         Operation redo = null;\r
265         try {\r
266             redo = support.undo(ops);\r
267         } finally {\r
268             commitOkDisabled = false;\r
269         }\r
270         if (null == redo)\r
271             return Collections.emptyList();\r
272         redos.add(redo);\r
273         undoRedoOn = true;\r
274         return ops;\r
275     }\r
276     @Override\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
281             if (null == op)\r
282                 break;\r
283             for (Operation o : op.getOperations())\r
284                 ops.add(o);\r
285         }\r
286         if (ops.size() < 1)\r
287             throw new NoHistoryException("Illegal call, redo list is empty.");\r
288         Operation undo = null;\r
289         try {\r
290             commitOkDisabled = true;\r
291             undo = support.undo(ops);\r
292         } finally {\r
293             commitOkDisabled = false;\r
294         }\r
295         if (null == undo)\r
296             return Collections.emptyList();\r
297         if (DEBUG)\r
298             System.out.println("New operation added to undo list id=" + undo.getId() + ".");\r
299         operations.addLast(undo);\r
300         undoRedoOn = true;\r
301         return ops;\r
302     }\r
303     @Override\r
304     public void clear() {\r
305         operations.clear();\r
306         redos.clear();\r
307         pendingExternals.clear();\r
308     }\r
309     Operation removeLastRedo() {\r
310         if (redos.size() < 1)\r
311             return null;\r
312         Operation op = redos.removeLast();\r
313         if (DEBUG)\r
314             System.out.println("Remove last redo id=" + op.getId() + ".");\r
315         return op;\r
316     }\r
317     void undoPair(Operation undo, Operation redo) {\r
318         operations.remove(undo.getId());\r
319         redos.addLast(redo);\r
320     }\r
321     void redoPair(Operation redo, Operation undo) {\r
322         redos.remove(redo);\r
323         operations.addLast(undo);\r
324     }\r
325     public void addChangeListener(ChangeListener cl) {\r
326         operations.addChangeListener(cl);\r
327     }\r
328     public void removeChangeListener(ChangeListener cl) {\r
329         operations.removeChangeListener(cl);\r
330     }\r
331     public void addExternalOperation(ExternalOperation op) {\r
332         pendingExternals.add(op);\r
333     }\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
339     }\r
340     \r
341 }\r