--- /dev/null
+package org.simantics.db.layer0.variable;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.TreeMap;\r
+\r
+import gnu.trove.map.hash.TObjectLongHashMap;\r
+\r
+/**\r
+ * @author Antti Villberg\r
+ *\r
+ * @param <Node>\r
+ * @param <Value>\r
+ * \r
+ * @since 1.23\r
+ */\r
+public class NodeCache<Node,Value> {\r
+\r
+ // Expiration time for items in this cache\r
+ private long defaultExpirationTimeInNs;\r
+\r
+ // Here we hold all nodes with finite expiration times\r
+ private TreeMap<Long,Node> expirationTimes = new TreeMap<Long,Node>();\r
+ // Finite expiration times for nodes\r
+ private TObjectLongHashMap<Node> exp = new TObjectLongHashMap<Node>(10, 0.5f, -1L);\r
+ \r
+ // All node values\r
+ private Map<Node,Value> map = new HashMap<Node,Value>();\r
+\r
+ private boolean disposed;\r
+\r
+ public NodeCache() {\r
+ this(1_000_000_000L);\r
+ }\r
+\r
+ public NodeCache(long defaultExpirationTimeInNs) {\r
+ this.defaultExpirationTimeInNs = defaultExpirationTimeInNs;\r
+ }\r
+\r
+ public synchronized Value get(Node node) {\r
+ return map.get(node); \r
+ }\r
+ \r
+ public synchronized void clearExpired() {\r
+ \r
+ long now = System.nanoTime();\r
+ while(!expirationTimes.isEmpty()) {\r
+ Long first = expirationTimes.firstKey();\r
+ if(first < now) {\r
+ Node node = expirationTimes.remove(first);\r
+ exp.remove(node);\r
+ map.remove(node);\r
+ } else {\r
+ return;\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ private long scheduleExpiration(Node node, long expiration) {\r
+ while(expirationTimes.containsKey(expiration)) expiration++;\r
+ expirationTimes.put(expiration, node);\r
+ exp.put(node, expiration);\r
+ return expiration;\r
+ }\r
+ \r
+ private void refreshExpiration(Node node, long newExpiration, boolean existing) {\r
+ \r
+ long current = exp.get(node);\r
+ if(current == -1) {\r
+ if(existing) {\r
+ // We have infinite expiration => do nothing\r
+ } else {\r
+ // This is a new value\r
+ if(newExpiration == 0) {\r
+ // We require infinite expiration => do nothing\r
+ } else {\r
+ scheduleExpiration(node, newExpiration);\r
+ }\r
+ }\r
+ return;\r
+ }\r
+ \r
+ // This node is already under expiration tracking\r
+ if(newExpiration == 0) {\r
+ // We now want infinite expiration => remove expiration time info\r
+ expirationTimes.remove(current);\r
+ exp.remove(node);\r
+ } else {\r
+ if(newExpiration > current) {\r
+ // Update expiration time\r
+ expirationTimes.remove(current);\r
+ scheduleExpiration(node, newExpiration);\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ public synchronized void put(Node node, Value value) {\r
+ if (disposed)\r
+ return;\r
+ Value existing = map.put(node, value);\r
+ refreshExpiration(node, 0, existing != null);\r
+ }\r
+\r
+ public synchronized void put(Node node, Value value, long expiration) {\r
+ if (disposed)\r
+ return;\r
+ Value existing = map.put(node, value);\r
+ refreshExpiration(node, System.nanoTime() + expiration, existing != null);\r
+ }\r
+\r
+ public synchronized void removeListening(Node node) {\r
+ if (disposed)\r
+ return;\r
+ scheduleExpiration(node, System.nanoTime() + defaultExpirationTimeInNs);\r
+ }\r
+\r
+ public synchronized void dispose() {\r
+ disposed = true;\r
+ expirationTimes.clear();\r
+ exp.clear();\r
+ map.clear();\r
+ }\r
+ \r
+}\r