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