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