001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.observe;
025    
026    import java.lang.ref.WeakReference;
027    import java.util.Iterator;
028    import java.util.concurrent.CopyOnWriteArrayList;
029    import java.util.concurrent.atomic.AtomicBoolean;
030    import net.jcip.annotations.ThreadSafe;
031    import org.jboss.dna.common.util.CheckArg;
032    import org.jboss.dna.common.util.Logger;
033    
034    /**
035     * Reusable manager of change listeners, typically employed by another {@link Observable} implementation.
036     */
037    @ThreadSafe
038    public class ChangeObservers implements Observable {
039    
040        private final CopyOnWriteArrayList<ObserverReference> observers = new CopyOnWriteArrayList<ObserverReference>();
041        private final AtomicBoolean shutdown = new AtomicBoolean(false);
042    
043        public ChangeObservers() {
044        }
045    
046        /**
047         * {@inheritDoc}
048         * 
049         * @see org.jboss.dna.graph.observe.Observable#register(org.jboss.dna.graph.observe.ChangeObserver)
050         */
051        public boolean register( ChangeObserver observer ) {
052            if (observer != null && !shutdown.get() && observers.addIfAbsent(new ObserverReference(observer))) {
053                observer.registeredWith(this);
054                return true;
055            }
056            return false;
057        }
058    
059        /**
060         * {@inheritDoc}
061         * 
062         * @see org.jboss.dna.graph.observe.Observable#unregister(org.jboss.dna.graph.observe.ChangeObserver)
063         */
064        public boolean unregister( ChangeObserver observer ) {
065            if (observer != null && observers.remove(observer)) {
066                observer.unregisteredWith(this);
067                return true;
068            }
069            return false;
070        }
071    
072        /**
073         * Unregister all registered observers, and mark this as no longer accepting new registered observers.
074         */
075        public void shutdown() {
076            shutdown.set(true);
077            while (!observers.isEmpty()) {
078                Iterator<ObserverReference> iter = observers.iterator(); // gets snapshot
079                observers.clear();
080                while (iter.hasNext()) {
081                    ObserverReference reference = iter.next();
082                    if (reference.get() != null) reference.get().unregisteredWith(this);
083                }
084            }
085        }
086    
087        /**
088         * Determine whether there are any observers at the time this method is called.
089         * 
090         * @return true if there are currently no observers, or false if there is at least one observer
091         */
092        public boolean isEmpty() {
093            return observers.isEmpty();
094        }
095    
096        /**
097         * Broadcast the supplied changes to the registered observers.
098         * 
099         * @param changes the changes to broadcast
100         * @throws IllegalArgumentException if the changes reference is null
101         */
102        public void broadcast( Changes changes ) {
103            CheckArg.isNotNull(changes, "changes");
104            for (ObserverReference observerReference : observers) {
105                ChangeObserver observer = observerReference.get();
106                if (observer == null) {
107                    observers.remove(observerReference);
108                    continue;
109                }
110                try {
111                    observer.notify(changes);
112                } catch (Throwable t) {
113                    Logger.getLogger(getClass()).debug(t, "Exception while notifying");
114                }
115            }
116        }
117    
118        /**
119         * A {@link WeakReference} implementation that provides a valid
120         */
121        protected final class ObserverReference extends WeakReference<ChangeObserver> {
122            final int hc;
123    
124            protected ObserverReference( ChangeObserver source ) {
125                super(source);
126                this.hc = source.hashCode();
127            }
128    
129            /**
130             * {@inheritDoc}
131             * 
132             * @see java.lang.Object#hashCode()
133             */
134            @Override
135            public int hashCode() {
136                return hc;
137            }
138    
139            /**
140             * {@inheritDoc}
141             * 
142             * @see java.lang.Object#equals(java.lang.Object)
143             */
144            @Override
145            public boolean equals( Object obj ) {
146                if (obj == this) return true;
147                if (obj instanceof ObserverReference) {
148                    ObserverReference that = (ObserverReference)obj;
149                    ChangeObserver thisSource = this.get();
150                    ChangeObserver thatSource = that.get();
151                    return thisSource == thatSource; // reference equality, not object equality!
152                }
153                if (obj instanceof ChangeObserver) {
154                    ChangeObserver that = (ChangeObserver)obj;
155                    return this.get() == that; // reference equality, not object equality!
156                }
157                return false;
158            }
159        }
160    }