+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 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
+ *******************************************************************************/\r
+/*\r
+ *\r
+ * @author Toni Kalajainen\r
+ */\r
+package org.simantics.utils.datastructures.hints;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+import java.lang.reflect.Method;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.threads.Executable;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.SyncListenerList;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+\r
+/**\r
+ * TODO Optimize class queries.\r
+ * \r
+ * @author Toni Kalajainen\r
+ */\r
+public abstract class AbstractHintObservable implements IHintObservable {\r
+\r
+ /** Global listeners */\r
+ protected SyncListenerList<IHintListener> listeners = \r
+ new SyncListenerList<IHintListener>(IHintListener.class);\r
+ \r
+ /** Key specific listeners */\r
+ protected Map<Key, SyncListenerList<IHintListener>> keyListeners =\r
+ new THashMap<Key, SyncListenerList<IHintListener>>(2); \r
+ \r
+ private final static SyncListenerList<IHintListener> EMPTY_LIST = \r
+ new SyncListenerList<IHintListener>(IHintListener.class);\r
+ private final static Runnable NO_ACTION = new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ }};\r
+ \r
+\r
+ private synchronized SyncListenerList<IHintListener> getOrCreateKeyListeners(Key key)\r
+ {\r
+ SyncListenerList<IHintListener> result = keyListeners.get(key);\r
+ if (result==null) {\r
+ result = new SyncListenerList<IHintListener>(IHintListener.class);\r
+ keyListeners.put(key, result);\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ protected synchronized SyncListenerList<IHintListener> getListenerList(Key forKey)\r
+ {\r
+ return keyListeners.get(forKey);\r
+ }\r
+ \r
+ private static Method hintChanged = SyncListenerList.getMethod(IHintListener.class, "hintChanged");\r
+ private static Method hintRemoved = SyncListenerList.getMethod(IHintListener.class, "hintRemoved");\r
+\r
+ protected void fireKeyChanged(IHintObservable sender, Key key, Object oldValue, Object newValue)\r
+ { \r
+ createFireKeyChangedRunnable(sender, key, oldValue, newValue).run();\r
+ }\r
+ \r
+ protected synchronized Executable[] getFireKeyChangedExecutables(IHintObservable sender, Key key, Object oldValue, Object newValue)\r
+ {\r
+ if (listeners.isEmpty() && keyListeners.isEmpty()) return Executable.EMPTY_ARRAY;\r
+ List<Executable> list = new ArrayList<Executable>();\r
+ addFireKeyChangedExecutables(list, sender, key, oldValue, newValue);\r
+ return list.toArray(new Executable[list.size()]);\r
+ }\r
+ \r
+ protected synchronized Runnable createFireKeyChangedRunnable(final IHintObservable sender, final Key key, final Object oldValue, final Object newValue)\r
+ {\r
+ SyncListenerList<IHintListener> l1 = listeners;\r
+ SyncListenerList<IHintListener> l2 = keyListeners.get(key);\r
+ if (l2==null) l2 = EMPTY_LIST;\r
+ if (l1.isEmpty() && l2.isEmpty()) return NO_ACTION;\r
+ if (!l1.executableInCurrentThread() || !l2.executableInCurrentThread()) {\r
+ final Executable e[] = getFireKeyChangedExecutables(sender, key, oldValue, newValue); \r
+ return new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ ThreadUtils.multiSyncExec(e);\r
+ }};\r
+ } else {\r
+ final Map<IThreadWorkQueue, IHintListener[]> list1 = l1.getSnapshot(); \r
+ final Map<IThreadWorkQueue, IHintListener[]> list2 = l2.getSnapshot();\r
+ return new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (list1!=null)\r
+ for (IHintListener[] ll : list1.values())\r
+ for (IHintListener l : ll)\r
+ l.hintChanged(sender, key, oldValue, newValue);\r
+ if (list2!=null)\r
+ for (IHintListener[] ll : list2.values())\r
+ for (IHintListener l : ll)\r
+ l.hintChanged(sender, key, oldValue, newValue); \r
+ }\r
+ }; \r
+ }\r
+ }\r
+\r
+ protected synchronized void addFireKeyChangedExecutables(Collection<Executable> list, IHintObservable sender, Key key, Object oldValue, Object newValue)\r
+ {\r
+ // Add Key specific listeners\r
+ Object[] args = {sender, key, oldValue, newValue};\r
+ SyncListenerList<IHintListener> keyListeners = getListenerList(key);\r
+ if (keyListeners!=null) \r
+ keyListeners.addExecutables(list, hintChanged, args);\r
+ // Add generic listeners\r
+ listeners.addExecutables(list, hintChanged, args);\r
+ }\r
+ \r
+ protected void fireKeyRemoved(IHintObservable sender, Key key, Object oldValue)\r
+ { \r
+ createFireKeyRemovedRunnable(sender, key, oldValue).run();\r
+ }\r
+ \r
+ protected synchronized Executable[] getFireKeyRemovedExecutables(IHintObservable sender, Key key, Object oldValue)\r
+ {\r
+ List<Executable> list = new ArrayList<Executable>();\r
+ addFireKeyRemovedExecutables(list, sender, key, oldValue);\r
+ return list.toArray(new Executable[list.size()]);\r
+ }\r
+ \r
+ protected synchronized Runnable createFireKeyRemovedRunnable(final IHintObservable sender, final Key key, final Object oldValue)\r
+ {\r
+ SyncListenerList<IHintListener> l1 = listeners;\r
+ SyncListenerList<IHintListener> l2 = keyListeners.get(key);\r
+ if (l2==null) l2 = EMPTY_LIST;\r
+ if (l1.isEmpty() && l2.isEmpty()) return NO_ACTION;\r
+ if (!l1.executableInCurrentThread() || !l2.executableInCurrentThread()) {\r
+ final Executable e[] = getFireKeyRemovedExecutables(sender, key, oldValue); \r
+ return new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ ThreadUtils.multiSyncExec(e);\r
+ }};\r
+ } else { \r
+ final Map<IThreadWorkQueue, IHintListener[]> list1 = l1.getSnapshot(); \r
+ final Map<IThreadWorkQueue, IHintListener[]> list2 = l2.getSnapshot();\r
+ return new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ for (IHintListener[] ll : list1.values())\r
+ for (IHintListener l : ll)\r
+ l.hintRemoved(sender, key, oldValue); \r
+ for (IHintListener[] ll : list2.values())\r
+ for (IHintListener l : ll)\r
+ l.hintRemoved(sender, key, oldValue); \r
+ }\r
+ }; \r
+ }\r
+ }\r
+ \r
+ protected synchronized void addFireKeyRemovedExecutables(Collection<Executable> list, IHintObservable sender, Key key, Object oldValue)\r
+ {\r
+ // Add Key specific listeners\r
+ Object[] args = {sender, key, oldValue};\r
+ SyncListenerList<IHintListener> keyListeners = getListenerList(key);\r
+ if (keyListeners!=null) \r
+ keyListeners.addExecutables(list, hintRemoved, args);\r
+ // Add generic listeners\r
+ listeners.addExecutables(list, hintRemoved, args);\r
+ }\r
+\r
+ protected void fireKeyChanged(Key key, Object oldValue, Object newValue)\r
+ {\r
+ fireKeyChanged(this, key, oldValue, newValue);\r
+ }\r
+ \r
+ protected void fireKeyRemoved(Key key, Object oldValue)\r
+ {\r
+ fireKeyRemoved(this, key, oldValue);\r
+ }\r
+ \r
+ public synchronized boolean hasListeners() \r
+ {\r
+ return !listeners.isEmpty() || !keyListeners.isEmpty();\r
+ }\r
+\r
+ public void addHintListener(IHintListener listener) {\r
+ listeners.add(listener);\r
+ }\r
+\r
+ public void addKeyHintListener(Key key, IHintListener listener) {\r
+ getOrCreateKeyListeners(key).add(listener);\r
+ }\r
+\r
+ public void removeHintListener(IHintListener listener) {\r
+ listeners.remove(listener);\r
+ }\r
+\r
+ public synchronized void removeKeyHintListener(Key key, IHintListener listener) {\r
+ SyncListenerList<IHintListener> list = keyListeners.get(key);\r
+ if (list==null) return;\r
+ list.remove(listener);\r
+ if (list.isEmpty()) keyListeners.remove(key); \r
+ }\r
+ \r
+ /**\r
+ * Adds hint listener, which gets events for all hint changes\r
+ * \r
+ * @param listener\r
+ */\r
+ public void addHintListener(IThreadWorkQueue threadAccess, IHintListener listener)\r
+ {\r
+ listeners.add(threadAccess, listener);\r
+ }\r
+ \r
+ /**\r
+ * Removes hint listener\r
+ * \r
+ * @param listener\r
+ */\r
+ public void removeHintListener(IThreadWorkQueue threadAccess, IHintListener listener)\r
+ {\r
+ listeners.remove(threadAccess, listener);\r
+ }\r
+ \r
+ /**\r
+ * Adds hint listener for a specific key\r
+ * @param key\r
+ * @param listener\r
+ */\r
+ public void addKeyHintListener(IThreadWorkQueue threadAccess, Key key, IHintListener listener)\r
+ {\r
+ getOrCreateKeyListeners(key).add(threadAccess, listener); \r
+ }\r
+ \r
+ /**\r
+ * Removes hint listener\r
+ * @param key\r
+ * @param listener\r
+ */\r
+ public void removeKeyHintListener(IThreadWorkQueue threadAccess, Key key, IHintListener listener)\r
+ {\r
+ SyncListenerList<IHintListener> list = keyListeners.get(key);\r
+ if (list==null) return;\r
+ list.remove(threadAccess, listener);\r
+ if (list.isEmpty()) keyListeners.remove(key); \r
+ }\r
+ \r
+}\r