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