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