1 package org.simantics.g3d.scl;
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;
11 import java.util.Stack;
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;
32 * NodeMap implementation used with SCL scripts.
34 * In practice, there are no visible objects to synchronize.
37 * This implementation is Copy-paste of AbstractVTKNodeMap with VTK references removed.
38 * There may be more sensible way to do this.
45 public abstract class ScriptNodeMap<DBObject,E extends INode> implements NodeMap<DBObject,Object,E>, IMappingListener, NodeListener {
47 private static final boolean DEBUG = false;
49 protected Session session;
50 protected IMapping<DBObject,INode> mapping;
52 protected ParentNode<E> rootNode;
54 protected Set<E> nodes = new HashSet<E>();
56 private boolean dirty = false;
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);
68 protected abstract void addActor(E node);
69 protected abstract void removeActor(E node);
70 protected abstract void updateActor(E node,Set<String> ids);
72 public void repaint() {
76 public void populate() {
77 for (E node : rootNode.getNodes()) {
78 receiveAdd(node, node.getParentRel(),true);
84 public E getNode(Object o) {
88 @SuppressWarnings("unchecked")
90 public Collection<Object> getRenderObjects(INode node) {
91 return Collections.EMPTY_LIST;
95 public ParentNode<E> getRootNode() {
96 return (ParentNode<E>)rootNode;
100 public boolean isChangeTracking() {
101 return changeTracking;
105 public void setChangeTracking(boolean enabled) {
106 changeTracking = enabled;
109 private boolean changeTracking = true;
111 protected Object syncMutex = new Object();
114 private List<Pair<E,String>> added = new ArrayList<Pair<E,String>>();
115 private List<Pair<E,String>> removed = new ArrayList<Pair<E,String>>();
116 private MapSet<E, String> updated = new MapSet.Hash<E, String>();
118 private boolean rangeModified = false;
121 public void updateRenderObjectsFor(E node) {
125 @SuppressWarnings("unchecked")
126 private void receiveAdd(E node, String id, boolean db) {
127 if (DEBUG) System.out.println("receiveAdd " + debugString(node) + " " + id + " " + db);
128 synchronized (syncMutex) {
129 for (Pair<E, String> n : added) {
130 if (n.first.equals(node))
133 if (changeTracking) {
134 mapping.rangeModified((E)node.getParent());
136 added.add(new Pair<E, String>(node, id));
137 rangeModified = true;
142 @SuppressWarnings("unchecked")
143 private void receiveRemove(E node, String id, boolean db) {
144 if (DEBUG) System.out.println("receiveRemove " + debugString(node) + " " + id + " " + db);
145 synchronized (syncMutex) {
146 for (Pair<E, String> n : removed) {
147 if (n.first.equals(node))
150 if (changeTracking && !db)
151 mapping.rangeModified((E)node.getParent());
152 removed.add(new Pair<E, String>(node, id));
153 rangeModified = true;
158 private void receiveUpdate(E node, String id, boolean db) {
159 if (DEBUG) System.out.println("receiveUpdate " + debugString(node) + " " + id + " " + db);
160 synchronized (syncMutex) {
161 // for (Pair<E, String> n : updated) {
162 // if (n.first.equals(node))
165 if (changeTracking && !db)
166 mapping.rangeModified(node);
167 //updated.add(new Pair<E, String>(node, id));
168 updated.add(node, id);
169 rangeModified = true;
174 private boolean graphUpdates = false;
175 private Set<E> graphModified = new HashSet<E>();
177 private boolean requestCommit = false;
178 private String commitMessage = null;
181 public void commit(String message) {
182 requestCommit = true;
183 commitMessage = message;
186 protected void doCommit() throws DatabaseException {
187 session.syncRequest(new WriteRequest() {
190 public void perform(WriteGraph graph) throws DatabaseException {
191 if (DEBUG) System.out.println("Commit " + commitMessage);
192 if (commitMessage != null) {
193 Layer0Utils.addCommentMetadata(graph, commitMessage);
194 graph.markUndoPoint();
195 commitMessage = null;
203 protected void commit(WriteGraph graph) throws DatabaseException {
204 synchronized(syncMutex) {
205 if (DEBUG) System.out.println("Commit");
207 mapping.updateDomain(graph);
208 graphUpdates = false;
210 if (DEBUG) System.out.println("Commit done");
217 public void domainModified() {
220 if (DEBUG)System.out.println("domainModified");
221 // FIXME : this is called by IMapping id DB thread
223 // session.asyncRequest(new ReadRequest() {
225 // @SuppressWarnings("unchecked")
227 // public void run(ReadGraph graph) throws DatabaseException {
234 protected void reset(ReadGraph graph) throws MappingException {
235 if (DEBUG) System.out.println("Reset");
236 synchronized (syncMutex) {
238 mapping.getRangeModified().clear();
239 for (DBObject o : mapping.getDomain())
240 mapping.domainModified(o);
241 mapping.updateRange(graph);
242 graphModified.clear();
243 graphUpdates = false;
247 protected void update(ReadGraph graph) throws DatabaseException {
248 if (DEBUG) System.out.println("Graph update start");
249 synchronized (syncMutex) {
251 for (DBObject domainObject : mapping.getDomainModified()) {
252 @SuppressWarnings("unchecked")
253 E rangeObject = (E) mapping.get(domainObject);
254 if (rangeObject != null)
255 graphModified.add(rangeObject);
257 mapping.updateRange(graph);
258 graphModified.clear();
261 graphUpdates = false;
265 //if (mapping.isRangeModified() && !runUndo) // FIXME : redo?
266 if (mapping.isRangeModified())
267 commit((String)null);
268 if (DEBUG) System.out.println("Graph update done");
272 public void rangeModified() {
273 //System.out.println("rangeModified");
277 public void update() throws DatabaseException{
283 if (requestCommit && !rangeModified) { // FIXME : not thread safe.
284 requestCommit = false;
287 session.syncRequest(new ReadRequest() {
289 public void run(ReadGraph graph) throws DatabaseException {
298 // Reusable containers for data synchronisation
299 List<Pair<E, String>> rem = new ArrayList<Pair<E,String>>(); // Removed objects
300 List<Pair<E, String>> add = new ArrayList<Pair<E,String>>(); // Added objects
301 MapSet<E, String> mod = new MapSet.Hash<E, String>(); // Modified objects
302 Set<E> propagation = new HashSet<E>(); // Objects with propagated changes
303 Stack<E> stack = new Stack<E>(); // Stack for handling propagation
304 Set<E> delete = Collections.synchronizedSet(new HashSet<E>()); // Objects to be completely deleted
305 Set<E> deleteUC = new HashSet<E>();
310 * When objects are removed (either from Java or Graph), after remove processing the Java objects remain in mapping cache.
311 * This causes problems with Undo and Redo, whcih the end up re-using the removed objects from mapping cache.
313 * This code here synchronizes removed and added objects to collect deletable objects. (a deletable object is one which is removed but not added).
316 @SuppressWarnings("unused")
317 protected void syncDeletes() {
319 for (Pair<E, String> n : removed) {
320 deleteUC.add(n.first);
322 for (Pair<E, String> n : added) {
323 deleteUC.remove(n.first);
325 if (DEBUG && deleteUC.size() > 0) {
326 System.out.println("Delete sync");
328 System.out.println(debugString(n));
331 delete.addAll(deleteUC);
336 * Clears deletable objects from mapping cache.
338 @SuppressWarnings("unused")
339 protected void clearDeletes() {
340 if (DEBUG && delete.size() > 0) System.out.println("Delete");
342 if (DEBUG) System.out.println(debugString(n));
343 mapping.getRange().remove(n);
348 protected String debugString(E n) {
349 return n + "@" + Integer.toHexString(n.hashCode());
352 @SuppressWarnings("unchecked")
353 protected void updateCycle() {
360 synchronized (syncMutex) {
361 // Check for overlapping additions and deletions, prevent deleting objects that are also added.
362 Deque<E> stack = new ArrayDeque<E>();
363 for (Pair<E, String> n : added) {
366 while (!stack.isEmpty()) {
368 for (int i = removed.size()-1; i >= 0; i--) {
369 if (removed.get(i).first == n) {
374 if (n instanceof ParentNode) {
375 ParentNode<INode> pn = (ParentNode<INode>)n;
376 for (INode cn : pn.getNodes()) {
384 for (E e : updated.getKeys()) {
385 for (String s : updated.getValues(e)) {
395 for (Pair<E, String> n : rem) {
396 stopListening(n.first);
397 removeActor(n.first);
400 for (Pair<E, String> n : add) {
405 for (E e : mod.getKeys()) {
406 Set<String> ids = mod.getValues(e);
407 if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {
408 if (!propagation.contains(e))
413 if (propagation.size() > 0) {
415 stack.addAll(propagation);
417 while (!stack.isEmpty()) {
418 E node = stack.pop();
419 if (propagation.contains(node))
421 propagation.add(node);
422 for (NodeListener l : node.getListeners()) {
424 //changeTracking = false;
425 //l.propertyChanged(node, G3D.URIs.hasPosition);
426 //changeTracking = true;
428 l.propertyChanged(node, G3D.URIs.hasWorldPosition);
431 if (node instanceof ParentNode) {
432 stack.addAll(((ParentNode<E>)node).getNodes());
437 // synchronized (syncMutex) {
438 // rem.addAll(removed);
439 // add.addAll(added);
440 // //mod.addAll(updated);
441 // for (E e : updated.getKeys()) {
442 // for (String s : updated.getValues(e))
451 for (E e : mod.getKeys()) {
452 Set<String> ids = mod.getValues(e);
457 for (Pair<E, String> n : rem) {
458 for (NodeListener l : nodeListeners)
459 l.nodeRemoved(null, n.first, n.second);
461 for (Pair<E, String> n : add) {
462 for (NodeListener l : nodeListeners)
463 l.nodeAdded(n.first.getParent(), n.first, n.second);
465 // for (Pair<E, String> n : mod) {
466 // for (NodeListener l : nodeListeners)
467 // l.propertyChanged(n.first, n.second);
469 for (E e : mod.getKeys()) {
470 for (NodeListener l : nodeListeners)
471 for (String s : mod.getValues(e))
472 l.propertyChanged(e, s);
475 synchronized (syncMutex) {
476 if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)
477 rangeModified = false;
481 @SuppressWarnings("unchecked")
482 private void listen(INode node) {
483 node.addListener(this);
484 if (node instanceof ParentNode<?>) {
485 ParentNode<INode> parentNode = (ParentNode<INode>)node;
486 for (INode n : parentNode.getNodes())
491 private void stopListening(INode node) {
492 node.removeListener(this);
493 if (node instanceof ParentNode<?>) {
494 @SuppressWarnings("unchecked")
495 ParentNode<INode> parentNode = (ParentNode<INode>)node;
496 for (INode n : parentNode.getNodes())
501 @SuppressWarnings("unchecked")
503 public void propertyChanged(INode node, String id) {
504 //receiveUpdate((E)node, id, graphUpdates);
505 receiveUpdate((E)node, id, graphModified.contains(node));
509 @SuppressWarnings("unchecked")
511 public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,
513 if (DEBUG) System.out.println("Node added " + child + " parent " + node);
514 //receiveAdd((E)child, rel ,graphUpdates);
515 receiveAdd((E)child, rel ,graphModified.contains(node));
519 @SuppressWarnings("unchecked")
521 public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,
523 if (DEBUG) System.out.println("Node removed " + child + " parent " + node);
524 //receiveRemove((E)child, rel, graphUpdates);
525 receiveRemove((E)child, rel, graphModified.contains(node));
527 //FIXME : sometimes removed structural models cause ObjMap to add their children again.
528 // removing the listener here prevents corruption of visual model, but better fix is needed.
529 stopListening(child);
533 public void delete() {
535 changeTracking = false;
536 mapping.removeMappingListener(this);
538 for (E node : nodes) {
539 node.removeListener(this);
547 private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();
549 public void addListener(NodeListener listener) {
550 nodeListeners.add(listener);
555 public void removeListener(NodeListener listener) {
556 nodeListeners.remove(listener);
561 public IMapping<DBObject,INode> getMapping() {