1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/**
4 *******************************************************************************
5 * Copyright (C) 2001-2009, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9package com.ibm.icu.impl;
10
11import java.util.ArrayList;
12import java.util.EventListener;
13import java.util.Iterator;
14import java.util.List;
15
16/**
17 * <p>Abstract implementation of a notification facility.  Clients add
18 * EventListeners with addListener and remove them with removeListener.
19 * Notifiers call notifyChanged when they wish to notify listeners.
20 * This queues the listener list on the notification thread, which
21 * eventually dequeues the list and calls notifyListener on each
22 * listener in the list.</p>
23 *
24 * <p>Subclasses override acceptsListener and notifyListener
25 * to add type-safe notification.  AcceptsListener should return
26 * true if the listener is of the appropriate type; ICUNotifier
27 * itself will ensure the listener is non-null and that the
28 * identical listener is not already registered with the Notifier.
29 * NotifyListener should cast the listener to the appropriate
30 * type and call the appropriate method on the listener.
31 */
32public abstract class ICUNotifier {
33    private final Object notifyLock = new Object();
34    private NotifyThread notifyThread;
35    private List<EventListener> listeners;
36
37    /**
38     * Add a listener to be notified when notifyChanged is called.
39     * The listener must not be null. AcceptsListener must return
40     * true for the listener.  Attempts to concurrently
41     * register the identical listener more than once will be
42     * silently ignored.
43     */
44    public void addListener(EventListener l) {
45        if (l == null) {
46            throw new NullPointerException();
47        }
48
49        if (acceptsListener(l)) {
50            synchronized (notifyLock) {
51                if (listeners == null) {
52                    listeners = new ArrayList<EventListener>();
53                } else {
54                    // identity equality check
55                    for (EventListener ll : listeners) {
56                        if (ll == l) {
57                            return;
58                        }
59                    }
60                }
61
62                listeners.add(l);
63            }
64        } else {
65            throw new IllegalStateException("Listener invalid for this notifier.");
66        }
67    }
68
69    /**
70     * Stop notifying this listener.  The listener must
71     * not be null.  Attempts to remove a listener that is
72     * not registered will be silently ignored.
73     */
74    public void removeListener(EventListener l) {
75        if (l == null) {
76            throw new NullPointerException();
77        }
78        synchronized (notifyLock) {
79            if (listeners != null) {
80                // identity equality check
81                Iterator<EventListener> iter = listeners.iterator();
82                while (iter.hasNext()) {
83                    if (iter.next() == l) {
84                        iter.remove();
85                        if (listeners.size() == 0) {
86                            listeners = null;
87                        }
88                        return;
89                    }
90                }
91            }
92        }
93    }
94
95    /**
96     * Queue a notification on the notification thread for the current
97     * listeners.  When the thread unqueues the notification, notifyListener
98     * is called on each listener from the notification thread.
99     */
100    public void notifyChanged() {
101        synchronized (notifyLock) {
102            if (listeners != null) {
103                if (notifyThread == null) {
104                    notifyThread = new NotifyThread(this);
105                    notifyThread.setDaemon(true);
106                    notifyThread.start();
107                }
108                notifyThread.queue(listeners.toArray(new EventListener[listeners.size()]));
109            }
110        }
111    }
112
113    /**
114     * The notification thread.
115     */
116    private static class NotifyThread extends Thread {
117        private final ICUNotifier notifier;
118        private final List<EventListener[]> queue = new ArrayList<EventListener[]>();
119
120        NotifyThread(ICUNotifier notifier) {
121            this.notifier = notifier;
122        }
123
124        /**
125         * Queue the notification on the thread.
126         */
127        public void queue(EventListener[] list) {
128            synchronized (this) {
129                queue.add(list);
130                notify();
131            }
132        }
133
134        /**
135         * Wait for a notification to be queued, then notify all
136         * listeners listed in the notification.
137         */
138        @Override
139        public void run() {
140            EventListener[] list;
141            while (true) {
142                try {
143                    synchronized (this) {
144                        while (queue.isEmpty()) {
145                            wait();
146                        }
147                        list = queue.remove(0);
148                    }
149
150                    for (int i = 0; i < list.length; ++i) {
151                        notifier.notifyListener(list[i]);
152                    }
153                }
154                catch (InterruptedException e) {
155                }
156            }
157        }
158    }
159
160    /**
161     * Subclasses implement this to return true if the listener is
162     * of the appropriate type.
163     */
164    protected abstract boolean acceptsListener(EventListener l);
165
166    /**
167     * Subclasses implement this to notify the listener.
168     */
169    protected abstract void notifyListener(EventListener l);
170}
171