1 /*
2  * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.java.accessibility.util;
27 
28 import java.util.*;
29 import java.beans.*;
30 import java.awt.*;
31 import java.awt.event.*;
32 import javax.accessibility.*;
33 
34 /**
35  * <P>{@code AccessibilityEventMonitor} implements a PropertyChange listener
36  * on every UI object that implements interface {@code Accessible} in the Java
37  * Virtual Machine.  The events captured by these listeners are made available
38  * through listeners supported by {@code AccessibilityEventMonitor}.
39  * With this, all the individual events on each of the UI object
40  * instances are funneled into one set of PropertyChange listeners.
41  * <p>This class depends upon {@link EventQueueMonitor}, which provides the base
42  * level support for capturing the top-level containers as they are created.
43  *
44  */
45 
46 public class AccessibilityEventMonitor {
47 
48     // listeners
49     /**
50      * The current list of registered {@link java.beans.PropertyChangeListener
51      * PropertyChangeListener} classes.
52      *
53      * @see #addPropertyChangeListener
54      * @see #removePropertyChangeListener
55      */
56     static protected final AccessibilityListenerList listenerList =
57         new AccessibilityListenerList();
58 
59 
60     /**
61      * The actual listener that is installed on the component instances.
62      * This listener calls the other registered listeners when an event
63      * occurs.  By doing things this way, the actual number of listeners
64      * installed on a component instance is drastically reduced.
65      */
66     static private final AccessibilityEventListener accessibilityListener =
67         new AccessibilityEventListener();
68 
69     /**
70      * Adds the specified listener to receive all PropertyChange events on
71      * each UI object instance in the Java Virtual Machine as they occur.
72      * <P>Note: This listener is automatically added to all component
73      * instances created after this method is called.  In addition, it
74      * is only added to UI object instances that support this listener type.
75      *
76      * @param l the listener to add
77      *
78      * @see #removePropertyChangeListener
79      */
addPropertyChangeListener(PropertyChangeListener l)80     static public void addPropertyChangeListener(PropertyChangeListener l) {
81         if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
82             accessibilityListener.installListeners();
83         }
84         listenerList.add(PropertyChangeListener.class, l);
85     }
86 
87     /**
88      * Removes the specified listener so it no longer receives PropertyChange
89      * events when they occur.
90      * @see #addPropertyChangeListener
91      * @param l the listener to remove
92      */
removePropertyChangeListener(PropertyChangeListener l)93     static public void removePropertyChangeListener(PropertyChangeListener l) {
94         listenerList.remove(PropertyChangeListener.class, l);
95         if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
96             accessibilityListener.removeListeners();
97         }
98     }
99 
100 
101     /**
102      * AccessibilityEventListener is the class that does all the work for
103      * AccessibilityEventMonitor.  It is not intended for use by any other
104      * class except AccessibilityEventMonitor.
105      *
106      */
107 
108     static class AccessibilityEventListener implements TopLevelWindowListener,
109                 PropertyChangeListener {
110 
111         /**
112          * Create a new instance of this class and install it on each component
113          * instance in the virtual machine that supports any of the currently
114          * registered listeners in AccessibilityEventMonitor.  Also registers
115          * itself as a TopLevelWindowListener with EventQueueMonitor so it can
116          * automatically add new listeners to new components.
117          * @see EventQueueMonitor
118          * @see AccessibilityEventMonitor
119          */
AccessibilityEventListener()120         public AccessibilityEventListener() {
121             EventQueueMonitor.addTopLevelWindowListener(this);
122         }
123 
124         /**
125          * Installs PropertyChange listeners on all Accessible objects based
126          * upon the current topLevelWindows cached by EventQueueMonitor.
127          * @see EventQueueMonitor
128          * @see AWTEventMonitor
129          */
installListeners()130         protected void installListeners() {
131             Window[] topLevelWindows = EventQueueMonitor.getTopLevelWindows();
132             if (topLevelWindows != null) {
133                 for (int i = 0; i < topLevelWindows.length; i++) {
134                     if (topLevelWindows[i] instanceof Accessible) {
135                         installListeners((Accessible) topLevelWindows[i]);
136                     }
137                 }
138             }
139         }
140 
141         /**
142          * Installs PropertyChange listeners to the Accessible object, and it's
143          * children (so long as the object isn't of TRANSIENT state).
144          * @param a the Accessible object to add listeners to
145          */
installListeners(Accessible a)146         protected void installListeners(Accessible a) {
147             installListeners(a.getAccessibleContext());
148         }
149 
150         /**
151          * Installs PropertyChange listeners to the AccessibleContext object,
152          * and it's * children (so long as the object isn't of TRANSIENT state).
153          * @param a the Accessible object to add listeners to
154          */
installListeners(AccessibleContext ac)155         private void installListeners(AccessibleContext ac) {
156 
157             if (ac != null) {
158                 AccessibleStateSet states = ac.getAccessibleStateSet();
159                 if (!states.contains(AccessibleState.TRANSIENT)) {
160                     ac.addPropertyChangeListener(this);
161                     /*
162                      * Don't add listeners to transient children. Components
163                      * with transient children should return an AccessibleStateSet
164                      * containing AccessibleState.MANAGES_DESCENDANTS. Components
165                      * may not explicitly return the MANAGES_DESCENDANTS state.
166                      * In this case, don't add listeners to the children of
167                      * lists, tables and trees.
168                      */
169                     AccessibleStateSet set = ac.getAccessibleStateSet();
170                     if (set.contains(_AccessibleState.MANAGES_DESCENDANTS)) {
171                         return;
172                     }
173                     AccessibleRole role = ac.getAccessibleRole();
174                     if (role == AccessibleRole.LIST ||
175                         role == AccessibleRole.TREE) {
176                         return;
177                     }
178                     if (role == AccessibleRole.TABLE) {
179                         // handle Oracle tables containing tables
180                         Accessible child = ac.getAccessibleChild(0);
181                         if (child != null) {
182                             AccessibleContext ac2 = child.getAccessibleContext();
183                             if (ac2 != null) {
184                                 role = ac2.getAccessibleRole();
185                                 if (role != null && role != AccessibleRole.TABLE) {
186                                     return;
187                                 }
188                             }
189                         }
190                     }
191                     int count = ac.getAccessibleChildrenCount();
192                     for (int i = 0; i < count; i++) {
193                         Accessible child = ac.getAccessibleChild(i);
194                         if (child != null) {
195                             installListeners(child);
196                         }
197                     }
198                 }
199             }
200         }
201 
202         /**
203          * Removes PropertyChange listeners on all Accessible objects based
204          * upon the topLevelWindows cached by EventQueueMonitor.
205          * @param eventID the event ID
206          * @see EventID
207          */
removeListeners()208         protected void removeListeners() {
209             Window[] topLevelWindows = EventQueueMonitor.getTopLevelWindows();
210             if (topLevelWindows != null) {
211                 for (int i = 0; i < topLevelWindows.length; i++) {
212                     if (topLevelWindows[i] instanceof Accessible) {
213                         removeListeners((Accessible) topLevelWindows[i]);
214                     }
215                 }
216             }
217         }
218 
219         /**
220          * Removes PropertyChange listeners for the given Accessible object,
221          * it's children (so long as the object isn't of TRANSIENT state).
222          * @param a the Accessible object to remove listeners from
223          */
removeListeners(Accessible a)224         protected void removeListeners(Accessible a) {
225             removeListeners(a.getAccessibleContext());
226         }
227 
228         /**
229          * Removes PropertyChange listeners for the given AccessibleContext
230          * object, it's children (so long as the object isn't of TRANSIENT
231          * state).
232          * @param a the Accessible object to remove listeners from
233          */
removeListeners(AccessibleContext ac)234         private void removeListeners(AccessibleContext ac) {
235 
236 
237             if (ac != null) {
238                 // Listeners are not added to transient components.
239                 AccessibleStateSet states = ac.getAccessibleStateSet();
240                 if (!states.contains(AccessibleState.TRANSIENT)) {
241                     ac.removePropertyChangeListener(this);
242                     /*
243                      * Listeners are not added to transient children. Components
244                      * with transient children should return an AccessibleStateSet
245                      * containing AccessibleState.MANAGES_DESCENDANTS. Components
246                      * may not explicitly return the MANAGES_DESCENDANTS state.
247                      * In this case, don't remove listeners from the children of
248                      * lists, tables and trees.
249                      */
250                     if (states.contains(_AccessibleState.MANAGES_DESCENDANTS)) {
251                         return;
252                     }
253                     AccessibleRole role = ac.getAccessibleRole();
254                     if (role == AccessibleRole.LIST ||
255                         role == AccessibleRole.TABLE ||
256                         role == AccessibleRole.TREE) {
257                         return;
258                     }
259                     int count = ac.getAccessibleChildrenCount();
260                     for (int i = 0; i < count; i++) {
261                         Accessible child = ac.getAccessibleChild(i);
262                         if (child != null) {
263                             removeListeners(child);
264                         }
265                     }
266                 }
267             }
268         }
269 
270         /********************************************************************/
271         /*                                                                  */
272         /* Listener Interface Methods                                       */
273         /*                                                                  */
274         /********************************************************************/
275 
276         /* TopLevelWindow Methods ***************************************/
277 
278         /**
279          * Called when top level window is created.
280          * @see EventQueueMonitor
281          * @see EventQueueMonitor#addTopLevelWindowListener
282          */
topLevelWindowCreated(Window w)283         public void topLevelWindowCreated(Window w) {
284             if (w instanceof Accessible) {
285                 installListeners((Accessible) w);
286             }
287         }
288 
289         /**
290          * Called when top level window is destroyed.
291          * @see EventQueueMonitor
292          * @see EventQueueMonitor#addTopLevelWindowListener
293          */
topLevelWindowDestroyed(Window w)294         public void topLevelWindowDestroyed(Window w) {
295             if (w instanceof Accessible) {
296                 removeListeners((Accessible) w);
297             }
298         }
299 
300 
301         /* PropertyChangeListener Methods **************************************/
302 
propertyChange(PropertyChangeEvent e)303         public void propertyChange(PropertyChangeEvent e) {
304             // propogate the event
305             Object[] listeners =
306                     AccessibilityEventMonitor.listenerList.getListenerList();
307             for (int i = listeners.length-2; i>=0; i-=2) {
308                 if (listeners[i]==PropertyChangeListener.class) {
309                     ((PropertyChangeListener)listeners[i+1]).propertyChange(e);
310                 }
311             }
312 
313             // handle childbirth/death
314             String name = e.getPropertyName();
315             if (name.compareTo(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY) == 0) {
316                 Object oldValue = e.getOldValue();
317                 Object newValue = e.getNewValue();
318 
319                 if ((oldValue == null) ^ (newValue == null)) { // one null, not both
320                     if (oldValue != null) {
321                         // this Accessible is a child that's going away
322                         if (oldValue instanceof Accessible) {
323                             Accessible a = (Accessible) oldValue;
324                             removeListeners(a.getAccessibleContext());
325                         } else if (oldValue instanceof AccessibleContext) {
326                             removeListeners((AccessibleContext) oldValue);
327                         }
328                     } else if (newValue != null) {
329                         // this Accessible is a child was just born
330                         if (newValue instanceof Accessible) {
331                             Accessible a = (Accessible) newValue;
332                             installListeners(a.getAccessibleContext());
333                         } else if (newValue instanceof AccessibleContext) {
334                             installListeners((AccessibleContext) newValue);
335                         }
336                     }
337                 } else {
338                     System.out.println("ERROR in usage of PropertyChangeEvents for: " + e.toString());
339                 }
340             }
341         }
342     }
343 }
344 
345 /*
346  * workaround for no public AccessibleState constructor
347  */
348 class _AccessibleState extends AccessibleState {
349     /**
350      * Indicates this object is responsible for managing its
351      * subcomponents.  This is typically used for trees and tables
352      * that have a large number of subcomponents and where the
353      * objects are created only when needed and otherwise remain virtual.
354      * The application should not manage the subcomponents directly.
355      */
356     public static final _AccessibleState MANAGES_DESCENDANTS
357         = new _AccessibleState ("managesDescendants");
358 
359     /**
360      * Creates a new AccessibleState using the given locale independent key.
361      * This should not be a public method.  Instead, it is used to create
362      * the constants in this file to make it a strongly typed enumeration.
363      * Subclasses of this class should enforce similar policy.
364      * <p>
365      * The key String should be a locale independent key for the state.
366      * It is not intended to be used as the actual String to display
367      * to the user.  To get the localized string, use toDisplayString.
368      *
369      * @param key the locale independent name of the state.
370      * @see AccessibleBundle#toDisplayString
371      */
_AccessibleState(String key)372     protected _AccessibleState(String key) {
373         super(key);
374     }
375 }
376