View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  
24  /* *
25   * CategorisedListenerSet.java
26   *
27   * Created: 13 mai 2004
28   *
29   * @author Benjamin Poussin - poussin@codelutin.com
30   * Copyright Code Lutin
31   *
32   *
33   * Mise a jour: $Date$
34   * par : */
35  
36  package org.nuiton.util;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  import java.beans.Statement;
42  import java.util.Iterator;
43  import java.util.WeakHashMap;
44  
45  /**
46   * Objet permettant de géré plusieurs liste de listener de facon simple.
47   * Chaque liste de listener est rangé en fonction d'une cle (categorie)
48   * Une categorie peut avoir un pere, dans ce cas si un event doit etre lancé
49   * sur une categorie il est aussi lancer sur le pere de la categorie.
50   * Mais attention l'inverse n'est pas vrai, un event lancé sur un père n'est
51   * jamais lancé sur ses fils.
52   * Il existe une Category spéciale {@link #ALL} qui permet d'envoyer un event
53   * a tous les listeners.
54   * Si cette classe est la derniere classe a conserver l'objet categorie
55   * alors la categorie est libere et ainsi que les listeners si c'etait aussi
56   * leur derniers referencements
57   *
58   * <p> Si les categories sont representees par des Class, alors vous pouvez
59   * utiliser la hierachie de classe pour creer de facon automatique les peres.
60   *
61   * @param <L> listener type
62   * @see ListenerSet
63   */
64  public class CategorisedListenerSet<L> { // CategorisedListenerSet
65  
66      /** Logger. */
67      private static final Log log = LogFactory.getLog(CategorisedListenerSet.class);
68  
69      /**
70       * permet de remplacer toutes les categories.
71       * Si on utilise cette category, alors tous les listeners present
72       * seront utilisé.
73       */
74      public static final Object ALL = new Object();
75  
76      /**
77       * HashMap de ListenerSet, en cle l'objet qui caracterise la categorie
78       * en valeur un ListenerSet
79       */
80      protected WeakHashMap<Object, ListenerSet<L>> listeners = new WeakHashMap<Object, ListenerSet<L>>();
81  
82      protected WeakHashMap<Object, Object> categoryParent = new WeakHashMap<Object, Object>();
83  
84      protected boolean isClassCategory = true;
85  
86      /** Empty constructor. */
87      public CategorisedListenerSet() {
88  
89      }
90  
91      /**
92       * @param isClassCategory si vrai et que les categorie passé en arguement
93       *                        lors de l'ajout sont de type Class alors lors du fire on recherche aussi
94       *                        les peres dans la hierarchie d'heritage de la classe (Super class et
95       *                        interfaces)
96       */
97      public CategorisedListenerSet(boolean isClassCategory) {
98          this();
99          this.isClassCategory = isClassCategory;
100     }
101 
102     protected void checkCategory(Object category) {
103         if (ALL.equals(category)) {
104             throw new IllegalArgumentException(
105                     "ALL category can't be use to add listener or add Category");
106         }
107     }
108 
109     /**
110      * Ajoute une categorie en indiquant sont pere. Une categorie ne peut
111      * avoir qu'un seul pere, si la nouvelle categorie existait deja
112      * alors l'appel a cette methode change son pere.
113      *
114      * @param parent      le pere de la categorie, null si on ne souhaite pas de pere
115      * @param newCategory la nouvelle caterogie
116      */
117     public void addCategory(Object parent, Object newCategory) {
118         checkCategory(parent);
119         checkCategory(newCategory);
120         categoryParent.put(newCategory, parent);
121     }
122 
123     /**
124      * Ajoute un listener sur une certaine categorie, si la categorie n'existe
125      * alors on la crée en ne lui affectant pas de père
126      *
127      * @param category la categorie dans lequel il faut ajouter le listener
128      * @param l        le listener a ajouter
129      */
130     public void add(Object category, L l) {
131         checkCategory(category);
132         ListenerSet<L> listeners = getListeners(category);
133         listeners.add(l);
134     }
135 
136     /**
137      * Supprime un listener d'une categorie, si la categorie ou le listener
138      * n'existe pas, rien ne se passe.
139      *
140      * @param category la categorie dans lequel il faut supprimer le listener
141      * @param l        le listener a supprimer
142      */
143     public void remove(Object category, L l) {
144         ListenerSet<L> listeners = getListeners(category);
145         listeners.remove(l);
146     }
147 
148     /**
149      * Permet de lancer un event dans une categorie, l'event est aussi propagé
150      * sur les ancètres de la categorie
151      *
152      * @param category   la categorie a partir duquel il faut lancer l'evenement
153      * @param methodName le nom de la méthode de la classe listener
154      * @param event      l'objet event a passer en paramètre de la methode du
155      *                   listener
156      * @throws Exception if event can't be fired
157      */
158     public void fire(Object category, String methodName, Object event)
159             throws Exception {
160         if (log.isTraceEnabled()) {
161             log.trace("fire category: " + category + " method: " + methodName);
162         }
163         ListenerSet<L> ls = getAllListeners(category);
164         ls.fire(methodName, event);
165     }
166 
167     /**
168      * Permet de lancer un event dans une categorie, l'event est aussi propagé
169      * sur les ancètres de la categorie, si un meme objet etait listener
170      * dans plusieurs categories alors il ne recevra qu'une seul notification
171      *
172      * @param category   la categorie a partir duquel il faut lancer l'evenement
173      * @param methodName le nom de la méthode de la classe listener
174      * @throws Exception if event can't be fired
175      */
176     public void fire(Object category, String methodName) throws Exception {
177         for (L l : getAllListeners(category)) {
178             Statement stm = new Statement(l, methodName, null);
179             stm.execute();
180         }
181     }
182 
183     /**
184      * Retourne un Iterator sur tous les listeners qu'il faut prevenir si on
185      * souhaite prevenir une certaine categorie. Ceci inclue les ancetre de la
186      * categorie
187      *
188      * @param category category to get iterator on
189      * @return iterator
190      */
191     public Iterator<L> iterator(Object category) {
192         return getAllListeners(category).iterator();
193     }
194 
195     /**
196      * @param category categorie demandee
197      * @return un ListenerSet contenant tous les listeners c'est à dire les
198      *         listener de la categorie demandé mais aussi les listeners des ancetres
199      */
200     protected ListenerSet<L> getAllListeners(Object category) {
201         ListenerSet<L> result = new ListenerSet<L>();
202         if (ALL.equals(category)) {
203             for (ListenerSet<L> ls : listeners.values()) {
204                 result.addAll(ls);
205             }
206         } else {
207             Object parentCategory = category;
208             while (parentCategory != null) {
209                 result.addAll(getListeners(parentCategory));
210                 if (isClassCategory && parentCategory instanceof Class) {
211                     result.addAll(getListenersClass((Class<?>) parentCategory));
212                 }
213                 parentCategory = categoryParent.get(parentCategory);
214             }
215         }
216         if (log.isTraceEnabled()) {
217             log.trace("getAllListeners category: " + category + " result: "
218                       + result);
219         }
220         return result;
221     }
222 
223     protected ListenerSet<L> getListenersClass(Class<?> category) {
224         ListenerSet<L> result = new ListenerSet<L>();
225         Class<?> superClass = category.getSuperclass();
226         if (superClass != null) {
227             result.addAll(getAllListeners(superClass));
228         }
229         for (Class<?> c : category.getInterfaces()) {
230             result.addAll(getAllListeners(c));
231         }
232         return result;
233     }
234 
235     /**
236      * @param category categorie demandee
237      * @return un ListenerSet contenant seulement les listener de la categorie
238      *         demandé. Si la categorie n'existe pas alors elle est créé.
239      */
240     protected ListenerSet<L> getListeners(Object category) {
241         ListenerSet<L> result = listeners.get(category);
242         if (result == null) {
243             listeners.put(category, result = new ListenerSet<L>());
244         }
245         if (log != null && log.isTraceEnabled()) {
246             log.trace("getListeners category: " + category + " result: "
247                       + result);
248         }
249         return result;
250     }
251 
252     public String toString() {
253         return "Listeners Category: " + categoryParent + "\nListener: "
254                + listeners;
255     }
256 
257 } // CategorisedListenerSet
258