001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.util.listener;
018
019import java.io.Serializable;
020import java.util.Iterator;
021import java.util.List;
022import java.util.concurrent.CopyOnWriteArrayList;
023
024import org.apache.wicket.util.collections.ReverseListIterator;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * Represents a collection of listeners. Facilitates invocation of events on each listener.
030 * <p>
031 * Listeners will be invoked in the order added to the collection when using
032 * {@link #notify(INotifier)} or in reversed order when using {@link #reversedNotify(INotifier)}.
033 * </p>
034 * 
035 * @author ivaynberg (Igor Vaynberg)
036 * @author Jonathan Locke
037 * 
038 * @param <T>
039 *            type of listeners
040 */
041public abstract class ListenerCollection<T> implements Serializable, Iterable<T>
042{
043        private static final long serialVersionUID = 1L;
044
045        private static final Logger logger = LoggerFactory.getLogger(ListenerCollection.class);
046
047        /** list of listeners */
048        private final List<T> listeners = new CopyOnWriteArrayList<>();
049
050        /**
051         * Adds a listener to this set of listeners.
052         * 
053         * @param listener
054         *            The listener to add
055         * @return {@code true} if the listener was added
056         */
057        public boolean add(final T listener)
058        {
059                if ((listener == null) && !isAllowingNulls())
060                {
061                        return false;
062                }
063                if (!isAllowingDuplicates() && listeners.contains(listener))
064                {
065                        return false;
066                }
067                listeners.add(listener);
068                return true;
069        }
070
071        /**
072         * Notifies each listener in this
073         * 
074         * @param notifier
075         *            notifier used to notify each listener
076         */
077        protected void notify(final INotifier<T> notifier)
078        {
079                for (T listener : listeners)
080                {
081                        notifier.notify(listener);
082                }
083        }
084
085        /**
086         * Notifies each listener in this set ignoring exceptions. Exceptions will be logged.
087         * 
088         * @param notifier
089         *            notifier used to notify each listener
090         */
091        protected void notifyIgnoringExceptions(final INotifier<T> notifier)
092        {
093                for (T listener : listeners)
094                {
095                        try
096                        {
097                                notifier.notify(listener);
098                        }
099                        catch (Exception e)
100                        {
101                                logger.error("Error invoking listener: " + listener, e);
102                        }
103                }
104        }
105
106        /**
107         * Notifies each listener in this set in reverse order ignoring exceptions
108         * 
109         * @param notifier
110         *            notifier used to notify each listener
111         */
112        protected void reversedNotifyIgnoringExceptions(final INotifier<T> notifier)
113        {
114                reversedNotify(new INotifier<T>()
115                {
116                        @Override
117                        public void notify(T listener)
118                        {
119                                try
120                                {
121                                        notifier.notify(listener);
122                                }
123                                catch (Exception e)
124                                {
125                                        logger.error("Error invoking listener: " + listener, e);
126                                }
127
128                        }
129
130                });
131        }
132
133        /**
134         * Notifies each listener in this in reversed order
135         * 
136         * @param notifier
137         *            notifier used to notify each listener
138         */
139        protected void reversedNotify(final INotifier<T> notifier)
140        {
141                Iterator<T> it = new ReverseListIterator<>(listeners);
142                while (it.hasNext())
143                {
144                        T listener = it.next();
145                        notifier.notify(listener);
146                }
147        }
148
149        /**
150         * Removes a listener from this set.
151         * 
152         * @param listener
153         *            The listener to remove
154         */
155        public void remove(final T listener)
156        {
157                listeners.remove(listener);
158        }
159
160        /**
161         * Whether or not added listeners should be checked for duplicates.
162         * 
163         * @return {@code true} to ignore duplicates
164         */
165        protected boolean isAllowingDuplicates()
166        {
167                return true;
168        }
169
170        /**
171         * Whether or not to allow {@code null}s in listener collection.
172         * 
173         * @return {@code} true to allow nulls to be added to the collection
174         */
175        protected boolean isAllowingNulls()
176        {
177                return false;
178        }
179
180        /**
181         * Used to notify a listener. Usually this method simply forwards the {@link #notify(Object)} to
182         * the proper method on the listener.
183         * 
184         * @author ivaynberg (Igor Vaynberg)
185         * @param <T>
186         */
187        protected static interface INotifier<T>
188        {
189                void notify(T listener);
190        }
191
192        /**
193         * Returns an iterator that can iterate the listeners.
194         * 
195         * @return an iterator that can iterate the listeners.
196         */
197        @Override
198        public Iterator<T> iterator()
199        {
200                return listeners.iterator();
201        }
202}