]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.g3d/src/org/simantics/g3d/scl/ScriptNodeMap.java
Use generics type variable for mapping db object.
[simantics/3d.git] / org.simantics.g3d / src / org / simantics / g3d / scl / ScriptNodeMap.java
1 package org.simantics.g3d.scl;
2
3 import java.util.ArrayDeque;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.Deque;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Set;
11 import java.util.Stack;
12
13 import org.simantics.db.ReadGraph;
14 import org.simantics.db.Session;
15 import org.simantics.db.WriteGraph;
16 import org.simantics.db.common.request.ReadRequest;
17 import org.simantics.db.common.request.WriteRequest;
18 import org.simantics.db.exception.DatabaseException;
19 import org.simantics.db.layer0.util.Layer0Utils;
20 import org.simantics.g3d.ontology.G3D;
21 import org.simantics.g3d.scenegraph.NodeMap;
22 import org.simantics.g3d.scenegraph.base.INode;
23 import org.simantics.g3d.scenegraph.base.NodeListener;
24 import org.simantics.g3d.scenegraph.base.ParentNode;
25 import org.simantics.objmap.exceptions.MappingException;
26 import org.simantics.objmap.graph.IMapping;
27 import org.simantics.objmap.graph.IMappingListener;
28 import org.simantics.utils.datastructures.MapSet;
29 import org.simantics.utils.datastructures.Pair;
30
31 /**
32  * NodeMap implementation used with SCL scripts.
33  * 
34  * In practice, there are no visible objects to synchronize.
35  * 
36  * TODO:
37  * This implementation is Copy-paste of AbstractVTKNodeMap with VTK references removed.
38  * There may be more sensible way to do this.
39  * 
40  * 
41  * @author luukkainen
42  *
43  * @param <E>
44  */
45 public abstract class ScriptNodeMap<DBObject,E extends INode> implements NodeMap<DBObject,Object,E>, IMappingListener, NodeListener {
46
47         private static final boolean DEBUG = false;
48         
49         protected Session session;
50         protected IMapping<DBObject,E> mapping;
51         
52         protected ParentNode<E> rootNode;
53         
54         protected Set<E> nodes = new HashSet<E>();
55         
56         private boolean dirty = false;
57         
58         public ScriptNodeMap(Session session, IMapping<DBObject,E> mapping, ParentNode<E> rootNode) {
59                 this.session = session;
60                 this.mapping = mapping;
61                 this.rootNode = rootNode;
62                 mapping.addMappingListener(this);
63                 rootNode.addListener(this); 
64         }
65         
66         
67         
68         protected abstract void addActor(E node);
69         protected abstract void removeActor(E node);
70         protected abstract void updateActor(E node,Set<String> ids);
71         
72         public void repaint() {
73                 dirty = true;
74         }
75         
76         public void populate() {
77                 for (E node : rootNode.getNodes()) {
78                         receiveAdd(node, node.getParentRel(),true);
79                 }
80                 repaint();
81         }
82         
83         @Override
84         public E getNode(Object o) {
85                 return null;
86         }
87         
88         @SuppressWarnings("unchecked")
89         @Override
90         public Collection<Object> getRenderObjects(INode node) {
91                 return Collections.EMPTY_LIST;
92         }
93         
94         @SuppressWarnings("unchecked")
95         @Override
96         public ParentNode<E> getRootNode() {
97                 return (ParentNode<E>)rootNode;
98         }
99         
100         
101         
102         @Override
103         public boolean isChangeTracking() {
104                 return changeTracking;
105         }
106         
107         @Override
108         public void setChangeTracking(boolean enabled) {
109                 changeTracking = enabled;
110         }
111         
112         private boolean changeTracking = true;
113         
114         protected Object syncMutex = new Object(); 
115         
116
117         private List<Pair<E,String>> added = new ArrayList<Pair<E,String>>();
118         private List<Pair<E,String>> removed = new ArrayList<Pair<E,String>>();
119         private MapSet<E, String> updated = new MapSet.Hash<E, String>();
120
121         private boolean rangeModified = false;
122         
123         @SuppressWarnings("unchecked")
124         @Override
125         public void updateRenderObjectsFor(E node) {
126                 nodes.add((E)node);
127
128         }
129         
130         @SuppressWarnings("unchecked")
131         private void receiveAdd(E node, String id, boolean db) {
132                 if (DEBUG) System.out.println("receiveAdd " + debugString(node)  + " " + id + " " + db);
133                 synchronized (syncMutex) {
134                         for (Pair<E, String> n : added) {
135                                 if (n.first.equals(node))
136                                         return;
137                         }
138                         if (changeTracking) {
139                                 mapping.rangeModified((E)node.getParent());
140                         }
141                         added.add(new Pair<E, String>(node, id));
142                         rangeModified = true;
143                 }
144                 repaint();
145         }
146         
147         @SuppressWarnings("unchecked")
148         private void receiveRemove(E node, String id, boolean db) {
149                 if (DEBUG) System.out.println("receiveRemove " + debugString(node)  + " " + id + " " + db);
150                 synchronized (syncMutex) {
151                         for (Pair<E, String> n : removed) {
152                                 if (n.first.equals(node))
153                                         return;
154                         }
155                         if (changeTracking && !db)
156                                 mapping.rangeModified((E)node.getParent());
157                         removed.add(new Pair<E, String>(node, id));
158                         rangeModified = true;
159                 }
160                 repaint();
161         }
162         
163         @SuppressWarnings("unchecked")
164         private void receiveUpdate(E node, String id, boolean db) {
165                 if (DEBUG) System.out.println("receiveUpdate " + debugString(node)  + " " + id + " " + db);
166                 synchronized (syncMutex) {
167 //          for (Pair<E, String> n : updated) {
168 //              if (n.first.equals(node))
169 //                  return;
170 //          }
171                         if (changeTracking && !db)
172                                 mapping.rangeModified(node);
173                         //updated.add(new Pair<E, String>(node, id));
174                         updated.add(node, id);
175                         rangeModified = true;
176                 }
177                 repaint();
178         }
179         
180         private boolean graphUpdates = false;
181         private Set<E> graphModified = new HashSet<E>();
182         
183         private boolean requestCommit = false;
184         private String commitMessage = null;
185         
186         @Override
187         public void commit(String message) {
188                 requestCommit = true;
189                 commitMessage = message;
190         }
191         
192         protected void doCommit() throws DatabaseException {
193                 session.syncRequest(new WriteRequest() {
194                         
195                         @Override
196                         public void perform(WriteGraph graph) throws DatabaseException {
197                                 if (DEBUG) System.out.println("Commit " + commitMessage);
198                                 if (commitMessage != null) {
199                                         Layer0Utils.addCommentMetadata(graph, commitMessage);
200                                         graph.markUndoPoint();
201                                         commitMessage = null;
202                                 }
203                                 commit(graph);
204                         }
205                         
206                 });
207         }
208         
209         protected void commit(WriteGraph graph) throws DatabaseException {
210                 synchronized(syncMutex) {
211                         if (DEBUG) System.out.println("Commit");
212                         graphUpdates = true;
213                         mapping.updateDomain(graph);
214                         graphUpdates = false;
215                         clearDeletes();
216                         if (DEBUG) System.out.println("Commit done");
217                 }
218         }
219         
220         
221         
222         @Override
223         public void domainModified() {
224                 if (graphUpdates)
225                         return;
226                 if (DEBUG)System.out.println("domainModified");
227                 // FIXME : this is called by IMapping id DB thread
228                 dirty = true;
229 //        session.asyncRequest(new ReadRequest() {
230 //            
231 //            @SuppressWarnings("unchecked")
232 //            @Override
233 //            public void run(ReadGraph graph) throws DatabaseException {
234 //                update(graph);
235 //            }
236 //        });
237                 
238         }
239         
240         protected void reset(ReadGraph graph) throws MappingException {
241                 if (DEBUG) System.out.println("Reset");
242                 synchronized (syncMutex) {
243                         graphUpdates = true;
244                         mapping.getRangeModified().clear();
245                         for (DBObject o : mapping.getDomain())
246                                 mapping.domainModified(o);
247                         mapping.updateRange(graph);
248                         graphModified.clear();
249                         graphUpdates = false;
250                 }
251         }
252         
253         protected void update(ReadGraph graph) throws DatabaseException {
254                 if (DEBUG) System.out.println("Graph update start");
255                 synchronized (syncMutex) {
256                         graphUpdates = true;
257                         for (DBObject domainObject : mapping.getDomainModified()) {
258                                 E rangeObject = mapping.get(domainObject);
259                                 if (rangeObject != null)
260                                         graphModified.add(rangeObject);
261                         }
262                         mapping.updateRange(graph);
263                         graphModified.clear();
264                         syncDeletes();
265                         clearDeletes();
266                         graphUpdates = false;
267                 }
268                 
269                 
270                 //if (mapping.isRangeModified() && !runUndo) // FIXME : redo?
271                 if (mapping.isRangeModified())
272                         commit((String)null);
273                 if (DEBUG) System.out.println("Graph update done");
274         }
275         
276         @Override
277         public void rangeModified() {
278                 //System.out.println("rangeModified");
279
280         }
281         
282         public void update() throws DatabaseException{
283                 while (dirty) {
284                         dirty = false;
285                         // preRender
286                         updateCycle();
287                         // postRender
288                         if (requestCommit && !rangeModified) { // FIXME : not thread safe.
289                                 requestCommit = false;
290                                 doCommit();
291                         }
292                         session.syncRequest(new ReadRequest() { 
293                                 @SuppressWarnings("unchecked")
294                                 @Override
295                                 public void run(ReadGraph graph) throws DatabaseException {
296                                         update(graph);
297                                 }
298                         });
299                 }
300         }
301         
302
303         
304         // Reusable containers for data synchronisation
305         List<Pair<E, String>> rem = new ArrayList<Pair<E,String>>();  // Removed objects
306         List<Pair<E, String>> add = new ArrayList<Pair<E,String>>();  // Added objects
307         MapSet<E, String> mod = new MapSet.Hash<E, String>();         // Modified objects
308         Set<E> propagation = new HashSet<E>();                        // Objects with propagated changes 
309         Stack<E> stack = new Stack<E>();                              // Stack for handling propagation
310         Set<E> delete = Collections.synchronizedSet(new HashSet<E>()); // Objects to be completely deleted
311         Set<E> deleteUC = new HashSet<E>();
312         
313         
314         
315         /**
316          * When objects are removed (either from Java or Graph), after remove processing the Java objects remain in mapping cache.
317          * This causes problems with Undo and Redo, whcih the end up re-using the removed objects from mapping cache.
318          * 
319          * This code here synchronizes removed and added objects to collect deletable objects. (a deletable object is one which is removed but not added).  
320          * 
321          */
322         protected void syncDeletes() {
323                 deleteUC.clear();
324                 for (Pair<E, String> n : removed) {
325                         deleteUC.add(n.first);   
326                 }
327                 for (Pair<E, String> n : added) {
328                         deleteUC.remove(n.first);   
329                 } 
330                 if (DEBUG && deleteUC.size() > 0) {
331                         System.out.println("Delete sync");
332                         for (E n : delete) {
333                                 System.out.println(debugString(n));
334                         }
335                 }
336                 delete.addAll(deleteUC);
337                 deleteUC.clear();
338         }
339         
340         /**
341          * Clears deletable objects from mapping cache.
342          */
343         protected void clearDeletes() {
344                 if (DEBUG && delete.size() > 0) System.out.println("Delete");
345                 for (E n : delete) {
346                         if (DEBUG) System.out.println(debugString(n));
347                         mapping.getRange().remove(n);
348                 }
349                 delete.clear();
350         }
351         
352         protected String debugString(E n) {
353                 return n + "@" + Integer.toHexString(n.hashCode());
354         }
355         
356         @SuppressWarnings("unchecked")
357         protected void updateCycle() {
358                 rem.clear();
359                 add.clear();
360                 mod.clear();
361                 propagation.clear();
362                 
363                 
364                 synchronized (syncMutex) {
365                     // Check for overlapping additions and deletions, prevent deleting objects that are also added.
366             Deque<E> stack = new ArrayDeque<E>();
367             for (Pair<E, String> n : added) {
368                 stack.add(n.first);
369             }
370             while (!stack.isEmpty()) {
371                 E n = stack.pop();
372                 for (int i = removed.size()-1; i >= 0; i--) {
373                     if (removed.get(i).first == n) {
374                         removed.remove(i);
375                         break;
376                     }
377                 }
378                 if (n instanceof ParentNode) {
379                     ParentNode<INode> pn = (ParentNode<INode>)n;
380                     for (INode cn : pn.getNodes()) {
381                         stack.push((E)cn);
382                     }
383                 }
384             }
385             
386                         rem.addAll(removed);
387                         add.addAll(added);
388                         for (E e : updated.getKeys()) {
389                                 for (String s : updated.getValues(e)) {
390                                         mod.add(e, s);
391                                 }
392                         }
393                         syncDeletes();
394                         removed.clear();
395                         added.clear();
396                         updated.clear();
397                 }
398                 
399                 for (Pair<E, String> n : rem) {
400                         stopListening(n.first);
401                         removeActor(n.first);
402                 }
403                 
404                 for (Pair<E, String> n : add) {
405                         addActor(n.first);
406                         listen(n.first);
407                 }
408                 
409                 for (E e : mod.getKeys()) {
410                         Set<String> ids = mod.getValues(e);
411                         if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {
412                                 if (!propagation.contains(e))
413                                         propagation.add(e);
414                         }
415                 }
416
417                 if (propagation.size() > 0) {
418                         stack.clear();
419                         stack.addAll(propagation);
420                         propagation.clear();
421                         while (!stack.isEmpty()) {
422                                 E node = stack.pop();
423                                 if (propagation.contains(node))
424                                         continue;
425                                 propagation.add(node);
426                                 for (NodeListener l : node.getListeners()) {
427                                         if (l == this) {
428                                                 //changeTracking = false;
429                                                 //l.propertyChanged(node, G3D.URIs.hasPosition);
430                                                 //changeTracking = true;
431                                         } else {
432                                                 l.propertyChanged(node, G3D.URIs.hasWorldPosition);
433                                         }
434                                 }
435                                 if (node instanceof ParentNode) {
436                                         stack.addAll(((ParentNode<E>)node).getNodes());
437                                 }
438                         }
439                 }
440                 
441 //      synchronized (syncMutex) {
442 //          rem.addAll(removed);
443 //          add.addAll(added);
444 //          //mod.addAll(updated);
445 //          for (E e : updated.getKeys()) {
446 //              for (String s : updated.getValues(e))
447 //                  mod.add(e, s);
448 //          }
449 //          
450 //          removed.clear();
451 //          added.clear();
452 //          updated.clear();
453 //      }
454                 
455                 for (E e : mod.getKeys()) {
456                         Set<String> ids = mod.getValues(e);
457                         updateActor(e,ids);
458                 }
459                 
460                 
461                 for (Pair<E, String> n : rem) {
462                         for (NodeListener l : nodeListeners)
463                                 l.nodeRemoved(null, n.first, n.second);
464                 }
465                 for (Pair<E, String> n : add) {
466                         for (NodeListener l : nodeListeners)
467                                 l.nodeAdded(n.first.getParent(), n.first, n.second);
468                 }
469 //      for (Pair<E, String> n : mod) {
470 //          for (NodeListener l : nodeListeners)
471 //              l.propertyChanged(n.first, n.second);
472 //      }
473                 for (E e : mod.getKeys()) {
474                         for (NodeListener l : nodeListeners)
475                                 for (String s : mod.getValues(e))
476                                         l.propertyChanged(e, s);
477                 }
478                 
479                 synchronized (syncMutex) {
480                         if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)
481                                 rangeModified = false;
482                 }
483         }
484         
485         @SuppressWarnings("unchecked")
486         private void listen(INode node) {
487                 node.addListener(this);
488                 if (node instanceof ParentNode<?>) {
489                         ParentNode<INode> parentNode = (ParentNode<INode>)node;
490                         for (INode n : parentNode.getNodes())
491                                 listen(n);
492                 }
493         }
494         
495         private void stopListening(INode node) {
496                 node.removeListener(this);
497                 if (node instanceof ParentNode<?>) {
498                         @SuppressWarnings("unchecked")
499                         ParentNode<INode> parentNode = (ParentNode<INode>)node;
500                         for (INode n : parentNode.getNodes())
501                                 stopListening(n);
502                 }
503         }
504         
505         @SuppressWarnings("unchecked")
506         @Override
507         public void propertyChanged(INode node, String id) {
508                 //receiveUpdate((E)node, id, graphUpdates);
509                 receiveUpdate((E)node, id, graphModified.contains(node));
510                 
511         }
512         
513         @SuppressWarnings("unchecked")
514         @Override
515         public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,
516                         String rel) {
517                 if (DEBUG) System.out.println("Node added " + child + " parent " + node);
518                 //receiveAdd((E)child, rel ,graphUpdates);
519                 receiveAdd((E)child, rel ,graphModified.contains(node));
520                 
521         }
522         
523         @SuppressWarnings("unchecked")
524         @Override
525         public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,
526                         String rel) {
527                 if (DEBUG) System.out.println("Node removed " + child + " parent " + node);
528                 //receiveRemove((E)child, rel, graphUpdates);
529                 receiveRemove((E)child, rel, graphModified.contains(node));
530                 
531                 //FIXME : sometimes removed structural models cause ObjMap to add their children again.
532                 //        removing the listener here prevents corruption of visual model, but better fix is needed.
533                 stopListening(child);
534         }
535         
536         @Override
537         public void delete() {
538                 
539                 changeTracking = false;
540                 mapping.removeMappingListener(this);
541
542                 for (E node : nodes) {
543                         node.removeListener(this);
544                         removeActor(node);
545                         node.cleanup();
546                 }
547                 nodes.clear();
548         }
549         
550         
551         private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();
552         @Override
553         public void addListener(NodeListener listener) {
554                 nodeListeners.add(listener);
555                 
556         }
557         
558         @Override
559         public void removeListener(NodeListener listener) {
560                 nodeListeners.remove(listener);
561                 
562         }
563         
564         public IMapping<DBObject,E> getMapping() {
565                 return mapping;
566         }
567         
568         
569 }