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