/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.mx.notification;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.management.JMException;
import javax.management.ListenerNotFoundException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

/**
 * A notification listener registry.<p>
 *
 * For addition and removal, the registrations are deeply cloned to 
 * allow the registrations to be iterated externally without 
 * incurring the cost of synchronization.
 * 
 * @see org.jboss.mx.notification.ListenerRegistration
 * 
 * @author  <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>.
 * @author  <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
 *
 * @version $Revision$
 *
 * <p><b>Revisions:</b>
 *
 * <p><b>20030806 Juha Lindfors:</b>
 * <ul>
 * <li>
 *   Added removeAll()
 * </ul>
 *
 */
public class ListenerRegistry
{
   // Attributes ----------------------------------------------------

   /**
    * A map of listeners to a list of NotificationRegistrations.
    */
   private HashMap listeners = new HashMap();

   /**
    * The factory used to generate the listener registration.
    */
   private ListenerRegistrationFactory factory;


   // Constructor ---------------------------------------------------

   /**
    * Create a notification listener registry using the default
    * listener registration factory.
    */
   public ListenerRegistry()
   {
      this(null);
   }

   /**
    * Create a notification listener registry using the passed
    * listener registration factory.<p>
    *
    * @param factory the factory to create registrations, use
    *        null for the default factory
    */
   public ListenerRegistry(ListenerRegistrationFactory factory)
   {
      if (factory == null)
         this.factory = new DefaultListenerRegistrationFactory();
      else
         this.factory = factory;
   }

   // Public --------------------------------------------------------

   /**
    * Adds a listener to a broadcaster<p>
    *
    * @param listener the listener to register
    * @param filter filters the notifications in the broadcaster, can be null
    * @param handback the object to include in the notification, can be null
    * @exception IllegalArgumentException for a null listener
    * @exception JMException for an error adding to the registry
    */
   public void add(NotificationListener listener, NotificationFilter filter, Object handback)
      throws JMException
   {
      if (listener == null)
         throw new IllegalArgumentException("Null listener");

      synchronized(listeners)
      {
         HashMap newListeners = (HashMap) listeners.clone();

         ArrayList registrations = (ArrayList) newListeners.get(listener);
         if (registrations == null)
         {
            registrations = new ArrayList();
            newListeners.put(listener, registrations);
         }
         else
         {
            registrations = (ArrayList) registrations.clone();
            newListeners.put(listener, registrations);
         }

         registrations.add(factory.create(listener, filter, handback));

         listeners = newListeners;
      }
   }

   /**
    * Removes all registrations for a listener.
    *
    * @param listener the listener to remove
    * @exception ListenerNotFoundException when the listener is not registered
    */
   public void remove(NotificationListener listener)
      throws ListenerNotFoundException
   {
      ArrayList registrations = null;
      synchronized(listeners)
      {
         if (listeners.containsKey(listener) == false)
            throw new ListenerNotFoundException("Listener not found " + listener);

         HashMap newListeners = (HashMap) listeners.clone();

         registrations = (ArrayList) newListeners.remove(listener);

         listeners = newListeners;
      }

      for (Iterator iterator = registrations.iterator(); iterator.hasNext();)
      {
          ListenerRegistration registration = (ListenerRegistration) iterator.next();
          registration.removed();
      }
   }

   /**
    * Removes only the registrations for a listener that match the filter and handback.
    *
    * @param listener the listener to remove
    * @param filter the filter of the registration to remove
    * @param handback the handback object of the registration to remove
    * @exception ListenerNotFoundException when the listener is not registered
    */
   public void remove(NotificationListener listener, NotificationFilter filter, Object handback)
      throws ListenerNotFoundException
   {
      ListenerRegistration registration = null;
      synchronized(listeners)
      {
         ArrayList registrations = (ArrayList) listeners.get(listener);
         if (registrations == null)
            throw new ListenerNotFoundException("No registristrations for listener not listener=" + listener +
                                                " filter=" + filter + " handback=" + handback);

         registration = new DefaultListenerRegistration(listener, filter, handback);
         int index = registrations.indexOf(registration);
         if (index == -1)
            throw new ListenerNotFoundException("Listener not found listener=" + listener +
                                                " filter=" + filter + " handback=" + handback);

         HashMap newListeners = (HashMap) listeners.clone();

         registrations = (ArrayList) registrations.clone();
         registration = (ListenerRegistration) registrations.remove(index);
         if (registrations.isEmpty())
            newListeners.remove(listener);
         else
            newListeners.put(listener, registrations);

         listeners = newListeners;
      }

      registration.removed();
   }

   /**
    *  Removes all listeners from this listener registry.
    */
   public void removeAll()
   {
      synchronized (listeners)
      {
         listeners.clear();
      }
   }

   /**
    * Retrieve an iterator over the registrations<p>
    *
    * The iterator behaves like a snapshot of the registrations
    * is taken during this operation.
    *
    * @return the iterator
    */
   public ListenerRegistrationIterator iterator()
   {
      return new ListenerRegistrationIterator();
   }

   /**
    * Test whether the registry is empty
    *
    * @return true when it is empty, false otherwise
    */
   public boolean isEmpty()
   {
      return listeners.isEmpty();
   }

   // Inner Classes -------------------------------------------------

   public class ListenerRegistrationIterator
      implements Iterator
   {
      private Iterator listenerIterator;
      private Iterator registrationIterator;

      /**
       * Constructs a new ListenerRegistration iterator
       */
      public ListenerRegistrationIterator()
      {
         listenerIterator = listeners.values().iterator();
      }

      public boolean hasNext()
      {
         if (registrationIterator == null || registrationIterator.hasNext() == false)
         {
            do
            {
               if (listenerIterator.hasNext() == false)
                  return false;

               registrationIterator = ((ArrayList) listenerIterator.next()).iterator();
            }
            while (registrationIterator.hasNext() == false);
         }
         return true;
      }

      public Object next()
      {
         if (hasNext() == false)
            throw new NoSuchElementException("Use hasNext before next");

         return registrationIterator.next();
      }

      /**
       * Convenience method to returned a typed object
       */
      public ListenerRegistration nextRegistration()
      {
         return (ListenerRegistration) next();
      }

      /**
       * @exception UnsupportedOpertionException remove is not supported
       */
      public void remove()
      {
         throw new UnsupportedOperationException("remove is not supported");
      }
   }
}
