Add isDisposed checking to avoid unexpected NPE
[simantics/platform.git] / bundles / org.simantics.db.layer0 / src / org / simantics / db / layer0 / variable / NodeStructureRequest.java
1 package org.simantics.db.layer0.variable;
2
3 import java.util.Collections;
4 import java.util.List;
5 import java.util.Map;
6
7 import org.simantics.databoard.util.ObjectUtils;
8 import org.simantics.db.ReadGraph;
9 import org.simantics.db.common.request.ParametrizedPrimitiveRead;
10 import org.simantics.db.common.utils.Logger;
11 import org.simantics.db.exception.DatabaseException;
12 import org.simantics.db.layer0.variable.Variables.NodeStructure;
13 import org.simantics.db.procedure.Listener;
14 import org.simantics.simulator.variable.exceptions.NodeManagerException;
15
16 import gnu.trove.map.hash.THashMap;
17
18 @SuppressWarnings("rawtypes")
19 class NodeStructureRequest extends ParametrizedPrimitiveRead<VariableNode, NodeStructure> implements VariableNodeReadRunnable {
20
21         private Listener<NodeStructure> listener = null;
22         private NodeStructure value = Variables.PENDING_NODE_STRUCTURE;
23         private boolean wasRun = false;
24
25         static class Probe implements Runnable {
26
27                 private VariableNode node;
28                 public NodeStructure result;
29
30                 public Probe(VariableNode node) {
31                         this.node = node;
32                 }
33
34                 @SuppressWarnings("unchecked")
35                 @Override
36                 public void run() {
37                         try {
38                                 result = NodeStructureRequest.get(node);
39                                 node.support.structureCache.put(node.node, result, 1000000000L);
40                         } catch (NodeManagerException e) {
41                                 e.printStackTrace();
42                         }
43                 }
44
45         }
46
47         public NodeStructureRequest(VariableNode node) {
48                 super(node);
49         }
50
51         @SuppressWarnings("unchecked")
52         @Override
53         public void register(ReadGraph graph, final Listener<NodeStructure> procedure) {
54
55                 if(procedure.isDisposed()) {
56
57                         // We are not listening
58                         NodeStructure result = (NodeStructure)parameter.support.structureCache.get(parameter.node);
59
60                         if(result != null) {
61                                 // Return cached value immediately
62                                 procedure.execute(result);
63                         } else {
64                                 NodeStructureRequest.Probe probe = new Probe(parameter);
65                                 parameter.support.manager.getRealm().asyncExec(probe);
66                                 if(probe.result != null) {
67                                         procedure.execute(probe.result);
68                                 } else {
69                                         procedure.execute(Variables.PENDING_NODE_STRUCTURE);
70                                 }
71                         }
72
73                         return;
74
75                 }
76
77                 // We need to listen
78                 listener = procedure;
79                 // Register listening
80                 parameter.support.manager.addNodeListener(parameter.node, this);
81                 synchronized(this) {
82                         if(wasRun) {
83                                 procedure.execute(value);
84                         } else {
85                                 NodeStructure result = (NodeStructure)parameter.support.structureCache.get(parameter.node);
86                                 if(result != null) {
87                                         procedure.execute(result);
88                                 } else {
89                                         procedure.execute(Variables.PENDING_NODE_STRUCTURE);
90                                 }
91                         }
92                 }
93
94         }
95
96         static class NodeListener implements VariableNodeReadRunnable {
97
98                 private VariableNode node;
99                 private NodeStructureRequest request;
100
101                 public NodeListener(VariableNode node, NodeStructureRequest request) {
102                         this.node = node;
103                         this.request = request;
104                 }
105
106                 @SuppressWarnings("unchecked")
107                 @Override
108                 public void run() {
109                         node.support.manager.addNodeListener(node.node, request);
110                 }
111
112         }
113
114         @SuppressWarnings("unchecked")
115         @Override
116         public void unregistered() {
117                 parameter.support.manager.removeNodeListener(parameter.node, this);
118                 parameter.support.structureCache.removeListening(parameter.node);
119                 listener = null;
120         }
121
122         @SuppressWarnings("unchecked")
123         public static NodeStructure get(VariableNode parameter) throws NodeManagerException {
124                 List<?> children = parameter.support.manager.getChildren(parameter.node);
125                 List<?> properties = parameter.support.manager.getProperties(parameter.node);
126                 Map<String, Object> childMap = Collections.emptyMap();
127                 Map<String, Object> propertyMap = childMap;
128                 if(!children.isEmpty()) {
129                         childMap = new THashMap<>(children.size());
130                         for(Object o : children) {
131                                 String name = parameter.support.manager.getName(o);
132                                 childMap.put(name, o);
133                         }
134                 }
135                 if(!properties.isEmpty()) {
136                         propertyMap = new THashMap<>(properties.size());
137                         for(Object o : properties) {
138                                 String name = parameter.support.manager.getName(o);
139                                 propertyMap.put(name, o);
140                         }
141                 }
142                 return new NodeStructure(childMap, propertyMap);
143         }
144
145         @SuppressWarnings("unchecked")
146         @Override
147         public synchronized void run() {
148                 try {
149                         // Cache this value with infinite cache time since we are listening
150                         NodeStructure newValue = get(parameter);
151                         if (wasRun && ObjectUtils.objectEquals(value, newValue)) {
152                                 //System.out.println("CACHE VALUE MATCH (" + newValue + ") for " + node.node);
153                                 return;
154                         }
155                         value = newValue;
156                         parameter.support.structureCache.put(parameter.node, value);
157                 } catch (Throwable e) {
158                         // Must catch everything to prevent DB client from getting stuck.
159                         Logger.defaultLogError(e);
160                         // Invoke the exception method of the listener
161                         Listener<NodeStructure> listener = this.listener;
162                         if (listener != null) listener.exception(new DatabaseException("External data access error", e));
163                         wasRun = true;
164                         return;
165                 }
166
167                 // Must always invoke an existing listener, regardless of earlier errors.
168                 Listener<NodeStructure> listener = this.listener;
169                 if (listener != null) {
170                         listener.execute(value);
171                 }
172                 wasRun = true;
173         }
174
175         @Override
176         public String toString() {
177                 return "NodeStructureRequest.run @ " + System.identityHashCode(this);
178         }
179
180 }