Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 267   Methods: 15
NCLOC: 198   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
NotificationDispatcher.java 35.7% 52.6% 46.7% 47.9%
coverage coverage
 1    /*
 2    * JBoss, Home of Professional Open Source
 3    * Copyright 2005, JBoss Inc., and individual contributors as indicated
 4    * by the @authors tag. See the copyright.txt in the distribution for a
 5    * full listing of individual contributors.
 6    *
 7    * This is free software; you can redistribute it and/or modify it
 8    * under the terms of the GNU Lesser General Public License as
 9    * published by the Free Software Foundation; either version 2.1 of
 10    * the License, or (at your option) any later version.
 11    *
 12    * This software 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 GNU
 15    * Lesser General Public License for more details.
 16    *
 17    * You should have received a copy of the GNU Lesser General Public
 18    * License along with this software; if not, write to the Free
 19    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 20    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 21    */
 22   
 23    package org.jboss.cache.pojo.impl;
 24   
 25    import java.lang.annotation.Annotation;
 26    import java.lang.reflect.Method;
 27    import java.lang.reflect.Modifier;
 28    import java.util.ArrayList;
 29    import java.util.Collections;
 30    import java.util.HashMap;
 31    import java.util.HashSet;
 32    import java.util.List;
 33    import java.util.Map;
 34    import java.util.Set;
 35    import java.util.concurrent.CopyOnWriteArraySet;
 36    import java.util.regex.Pattern;
 37   
 38    import org.jboss.cache.Fqn;
 39    import org.jboss.cache.pojo.PojoCacheException;
 40    import org.jboss.cache.pojo.notification.annotation.ArrayModified;
 41    import org.jboss.cache.pojo.notification.annotation.Attached;
 42    import org.jboss.cache.pojo.notification.annotation.Detached;
 43    import org.jboss.cache.pojo.notification.annotation.FieldModified;
 44    import org.jboss.cache.pojo.notification.annotation.ListModified;
 45    import org.jboss.cache.pojo.notification.annotation.MapModified;
 46    import org.jboss.cache.pojo.notification.annotation.PojoCacheListener;
 47    import org.jboss.cache.pojo.notification.annotation.SetModified;
 48    import org.jboss.cache.pojo.notification.annotation.TransactionCompleted;
 49    import org.jboss.cache.pojo.notification.annotation.TransactionRegistered;
 50    import org.jboss.cache.pojo.notification.event.ArrayModifiedEvent;
 51    import org.jboss.cache.pojo.notification.event.AttachedEvent;
 52    import org.jboss.cache.pojo.notification.event.DetachedEvent;
 53    import org.jboss.cache.pojo.notification.event.Event;
 54    import org.jboss.cache.pojo.notification.event.FieldModifiedEvent;
 55    import org.jboss.cache.pojo.notification.event.ListModifiedEvent;
 56    import org.jboss.cache.pojo.notification.event.MapModifiedEvent;
 57    import org.jboss.cache.pojo.notification.event.SetModifiedEvent;
 58    import org.jboss.cache.pojo.notification.event.TransactionCompletedEvent;
 59    import org.jboss.cache.pojo.notification.event.TransactionRegisteredEvent;
 60   
 61    // $Id: NotificationDispatcher.java,v 1.2 2007/06/29 04:34:01 jgreene Exp $
 62   
 63    /**
 64    * Dispatches notification events to POJO cache listeners.
 65    *
 66    * @author Jason T. Greene
 67    * @revision $Id: NotificationDispatcher.java,v 1.2 2007/06/29 04:34:01 jgreene Exp $
 68    */
 69    class NotificationDispatcher
 70    {
 71    private final static Map<Class<? extends Annotation>, Class<? extends Event>> annotations = new HashMap<Class<? extends Annotation>, Class<? extends Event>>();
 72    private final Set<Entry> listeners = new CopyOnWriteArraySet<Entry>();
 73    private Set<Object> filteredListeners = new HashSet<Object>();
 74    private volatile boolean hasFilters;
 75   
 76    static
 77    {
 78  77 annotations.put(Attached.class, AttachedEvent.class);
 79  77 annotations.put(Detached.class, DetachedEvent.class);
 80  77 annotations.put(FieldModified.class, FieldModifiedEvent.class);
 81  77 annotations.put(ListModified.class, ListModifiedEvent.class);
 82  77 annotations.put(MapModified.class, MapModifiedEvent.class);
 83  77 annotations.put(SetModified.class, SetModifiedEvent.class);
 84  77 annotations.put(ArrayModified.class, ArrayModifiedEvent.class);
 85  77 annotations.put(TransactionRegistered.class, TransactionRegisteredEvent.class);
 86  77 annotations.put(TransactionCompleted.class, TransactionCompletedEvent.class);
 87    }
 88   
 89    final static class Entry
 90    {
 91    private final Object listener;
 92    private final Pattern pattern;
 93    private final Map<Class<?>, List<Method>> notifiers;
 94   
 95  50 private Entry(Object listener, boolean build)
 96    {
 97  50 this(listener, build, null);
 98    }
 99   
 100  50 private Entry(Object listener, boolean build, Pattern pattern)
 101    {
 102  50 if (listener == null)
 103  0 throw new IllegalArgumentException("Listener can't be null");
 104   
 105  50 this.listener = listener;
 106  50 this.pattern = pattern;
 107   
 108  50 this.notifiers = build ? buildNotifiers(listener.getClass()) : null;
 109    }
 110   
 111    // equality is confined to listener
 112  0 public int hashCode()
 113    {
 114  0 return listener.hashCode();
 115    }
 116   
 117    // equality is confined to listener
 118  0 public boolean equals(Object o)
 119    {
 120  0 if (o == this)
 121  0 return true;
 122  0 if (! (o instanceof Entry))
 123  0 return false;
 124   
 125  0 return ((Entry)o).equals(listener);
 126    }
 127   
 128  50 private static Map<Class<?>, List<Method>> buildNotifiers(Class clazz)
 129    {
 130  50 if (! Modifier.isPublic(clazz.getModifiers()))
 131  0 throw new IllegalArgumentException("Listener must be public! Class:" + clazz.getName());
 132   
 133  50 if (! clazz.isAnnotationPresent(PojoCacheListener.class))
 134  0 throw new IllegalArgumentException("Not a listener, class did not contain @PojoCacheListener. Class: " + clazz.getName());
 135   
 136   
 137  50 Map<Class<?>, List<Method>> notifiers = new HashMap<Class<?>, List<Method>>();
 138  50 for (Method method : clazz.getMethods())
 139    {
 140  600 for (Annotation annotation : method.getAnnotations())
 141    {
 142  304 Class<? extends Event> event = annotations.get(annotation.annotationType());
 143  304 if (event == null)
 144  0 continue;
 145   
 146  304 Class<?>[] types = method.getParameterTypes();
 147  304 if (types.length != 1 || !types[0].isAssignableFrom(event))
 148    {
 149  0 throw new IllegalArgumentException("Listener has invlaid method signature for annotation. " +
 150    "Method: \"" + method.getName() + "\" " +
 151    "Annotation: \"" + annotation.annotationType().getSimpleName() + "\" " +
 152    "Expected Parameter: \"" + event.getSimpleName() + "\"");
 153    }
 154   
 155  304 List<Method> list = notifiers.get(event);
 156  304 if (list == null)
 157    {
 158  304 list = new ArrayList<Method>();
 159  304 notifiers.put(event, list);
 160    }
 161   
 162  304 list.add(method);
 163    }
 164    }
 165   
 166  50 return notifiers;
 167    }
 168    }
 169   
 170  50 void add(Object listener)
 171    {
 172  50 listeners.add(new Entry(listener, true));
 173    }
 174   
 175    // gaurds filteredListeners
 176  0 synchronized void add(Object listener, Pattern pattern)
 177    {
 178  0 listeners.add(new Entry(listener, true, pattern));
 179  0 filteredListeners.add(listener);
 180  0 hasFilters = true;
 181    }
 182   
 183    // gaurds filteredListeners
 184  0 synchronized void remove(Object listener)
 185    {
 186  0 filteredListeners.remove(listener);
 187  0 if (filteredListeners.size() == 0)
 188  0 hasFilters = false;
 189  0 listeners.remove(new Entry(listener, false));
 190    }
 191   
 192  598 boolean hasFilters()
 193    {
 194  598 return hasFilters;
 195    }
 196   
 197  0 Set<Object> getListeners()
 198    {
 199  0 Set<Object> set = new HashSet<Object>();
 200  0 for (Entry entry : listeners)
 201  0 set.add(entry.listener);
 202   
 203  0 return Collections.unmodifiableSet(set);
 204    }
 205   
 206  0 Set<Entry> getListenerEntries(List<Fqn> fqns)
 207    {
 208  0 Set<Entry> set = new HashSet<Entry>();
 209  0 for (Entry entry : listeners)
 210    {
 211  0 if (entry.pattern == null)
 212    {
 213  0 set.add(entry);
 214  0 continue;
 215    }
 216   
 217  0 for (Fqn fqn : fqns)
 218    {
 219  0 if (entry.pattern.matcher(fqn.toString()).matches())
 220    {
 221  0 set.add(entry);
 222  0 break;
 223    }
 224    }
 225    }
 226   
 227  0 return set;
 228    }
 229   
 230  0 boolean isEmpty()
 231    {
 232  0 return listeners.size() == 0;
 233    }
 234   
 235  272 void dispatch(Event notification)
 236    {
 237  272 for (Entry entry : listeners)
 238    {
 239    // Prevent dispatch to filtered entries
 240  272 if (entry.pattern == null)
 241  272 dispatch(notification, entry);
 242    }
 243    }
 244   
 245  0 void dispatch(Event notification, Set<Entry> listeners)
 246    {
 247  0 for (Entry listener : listeners)
 248  0 dispatch(notification, listener);
 249    }
 250   
 251  272 private void dispatch(Event notification, Entry entry)
 252    {
 253  272 List<Method> methods = entry.notifiers.get(notification.getClass());
 254  272 if (methods == null)
 255  93 return;
 256   
 257  179 try
 258    {
 259  179 for (Method method : methods)
 260  179 method.invoke(entry.listener, notification);
 261    }
 262    catch (Exception e)
 263    {
 264  0 throw new PojoCacheException(e);
 265    }
 266    }
 267    }