001 // GraphLab Project: http://graphlab.sharif.edu 002 // Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology 003 // Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/ 004 package graphlab.platform.core; 005 006 import graphlab.platform.StaticUtils; 007 008 import java.util.HashMap; 009 import java.util.HashSet; 010 import java.util.Vector; 011 012 /** 013 * <b>BlackBoard is just like a blackboard. Anyone can write on anywhere of it, Any one can read anywhere of it, 014 * and anyone can look for changes in anewhere of it. It's the environment of anyone, just like the air.</b> 015 * <br/><br/> 016 * Structurally BlackBoard is a listanable hashmap. So it makes a usefull environment in plugable applications. 017 * here you don't have extension points. all you have is some data which stores in BlackBoard as a hash map, 018 * and some events which occurs on changing of data. (normally most places in BlackBoard are event place holders) 019 * <br/><br/> 020 * BlackBoard is where plugins (and almost everything in the GraphLab ui) connect together. It is the only common thing between all plugins. So if you want to share some data between your plugins (or perhaps inside a plugin) you can safely use blackboard: 021 * <p/> 022 * //here you save your data 023 * <p/> 024 * blackboard.setData("mydata", data); 025 * <p/> 026 * //here you load it 027 * <p/> 028 * data = blackboard.getData("mydata"); 029 * <p/> 030 * Also every place in blackboard is listenable, this means that you can listen to change of any data in the blackboard. suppose you want to listen to the change of "mydata" 031 * <p/> 032 * <pre> 033 * blackboard.addListener("mydata", new Listener() { 034 * public void keyChanged(String key, Object value) { 035 * System.out.println(String.valueOf(value)); 036 * } 037 * }); 038 * </pre> 039 * <p/> 040 * so every time that some one set "mydata" on blackboard keyChanged will be called. 041 <br> 042 * <br/> 043 * The difference between a NotifiableAttributeSet and a BlackBoard is that, NotifiableAttributeSet is designed 044 * for a small set of attributes, so for example getAttributeListeners() will return all listeners of all attributes, 045 * but BlackBoard is for a bigger set of attributes, and there you can give listeners for just one key at a time. 046 * 047 * @author rouzbeh Ebrahimi some minor revisions, removing getEvent, ... 048 * @author azin azadi 049 * 050 */ 051 public class BlackBoard { 052 private HashMap<String, Object> data = new HashMap<String, Object>(); 053 private HashMap<String, HashSet<Listener>> listeners = new HashMap<String, HashSet<Listener>>(); 054 private HashMap<String, Vector<Couple<Boolean, Listener>>> addRemoveAfterFiring = new HashMap<String, Vector<Couple<Boolean, Listener>>>(); 055 private HashMap<String, Integer> firingNames = new HashMap<String, Integer>(); 056 057 /** 058 * @param eventName 059 * @param value 060 * @see BlackBoard#setEvent(graphlab.platform.core.Event,Object) 061 */ 062 public <T> T getData(String key) { 063 return (T) data.get(key); 064 } 065 066 067 /** 068 * @param key 069 * @param value 070 */ 071 public void setData(String key, Object value) { 072 data.put(key, value); 073 fireListeners(key, value); 074 } 075 076 public boolean contains(String key) { 077 return data.containsKey(key); 078 } 079 080 /** 081 * adds a listener to the Data , which when the data changed, will be notified 082 * 083 * @param key 084 * @param listener 085 */ 086 public void addListener(String key, Listener listener) { 087 if (firingCount(key) == 0) { 088 putEvent(listeners, key, listener); 089 } else { 090 putEventAfter(key, listener, true); 091 } 092 } 093 094 private void putEvent(HashMap<String, HashSet<Listener>> _map, String key, Listener listener) { 095 HashSet<Listener> notifiables = _map.get(key); 096 if (notifiables == null) { 097 notifiables = new HashSet<Listener>(); 098 _map.put(key, notifiables); 099 } 100 notifiables.add(listener); 101 } 102 103 private void putEventAfter(String key, Listener listener, boolean isAdded) { 104 Vector<Couple<Boolean, Listener>> couples = addRemoveAfterFiring.get(key); 105 if (couples == null) { 106 couples = new Vector<Couple<Boolean, Listener>>(); 107 addRemoveAfterFiring.put(key, couples); 108 } 109 couples.add(new Couple<Boolean, Listener>(isAdded, listener)); 110 } 111 112 /** 113 * see addAttributeListener 114 * 115 * @param listener 116 */ 117 public void removeListener(String key, Listener listener) { 118 if (firingCount(key) == 0) { 119 HashSet<Listener> notifiables = listeners.get(key); 120 if (notifiables != null) 121 notifiables.remove(listener); 122 } else { 123 putEventAfter(key, listener, false); 124 } 125 } 126 127 public HashSet<Listener> getListeners(String key) { 128 return listeners.get(key); 129 } 130 131 /** 132 * @param key 133 */ 134 protected void fireListeners(String key, Object newValue) { 135 int fi = firingCount(key); 136 firingNames.put(key, fi + 1); 137 HashSet<Listener> notifiables = listeners.get(key); 138 if (notifiables != null) 139 for (Listener listener : notifiables) 140 try { 141 listener.keyChanged(key, newValue); 142 } catch (Exception e) { 143 e.printStackTrace(); 144 StaticUtils.addExceptiontoLog(e, this); 145 } 146 // } 147 firingNames.put(key, fi); 148 if (fi == 0) { 149 Vector<Couple<Boolean, Listener>> couples = addRemoveAfterFiring.get(key); 150 if (couples != null) { 151 for (Couple<Boolean, Listener> couple : couples) { 152 if (couple.a) { 153 addListener(key, couple.b); 154 } else { 155 removeListener(key, couple.b); 156 } 157 } 158 couples.clear(); 159 } 160 // HashSet<Listener> nn = removeAfterFiring.get(event); 161 // if (nn != null) { 162 // for (Listener _ : nn) 163 // removeListener(event, _); 164 // nn.clear(); 165 // } 166 // nn = addAfterFiring.get(event); 167 // if (nn != null) { 168 // for (Listener _ : nn) 169 // addListener(event, _); 170 // nn.clear(); 171 // } 172 } 173 } 174 175 //represents the number of threads that are firing the listeners of the given name 176 private int firingCount(String name) { 177 Integer fi = firingNames.get(name); 178 if (fi == null) 179 fi = 0; 180 return fi; 181 } 182 183 184 //todo: agar vasat e fire kardan iek thread e dige set kard oon name ro chekar baiad kard? 185 private class Couple<A, B> { 186 public A a; 187 public B b; 188 189 public Couple(A a, B b) { 190 this.a = a; 191 this.b = b; 192 } 193 } 194 }