ICUNotifier.java

// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (C) 2001-2009, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.impl;

import java.util.ArrayList;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;

/**
 * Abstract implementation of a notification facility. Clients add EventListeners with addListener
 * and remove them with removeListener. Notifiers call notifyChanged when they wish to notify
 * listeners. This queues the listener list on the notification thread, which eventually dequeues
 * the list and calls notifyListener on each listener in the list.
 *
 * <p>Subclasses override acceptsListener and notifyListener to add type-safe notification.
 * AcceptsListener should return true if the listener is of the appropriate type; ICUNotifier itself
 * will ensure the listener is non-null and that the identical listener is not already registered
 * with the Notifier. NotifyListener should cast the listener to the appropriate type and call the
 * appropriate method on the listener.
 */
public abstract class ICUNotifier {
    private final Object notifyLock = new Object();
    private NotifyThread notifyThread;
    private List<EventListener> listeners;

    /**
     * Add a listener to be notified when notifyChanged is called. The listener must not be null.
     * AcceptsListener must return true for the listener. Attempts to concurrently register the
     * identical listener more than once will be silently ignored.
     */
    public void addListener(EventListener l) {
        if (l == null) {
            throw new NullPointerException();
        }

        if (acceptsListener(l)) {
            synchronized (notifyLock) {
                if (listeners == null) {
                    listeners = new ArrayList<EventListener>();
                } else {
                    // identity equality check
                    for (EventListener ll : listeners) {
                        if (ll == l) {
                            return;
                        }
                    }
                }

                listeners.add(l);
            }
        } else {
            throw new IllegalStateException("Listener invalid for this notifier.");
        }
    }

    /**
     * Stop notifying this listener. The listener must not be null. Attempts to remove a listener
     * that is not registered will be silently ignored.
     */
    public void removeListener(EventListener l) {
        if (l == null) {
            throw new NullPointerException();
        }
        synchronized (notifyLock) {
            if (listeners != null) {
                // identity equality check
                Iterator<EventListener> iter = listeners.iterator();
                while (iter.hasNext()) {
                    if (iter.next() == l) {
                        iter.remove();
                        if (listeners.size() == 0) {
                            listeners = null;
                        }
                        return;
                    }
                }
            }
        }
    }

    /**
     * Queue a notification on the notification thread for the current listeners. When the thread
     * unqueues the notification, notifyListener is called on each listener from the notification
     * thread.
     */
    public void notifyChanged() {
        synchronized (notifyLock) {
            if (listeners != null) {
                if (notifyThread == null) {
                    notifyThread = new NotifyThread(this);
                    notifyThread.setDaemon(true);
                    notifyThread.start();
                }
                notifyThread.queue(listeners.toArray(new EventListener[listeners.size()]));
            }
        }
    }

    /** The notification thread. */
    private static class NotifyThread extends Thread {
        private final ICUNotifier notifier;
        private final List<EventListener[]> queue = new ArrayList<EventListener[]>();

        NotifyThread(ICUNotifier notifier) {
            this.notifier = notifier;
        }

        /** Queue the notification on the thread. */
        public void queue(EventListener[] list) {
            synchronized (this) {
                queue.add(list);
                notify();
            }
        }

        /**
         * Wait for a notification to be queued, then notify all listeners listed in the
         * notification.
         */
        @Override
        public void run() {
            EventListener[] list;
            while (true) {
                try {
                    synchronized (this) {
                        while (queue.isEmpty()) {
                            wait();
                        }
                        list = queue.remove(0);
                    }

                    for (int i = 0; i < list.length; ++i) {
                        notifier.notifyListener(list[i]);
                    }
                } catch (InterruptedException e) {
                }
            }
        }
    }

    /** Subclasses implement this to return true if the listener is of the appropriate type. */
    protected abstract boolean acceptsListener(EventListener l);

    /** Subclasses implement this to notify the listener. */
    protected abstract void notifyListener(EventListener l);
}