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    }