+/*******************************************************************************\r
+ * Copyright (c) 2007, 2015 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ * Semantum Oy - added getHintsUnsafe\r
+ *******************************************************************************/\r
+/*\r
+ *\r
+ * @author Toni Kalajainen\r
+ */\r
+package org.simantics.utils.datastructures.hints;\r
+\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.Map.Entry;\r
+\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.prioritystack.IPriorityStack;\r
+import org.simantics.utils.datastructures.prioritystack.IPriorityStackListener;\r
+import org.simantics.utils.datastructures.prioritystack.PriorityStack;\r
+import org.simantics.utils.threads.Executable;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+\r
+\r
+public class HintStack extends AbstractHintObservable implements IHintStack {\r
+\r
+ /** Stack of hint contexts */\r
+ PriorityStack<IHintContext> stack = \r
+ new PriorityStack<IHintContext>(IHintContext.class); \r
+ \r
+ IPriorityStackListener<IHintContext> stackListener =\r
+ new IPriorityStackListener<IHintContext>() {\r
+ @Override\r
+ public void itemAdded(IPriorityStack<IHintContext> sender, IHintContext item) {\r
+ item.addHintListener(ctxListener);\r
+ ArrayList<Executable> executables = new ArrayList<Executable>();\r
+ synchronized(HintStack.this.stack) {\r
+ if (!hasListeners()) return;\r
+ Map<Key, Object> hints = item.getHints();\r
+ if (hints.size()==0) return;\r
+ \r
+ Set<Key> keys = new HashSet<Key>(hints.keySet());\r
+ Map<Key, Object> oldValues = new HashMap<Key, Object>();\r
+ // Check if there is a change to the stack \r
+ IHintContext list[] = stack.toArray();\r
+\r
+ int index = stack.indexOf(item);\r
+ \r
+ // Remove all keys that are overridden by higher priority keys\r
+ for (int i=index+1; i<list.length; i++)\r
+ keys.removeAll(list[i].getHints().keySet());\r
+ \r
+ // Iterate all lower layers, and see if any key collides\r
+ for (int i=index-1; i>=0; i--)\r
+ {\r
+ Map<Key, Object> lowerLevelHints = list[i].getHints();\r
+ lowerLevelHints.keySet().retainAll(keys);\r
+ keys.removeAll(lowerLevelHints.keySet());\r
+ oldValues.putAll(lowerLevelHints);\r
+ }\r
+ \r
+ // Send events for all new hints\r
+ for (Key key : keys)\r
+ {\r
+ Object newValue = hints.get(key);\r
+ addFireKeyChangedExecutables(executables, HintStack.this, key, null, newValue);\r
+ }\r
+ \r
+ // Send events for all hints that were overridden\r
+ for (Entry<Key, Object> hint : oldValues.entrySet())\r
+ {\r
+ Key key = hint.getKey();\r
+ Object oldValue = hint.getValue();\r
+ Object newValue = hints.get(key);\r
+ addFireKeyChangedExecutables(executables, HintStack.this, key, oldValue, newValue);\r
+ }\r
+ }\r
+ ThreadUtils.multiSyncExec(executables);\r
+ }\r
+ @Override\r
+ public void itemRemoved(IPriorityStack<IHintContext> sender, IHintContext item) {\r
+ item.removeHintListener(ctxListener);\r
+ ArrayList<Executable> executables = new ArrayList<Executable>();\r
+ synchronized(HintStack.this.stack) {\r
+ if (!hasListeners()) return;\r
+ Map<Key, Object> hints = item.getHints();\r
+ if (hints.size()==0) return;\r
+ \r
+ Set<Key> keys = new HashSet<Key>(hints.keySet());\r
+ Map<Key, Object> overriddenValues = new HashMap<Key, Object>();\r
+ // Check if there is a change to the stack \r
+ IHintContext list[] = stack.toArray();\r
+\r
+ int index = stack.indexOf(item);\r
+ \r
+ // Remove all keys that are overridden by higher priority keys\r
+ for (int i=index+1; i<list.length; i++)\r
+ keys.removeAll(list[i].getHints().keySet());\r
+ \r
+ // Iterate all lower layers, and see if any key collides\r
+ for (int i=index-1; i>=0; i--)\r
+ {\r
+ Map<Key, Object> lowerLevelHints = list[i].getHints();\r
+ lowerLevelHints.keySet().retainAll(keys);\r
+ keys.removeAll(lowerLevelHints.keySet());\r
+ overriddenValues.putAll(lowerLevelHints);\r
+ }\r
+ \r
+ // Send events for all values were removed and were never overridden\r
+ for (Key key : keys)\r
+ {\r
+ Object oldValue = hints.get(key);\r
+ addFireKeyRemovedExecutables(executables, HintStack.this, key, oldValue);\r
+ }\r
+ \r
+ // Send events. overridden hints have become effective\r
+ for (Entry<Key, Object> hint : overriddenValues.entrySet())\r
+ {\r
+ Key key = hint.getKey();\r
+ Object newValue = hint.getValue();\r
+ Object oldValue = hints.get(key);\r
+ addFireKeyChangedExecutables(executables, HintStack.this, key, oldValue, newValue);\r
+ } \r
+ }\r
+ ThreadUtils.multiSyncExec(executables);\r
+ }\r
+ };\r
+ \r
+ IHintListener ctxListener = new IHintListener() {\r
+ @Override\r
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+ Runnable notifications;\r
+ synchronized(HintStack.this.stack) {\r
+ IHintContext list[] = stack.toArray();\r
+\r
+ int index = stack.indexOf((IHintContext)sender);\r
+ if (index<0) return;\r
+ \r
+ // Check whether the key is overridden\r
+ for (int i=index+1; i<list.length; i++)\r
+ if (list[i].getHint(key)!=null) return;\r
+ \r
+ // Check if this hint overrides another hint\r
+ if (oldValue==null) {\r
+ for (int i=index-1; i>=0; i--)\r
+ {\r
+ oldValue = list[i].getHint(key);\r
+ if (oldValue!=null) break;\r
+ }\r
+ }\r
+ notifications = createFireKeyChangedRunnable(HintStack.this, key, oldValue, newValue); \r
+ }\r
+ notifications.run();\r
+ }\r
+ @Override\r
+ public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {\r
+ Runnable notification;\r
+ synchronized(HintStack.this.stack) {\r
+ IHintContext list[] = stack.toArray();\r
+\r
+ int index = stack.indexOf((IHintContext)sender);\r
+ if (index<0) return;\r
+ \r
+ // Check whether the key was overridden\r
+ for (int i=index+1; i<list.length; i++)\r
+ if (list[i].getHint(key)!=null) return;\r
+ \r
+ // Check if this hint overrides another hint\r
+ Object newValue = null;\r
+ for (int i=index-1; i>=0; i--)\r
+ {\r
+ newValue = list[i].getHint(key);\r
+ if (newValue!=null) break;\r
+ }\r
+ if (newValue!=null)\r
+ notification = createFireKeyChangedRunnable(HintStack.this, key, oldValue, newValue); \r
+ else\r
+ notification = createFireKeyRemovedRunnable(HintStack.this, key, oldValue); \r
+ }\r
+ notification.run();\r
+ }\r
+ };\r
+ \r
+ public HintStack() {\r
+ stack.addStackListener(stackListener);\r
+ }\r
+ \r
+ @Override\r
+ public void addHintContext(IHintContext hints, int priority) {\r
+ stack.add(hints, priority);\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <E> E getHint(Key key) {\r
+ IHintContext list [] = stack.toArray();\r
+ for (int i=list.length-1; i>=0; i--)\r
+ {\r
+ IHintContext ctx = list[i];\r
+ Object value = ctx.getHint(key);\r
+ if (value!=null) return (E) value;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public boolean containsHint(Key key) {\r
+ IHintContext list [] = stack.toArray();\r
+ for (int i=list.length-1; i>=0; i--)\r
+ {\r
+ IHintContext ctx = list[i];\r
+ if (ctx.containsHint(key))\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public boolean removeHintContext(IHintContext hints) {\r
+ return stack.remove(hints);\r
+ }\r
+\r
+ @Override\r
+ public synchronized Map<Key, Object> getHints() {\r
+ Map<Key, Object> result = new HashMap<Key, Object>();\r
+ for (IHintContext ctx : stack.toArray())\r
+ result.putAll(ctx.getHints());\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public synchronized Map<Key, Object> getHintsUnsafe() {\r
+ return getHints();\r
+ }\r
+\r
+ @Override\r
+ public <E extends Key> Map<E, Object> getHintsOfClass(Class<E> clazz) {\r
+ Map<E, Object> result = new HashMap<E, Object>();\r
+ for (IHintContext ctx : stack.toArray())\r
+ result.putAll(ctx.getHintsOfClass(clazz));\r
+ return result;\r
+ }\r
+ \r
+ /**\r
+ * Returns a hint context whose read operations originate from the stack, and\r
+ * write operations are performed on a local stack\r
+ * \r
+ * @param ctx the hint context to write into\r
+ * @return write-localized hint context based on this hint stack\r
+ */\r
+ public IHintContext createStackRead(final IHintContext ctx)\r
+ {\r
+ return new IHintContext() {\r
+ @Override\r
+ public void clearWithoutNotification() {\r
+ ctx.clearWithoutNotification();\r
+ }\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <E> E removeHint(Key key) {\r
+ return (E) ctx.removeHint(key);\r
+ }\r
+ @Override\r
+ public void setHint(Key key, Object value) {\r
+ ctx.setHint(key, value);\r
+ }\r
+ @Override\r
+ public void addHintListener(IHintListener listener) {\r
+ HintStack.this.addHintListener(listener);\r
+ }\r
+ @Override\r
+ public void addHintListener(IThreadWorkQueue threadAccess, IHintListener listener) {\r
+ HintStack.this.addHintListener(threadAccess, listener);\r
+ }\r
+ @Override\r
+ public void addKeyHintListener(Key key, IHintListener listener) {\r
+ HintStack.this.addKeyHintListener(key, listener);\r
+ }\r
+ @Override\r
+ public void addKeyHintListener(IThreadWorkQueue threadAccess, Key key, IHintListener listener) {\r
+ HintStack.this.addKeyHintListener(threadAccess, key, listener);\r
+ }\r
+ @Override\r
+ public boolean containsHint(Key key) {\r
+ return HintStack.this.containsHint(key);\r
+ }\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <E> E getHint(Key key) {\r
+ return (E) HintStack.this.getHint(key);\r
+ }\r
+ @Override\r
+ public Map<Key, Object> getHints() {\r
+ return HintStack.this.getHints();\r
+ }\r
+ @Override\r
+ public Map<Key, Object> getHintsUnsafe() {\r
+ return HintStack.this.getHintsUnsafe();\r
+ }\r
+ @Override\r
+ public void removeHintListener(IHintListener listener) {\r
+ HintStack.this.removeHintListener(listener);\r
+ }\r
+ @Override\r
+ public void removeHintListener(IThreadWorkQueue threadAccess, IHintListener listener) {\r
+ HintStack.this.removeHintListener(threadAccess, listener);\r
+ }\r
+ @Override\r
+ public void removeKeyHintListener(Key key, IHintListener listener) {\r
+ HintStack.this.removeKeyHintListener(key, listener);\r
+ }\r
+ @Override\r
+ public void removeKeyHintListener(IThreadWorkQueue threadAccess, Key key, IHintListener listener) {\r
+ HintStack.this.removeKeyHintListener(threadAccess, key, listener);\r
+ }\r
+ @Override\r
+ public <E extends Key> Map<E, Object> getHintsOfClass(Class<E> clazz) {\r
+ return HintStack.this.getHintsOfClass(clazz);\r
+ }\r
+ @Override\r
+ public void setHints(Map<Key, Object> hints) {\r
+ ctx.setHints(hints);\r
+ }};\r
+ }\r
+ \r
+}\r