1 /*
2  * Copyright (c) 2011, 2019, 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 sun.lwawt.macosx;
27 
28 import java.awt.Component;
29 import java.awt.Container;
30 import java.awt.Dimension;
31 import java.awt.KeyboardFocusManager;
32 import java.awt.Point;
33 import java.awt.Window;
34 import java.awt.event.KeyEvent;
35 import java.beans.PropertyChangeEvent;
36 import java.beans.PropertyChangeListener;
37 import java.lang.reflect.InvocationTargetException;
38 import java.util.ArrayList;
39 import java.util.HashSet;
40 import java.util.Set;
41 import java.util.concurrent.Callable;
42 
43 import javax.accessibility.Accessible;
44 import javax.accessibility.AccessibleAction;
45 import javax.accessibility.AccessibleComponent;
46 import javax.accessibility.AccessibleContext;
47 import javax.accessibility.AccessibleRole;
48 import javax.accessibility.AccessibleSelection;
49 import javax.accessibility.AccessibleState;
50 import javax.accessibility.AccessibleStateSet;
51 import javax.accessibility.AccessibleTable;
52 import javax.accessibility.AccessibleText;
53 import javax.accessibility.AccessibleValue;
54 import javax.swing.Icon;
55 import javax.swing.JComponent;
56 import javax.swing.JEditorPane;
57 import javax.swing.JLabel;
58 import javax.swing.JMenuItem;
59 import javax.swing.JTextArea;
60 import javax.swing.KeyStroke;
61 
62 import sun.awt.AWTAccessor;
63 import sun.lwawt.LWWindowPeer;
64 
65 class CAccessibility implements PropertyChangeListener {
66     private static Set<String> ignoredRoles;
67 
68     static {
69         // Need to load the native library for this code.
java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { System.loadLibrary(R); return null; } })70         java.security.AccessController.doPrivileged(
71             new java.security.PrivilegedAction<Void>() {
72                 public Void run() {
73                     System.loadLibrary("awt");
74                     return null;
75                 }
76             });
77     }
78 
79     static CAccessibility sAccessibility;
getAccessibility(final String[] roles)80     static synchronized CAccessibility getAccessibility(final String[] roles) {
81         if (sAccessibility != null) return sAccessibility;
82         sAccessibility = new CAccessibility();
83 
84         if (roles != null) {
85             ignoredRoles = new HashSet<String>(roles.length);
86             for (final String role : roles) ignoredRoles.add(role);
87         } else {
88             ignoredRoles = new HashSet<String>();
89         }
90 
91         return sAccessibility;
92     }
93 
CAccessibility()94     private CAccessibility() {
95         KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this);
96     }
97 
propertyChange(final PropertyChangeEvent evt)98     public void propertyChange(final PropertyChangeEvent evt) {
99         Object newValue = evt.getNewValue();
100         if (newValue == null) return;
101         // Don't post focus on things that don't matter, i.e. alert, colorchooser,
102         // desktoppane, dialog, directorypane, filechooser, filler, fontchoose,
103         // frame, glasspane, layeredpane, optionpane, panel, rootpane, separator,
104         // tooltip, viewport, window.
105         // List taken from initializeRoles() in JavaComponentUtilities.m.
106         if (newValue instanceof Accessible) {
107             AccessibleContext nvAC = ((Accessible) newValue).getAccessibleContext();
108             AccessibleRole nvRole = nvAC.getAccessibleRole();
109             if (!ignoredRoles.contains(roleKey(nvRole))) {
110                 focusChanged();
111             }
112         }
113     }
114 
focusChanged()115     private native void focusChanged();
116 
invokeAndWait(final Callable<T> callable, final Component c)117     static <T> T invokeAndWait(final Callable<T> callable, final Component c) {
118         try {
119             return LWCToolkit.invokeAndWait(callable, c);
120         } catch (final Exception e) { e.printStackTrace(); }
121         return null;
122     }
123 
invokeAndWait(final Callable<T> callable, final Component c, final T defValue)124     static <T> T invokeAndWait(final Callable<T> callable, final Component c, final T defValue) {
125         T value = null;
126         try {
127             value = LWCToolkit.invokeAndWait(callable, c);
128         } catch (final Exception e) { e.printStackTrace(); }
129 
130         return value != null ? value : defValue;
131     }
132 
invokeLater(final Runnable runnable, final Component c)133     static void invokeLater(final Runnable runnable, final Component c) {
134         try {
135             LWCToolkit.invokeLater(runnable, c);
136         } catch (InvocationTargetException e) { e.printStackTrace(); }
137     }
138 
getAccessibleActionDescription(final AccessibleAction aa, final int index, final Component c)139     public static String getAccessibleActionDescription(final AccessibleAction aa, final int index, final Component c) {
140         if (aa == null) return null;
141 
142         return invokeAndWait(new Callable<String>() {
143             public String call() throws Exception {
144                 return aa.getAccessibleActionDescription(index);
145             }
146         }, c);
147     }
148 
149     public static void doAccessibleAction(final AccessibleAction aa, final int index, final Component c) {
150         // We make this an invokeLater because we don't need a reply.
151         if (aa == null) return;
152 
153         invokeLater(new Runnable() {
154             public void run() {
155                 aa.doAccessibleAction(index);
156             }
157         }, c);
158     }
159 
160     public static Dimension getSize(final AccessibleComponent ac, final Component c) {
161         if (ac == null) return null;
162 
163         return invokeAndWait(new Callable<Dimension>() {
164             public Dimension call() throws Exception {
165                 return ac.getSize();
166             }
167         }, c);
168     }
169 
170     public static AccessibleSelection getAccessibleSelection(final AccessibleContext ac, final Component c) {
171         if (ac == null) return null;
172 
173         return invokeAndWait(new Callable<AccessibleSelection>() {
174             public AccessibleSelection call() throws Exception {
175                 return ac.getAccessibleSelection();
176             }
177         }, c);
178     }
179 
180     public static Accessible ax_getAccessibleSelection(final AccessibleContext ac, final int index, final Component c) {
181         if (ac == null) return null;
182 
183         return invokeAndWait(new Callable<Accessible>() {
184             public Accessible call() throws Exception {
185                 final AccessibleSelection as = ac.getAccessibleSelection();
186                 if (as == null) return null;
187                 return as.getAccessibleSelection(index);
188             }
189         }, c);
190     }
191 
192     // KCH - can we make this a postEvent?
193     public static void addAccessibleSelection(final AccessibleContext ac, final int index, final Component c) {
194         if (ac == null) return;
195 
196         invokeLater(new Runnable() {
197             public void run() {
198                 final AccessibleSelection as = ac.getAccessibleSelection();
199                 if (as == null) return;
200                 as.addAccessibleSelection(index);
201             }
202         }, c);
203     }
204 
205     public static AccessibleContext getAccessibleContext(final Accessible a, final Component c) {
206         if (a == null) return null;
207 
208         return invokeAndWait(new Callable<AccessibleContext>() {
209             public AccessibleContext call() throws Exception {
210                 return a.getAccessibleContext();
211             }
212         }, c);
213     }
214 
215     public static boolean isAccessibleChildSelected(final Accessible a, final int index, final Component c) {
216         if (a == null) return false;
217 
218         return invokeAndWait(new Callable<Boolean>() {
219             public Boolean call() throws Exception {
220                 final AccessibleContext ac = a.getAccessibleContext();
221                 if (ac == null) return Boolean.FALSE;
222 
223                 final AccessibleSelection as = ac.getAccessibleSelection();
224                 if (as == null) return Boolean.FALSE;
225 
226                 return as.isAccessibleChildSelected(index);
227             }
228         }, c, false);
229     }
230 
231     public static AccessibleStateSet getAccessibleStateSet(final AccessibleContext ac, final Component c) {
232         if (ac == null) return null;
233 
234         return invokeAndWait(new Callable<AccessibleStateSet>() {
235             public AccessibleStateSet call() throws Exception {
236                 return ac.getAccessibleStateSet();
237             }
238         }, c);
239     }
240 
241     public static boolean contains(final AccessibleContext ac, final AccessibleState as, final Component c) {
242         if (ac == null || as == null) return false;
243 
244         return invokeAndWait(new Callable<Boolean>() {
245             public Boolean call() throws Exception {
246                 final AccessibleStateSet ass = ac.getAccessibleStateSet();
247                 if (ass == null) return null;
248                 return ass.contains(as);
249             }
250         }, c, false);
251     }
252 
253     static String getAccessibleRoleFor(final Accessible a) {
254         final AccessibleContext ac = a.getAccessibleContext();
255         if (ac == null) return null;
256 
257         final AccessibleRole role = ac.getAccessibleRole();
258         return AWTAccessor.getAccessibleBundleAccessor().getKey(role);
259     }
260 
261     public static String getAccessibleRole(final Accessible a, final Component c) {
262         if (a == null) return null;
263 
264         return invokeAndWait(new Callable<String>() {
265             public String call() throws Exception {
266                 final Accessible sa = CAccessible.getSwingAccessible(a);
267                 final String role = getAccessibleRoleFor(a);
268 
269                 if (!"text".equals(role)) return role;
270                 if (sa instanceof JTextArea || sa instanceof JEditorPane) {
271                     return "textarea";
272                 }
273                 return role;
274             }
275         }, c);
276     }
277 
278     public static Point getLocationOnScreen(final AccessibleComponent ac, final Component c) {
279         if (ac == null) return null;
280 
281         return invokeAndWait(new Callable<Point>() {
282             public Point call() throws Exception {
283                 return ac.getLocationOnScreen();
284             }
285         }, c);
286     }
287 
288     public static int getCharCount(final AccessibleText at, final Component c) {
289         if (at == null) return 0;
290 
291         return invokeAndWait(new Callable<Integer>() {
292             public Integer call() throws Exception {
293                 return at.getCharCount();
294             }
295         }, c, 0);
296     }
297 
298     // Accessibility Threadsafety for JavaComponentAccessibility.m
299     public static Accessible getAccessibleParent(final Accessible a, final Component c) {
300         if (a == null) return null;
301 
302         return invokeAndWait(new Callable<Accessible>() {
303             public Accessible call() throws Exception {
304                 final AccessibleContext ac = a.getAccessibleContext();
305                 if (ac == null) return null;
306                 return ac.getAccessibleParent();
307             }
308         }, c);
309     }
310 
311     public static int getAccessibleIndexInParent(final Accessible a, final Component c) {
312         if (a == null) return -1;
313 
314         return invokeAndWait(new Callable<Integer>() {
315             public Integer call() throws Exception {
316                 final AccessibleContext ac = a.getAccessibleContext();
317                 if (ac == null) return null;
318                 return ac.getAccessibleIndexInParent();
319             }
320         }, c, -1);
321     }
322 
323     public static AccessibleComponent getAccessibleComponent(final Accessible a, final Component c) {
324         if (a == null) return null;
325 
326         return invokeAndWait(new Callable<AccessibleComponent>() {
327             public AccessibleComponent call() throws Exception {
328                 final AccessibleContext ac = a.getAccessibleContext();
329                 if (ac == null) return null;
330                 return ac.getAccessibleComponent();
331             }
332         }, c);
333     }
334 
335     public static AccessibleValue getAccessibleValue(final Accessible a, final Component c) {
336         if (a == null) return null;
337 
338         return invokeAndWait(new Callable<AccessibleValue>() {
339             public AccessibleValue call() throws Exception {
340                 final AccessibleContext ac = a.getAccessibleContext();
341                 if (ac == null) return null;
342 
343                 AccessibleValue accessibleValue = ac.getAccessibleValue();
344                 return accessibleValue;
345             }
346         }, c);
347     }
348 
349     public static String getAccessibleName(final Accessible a, final Component c) {
350         if (a == null) return null;
351 
352         return invokeAndWait(new Callable<String>() {
353             public String call() throws Exception {
354                 final AccessibleContext ac = a.getAccessibleContext();
355                 if (ac == null) return null;
356 
357                 final String accessibleName = ac.getAccessibleName();
358                 if (accessibleName == null) {
359                     return ac.getAccessibleDescription();
360                 }
361                 final String acceleratorText = getAcceleratorText(ac);
362                 if (!acceleratorText.isEmpty()) {
363                     return accessibleName +' '+ acceleratorText;
364                 }
365                 return accessibleName;
366             }
367         }, c);
368     }
369 
370     public static AccessibleText getAccessibleText(final Accessible a, final Component c) {
371         if (a == null) return null;
372 
373         return invokeAndWait(new Callable<AccessibleText>() {
374             public AccessibleText call() throws Exception {
375                 final AccessibleContext ac = a.getAccessibleContext();
376                 if (ac == null) return null;
377 
378                 AccessibleText accessibleText = ac.getAccessibleText();
379                 return accessibleText;
380             }
381         }, c);
382     }
383 
384     /*
385      * Returns the JMenuItem accelerator. Implementation of this method is based
386      * on AccessBridge.getAccelerator(AccessibleContext) to access the KeyStroke
387      * and on AquaMenuPainter.paintMenuItem() to convert it to string.
388      */
389     @SuppressWarnings("deprecation")
390     private static String getAcceleratorText(AccessibleContext ac) {
391         String accText = "";
392         Accessible parent = ac.getAccessibleParent();
393         if (parent != null) {
394             // workaround for getAccessibleKeyBinding not returning the
395             // JMenuItem accelerator
396             int indexInParent = ac.getAccessibleIndexInParent();
397             Accessible child = parent.getAccessibleContext()
398                                      .getAccessibleChild(indexInParent);
399             if (child instanceof JMenuItem) {
400                 JMenuItem menuItem = (JMenuItem) child;
401                 KeyStroke keyStroke = menuItem.getAccelerator();
402                 if (keyStroke != null) {
403                     int modifiers = keyStroke.getModifiers();
404                     if (modifiers > 0) {
405                         accText = KeyEvent.getKeyModifiersText(modifiers);
406                     }
407                     int keyCode = keyStroke.getKeyCode();
408                     if (keyCode != 0) {
409                         accText += KeyEvent.getKeyText(keyCode);
410                     } else {
411                         accText += keyStroke.getKeyChar();
412                     }
413                 }
414             }
415         }
416         return accText;
417     }
418 
419     public static String getAccessibleDescription(final Accessible a, final Component c) {
420         if (a == null) return null;
421 
422         return invokeAndWait(new Callable<String>() {
423             public String call() throws Exception {
424                 final AccessibleContext ac = a.getAccessibleContext();
425                 if (ac == null) return null;
426 
427                 final String accessibleDescription = ac.getAccessibleDescription();
428                 if (accessibleDescription == null) {
429                     if (c instanceof JComponent) {
430                         String toolTipText = ((JComponent)c).getToolTipText();
431                         if (toolTipText != null) {
432                             return toolTipText;
433                         }
434                     }
435                 }
436 
437                 return accessibleDescription;
438             }
439         }, c);
440     }
441 
442     public static boolean isFocusTraversable(final Accessible a, final Component c) {
443         if (a == null) return false;
444 
445         return invokeAndWait(new Callable<Boolean>() {
446             public Boolean call() throws Exception {
447                 final AccessibleContext ac = a.getAccessibleContext();
448                 if (ac == null) return null;
449 
450                 final AccessibleComponent aComp = ac.getAccessibleComponent();
451                 if (aComp == null) return null;
452 
453                 return aComp.isFocusTraversable();
454             }
455         }, c, false);
456     }
457 
458     public static Accessible accessibilityHitTest(final Container parent, final float hitPointX, final float hitPointY) {
459         return invokeAndWait(new Callable<Accessible>() {
460             public Accessible call() throws Exception {
461                 final Point p = parent.getLocationOnScreen();
462 
463                 // Make it into local coords
464                 final Point localPoint = new Point((int)(hitPointX - p.getX()), (int)(hitPointY - p.getY()));
465 
466                 final Component component = parent.findComponentAt(localPoint);
467                 if (component == null) return null;
468 
469                 final AccessibleContext axContext = component.getAccessibleContext();
470                 if (axContext == null) return null;
471 
472                 final AccessibleComponent axComponent = axContext.getAccessibleComponent();
473                 if (axComponent == null) return null;
474 
475                 final int numChildren = axContext.getAccessibleChildrenCount();
476                 if (numChildren > 0) {
477                     // It has children, check to see which one is hit.
478                     final Point p2 = axComponent.getLocationOnScreen();
479                     final Point localP2 = new Point((int)(hitPointX - p2.getX()), (int)(hitPointY - p2.getY()));
480                     return CAccessible.getCAccessible(axComponent.getAccessibleAt(localP2));
481                 }
482 
483                 if (!(component instanceof Accessible)) return null;
484                 return CAccessible.getCAccessible((Accessible)component);
485             }
486         }, parent);
487     }
488 
489     public static AccessibleAction getAccessibleAction(final Accessible a, final Component c) {
490         if (a == null) return null;
491 
492         return invokeAndWait(new Callable<AccessibleAction>() {
493             public AccessibleAction call() throws Exception {
494                 final AccessibleContext ac = a.getAccessibleContext();
495                 if (ac == null) return null;
496                 return ac.getAccessibleAction();
497             }
498         }, c);
499     }
500 
501     public static boolean isEnabled(final Accessible a, final Component c) {
502         if (a == null) return false;
503 
504         return invokeAndWait(new Callable<Boolean>() {
505             public Boolean call() throws Exception {
506                 final AccessibleContext ac = a.getAccessibleContext();
507                 if (ac == null) return null;
508 
509                 final AccessibleComponent aComp = ac.getAccessibleComponent();
510                 if (aComp == null) return null;
511 
512                 return aComp.isEnabled();
513             }
514         }, c, false);
515     }
516 
517     // KCH - can we make this a postEvent instead?
518     public static void requestFocus(final Accessible a, final Component c) {
519         if (a == null) return;
520 
521         invokeLater(new Runnable() {
522             public void run() {
523                 final AccessibleContext ac = a.getAccessibleContext();
524                 if (ac == null) return;
525 
526                 final AccessibleComponent aComp = ac.getAccessibleComponent();
527                 if (aComp == null) return;
528 
529                 aComp.requestFocus();
530             }
531         }, c);
532     }
533 
534     public static void requestSelection(final Accessible a, final Component c) {
535         if (a == null) return;
536         invokeLater(new Runnable() {
537             public void run() {
538                 AccessibleContext ac = a.getAccessibleContext();
539                 if (ac == null) return;
540                 int i = ac.getAccessibleIndexInParent();
541                 if (i == -1) return;
542                 Accessible parent = ac.getAccessibleParent();
543                 AccessibleContext pac = parent.getAccessibleContext();
544                 if (pac == null) return;
545                 AccessibleSelection as = pac.getAccessibleSelection();
546                 if (as == null) return;
547                 as.addAccessibleSelection(i);
548             }
549         }, c);
550     }
551 
552     public static Number getMaximumAccessibleValue(final Accessible a, final Component c) {
553         if (a == null) return null;
554 
555         return invokeAndWait(new Callable<Number>() {
556             public Number call() throws Exception {
557                 final AccessibleContext ac = a.getAccessibleContext();
558                 if (ac == null) return null;
559 
560                 final AccessibleValue av = ac.getAccessibleValue();
561                 if (av == null) return null;
562 
563                 return av.getMaximumAccessibleValue();
564             }
565         }, c);
566     }
567 
568     public static Number getMinimumAccessibleValue(final Accessible a, final Component c) {
569         if (a == null) return null;
570 
571         return invokeAndWait(new Callable<Number>() {
572             public Number call() throws Exception {
573                 final AccessibleContext ac = a.getAccessibleContext();
574                 if (ac == null) return null;
575 
576                 final AccessibleValue av = ac.getAccessibleValue();
577                 if (av == null) return null;
578 
579                 return av.getMinimumAccessibleValue();
580             }
581         }, c);
582     }
583 
584     public static String getAccessibleRoleDisplayString(final Accessible a, final Component c) {
585         if (a == null) return null;
586 
587         return invokeAndWait(new Callable<String>() {
588             public String call() throws Exception {
589                 final AccessibleContext ac = a.getAccessibleContext();
590                 if (ac == null) return null;
591 
592                 final AccessibleRole ar = ac.getAccessibleRole();
593                 if (ar == null) return null;
594 
595                 return ar.toDisplayString();
596             }
597         }, c);
598     }
599 
600     public static Number getCurrentAccessibleValue(final AccessibleValue av, final Component c) {
601         if (av == null) return null;
602 
603         return invokeAndWait(new Callable<Number>() {
604             public Number call() throws Exception {
605                 Number currentAccessibleValue = av.getCurrentAccessibleValue();
606                 return currentAccessibleValue;
607             }
608         }, c);
609     }
610 
611     public static Accessible getFocusOwner(final Component c) {
612         return invokeAndWait(new Callable<Accessible>() {
613             public Accessible call() throws Exception {
614                 Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
615                 if (c == null || !(c instanceof Accessible)) return null;
616                 return CAccessible.getCAccessible((Accessible)c);
617             }
618         }, c);
619     }
620 
621     public static boolean[] getInitialAttributeStates(final Accessible a, final Component c) {
622         final boolean[] ret = new boolean[7];
623         if (a == null) return ret;
624 
625         return invokeAndWait(new Callable<boolean[]>() {
626             public boolean[] call() throws Exception {
627                 final AccessibleContext aContext = a.getAccessibleContext();
628                 if (aContext == null) return ret;
629 
630                 final AccessibleComponent aComponent = aContext.getAccessibleComponent();
631                 ret[0] = (aComponent != null);
632                 ret[1] = ((aComponent != null) && (aComponent.isFocusTraversable()));
633                 ret[2] = (aContext.getAccessibleValue() != null);
634                 ret[3] = (aContext.getAccessibleText() != null);
635 
636                 final AccessibleStateSet aStateSet = aContext.getAccessibleStateSet();
637                 ret[4] = (aStateSet.contains(AccessibleState.HORIZONTAL) || aStateSet.contains(AccessibleState.VERTICAL));
638                 ret[5] = (aContext.getAccessibleName() != null);
639                 ret[6] = (aContext.getAccessibleChildrenCount() > 0);
640                 return ret;
641             }
642         }, c);
643     }
644 
645     // Duplicated from JavaComponentAccessibility
646     // Note that values >=0 are indexes into the child array
647     static final int JAVA_AX_ALL_CHILDREN = -1;
648     static final int JAVA_AX_SELECTED_CHILDREN = -2;
649     static final int JAVA_AX_VISIBLE_CHILDREN = -3;
650 
651     // Each child takes up two entries in the array: one for itself and one for its role
652     public static Object[] getChildrenAndRoles(final Accessible a, final Component c, final int whichChildren, final boolean allowIgnored) {
653         if (a == null) return null;
654         return invokeAndWait(new Callable<Object[]>() {
655             public Object[] call() throws Exception {
656                 ArrayList<Object> childrenAndRoles = new ArrayList<Object>();
657                 _addChildren(a, whichChildren, allowIgnored, childrenAndRoles);
658 
659                 /* In the case of fetching a selection, need to check to see if
660                  * the active descendant is at the beginning of the list.  If it
661                  * is not it needs to be moved to the beginning of the list so
662                  * VoiceOver will annouce it correctly.  The list returned
663                  * from Java is always in order from top to bottom, but when shift
664                  * selecting downward (extending the list) or multi-selecting using
665                  * the VO keys control+option+command+return the active descendant
666                  * is not at the top of the list in the shift select down case and
667                  * may not be in the multi select case.
668                  */
669                 if (whichChildren == JAVA_AX_SELECTED_CHILDREN) {
670                     if (!childrenAndRoles.isEmpty()) {
671                         AccessibleContext activeDescendantAC =
672                             CAccessible.getActiveDescendant(a);
673                         if (activeDescendantAC != null) {
674                             String activeDescendantName =
675                                 activeDescendantAC.getAccessibleName();
676                             AccessibleRole activeDescendantRole =
677                                 activeDescendantAC.getAccessibleRole();
678                             // Move active descendant to front of list.
679                             // List contains pairs of each selected item's
680                             // Accessible and AccessibleRole.
681                             ArrayList<Object> newArray  = new ArrayList<Object>();
682                             int count = childrenAndRoles.size();
683                             Accessible currentAccessible = null;
684                             AccessibleContext currentAC = null;
685                             String currentName = null;
686                             AccessibleRole currentRole = null;
687                             for (int i = 0; i < count; i+=2) {
688                                 // Is this the active descendant?
689                                 currentAccessible = (Accessible)childrenAndRoles.get(i);
690                                 currentAC = currentAccessible.getAccessibleContext();
691                                 currentName = currentAC.getAccessibleName();
692                                 currentRole = (AccessibleRole)childrenAndRoles.get(i+1);
693                                 if (currentName != null && currentName.equals(activeDescendantName) &&
694                                      currentRole.equals(activeDescendantRole) ) {
695                                     newArray.add(0, currentAccessible);
696                                     newArray.add(1, currentRole);
697                                 } else {
698                                     newArray.add(currentAccessible);
699                                     newArray.add(currentRole);
700                                 }
701                             }
702                             childrenAndRoles = newArray;
703                         }
704                     }
705                 }
706 
707                 if ((whichChildren < 0) || (whichChildren * 2 >= childrenAndRoles.size())) {
708                     return childrenAndRoles.toArray();
709                 }
710 
711                 return new Object[] { childrenAndRoles.get(whichChildren * 2), childrenAndRoles.get((whichChildren * 2) + 1) };
712             }
713         }, c);
714     }
715 
716     private static final int JAVA_AX_ROWS = 1;
717     private static final int JAVA_AX_COLS = 2;
718 
719     public static int getTableInfo(final Accessible a, final Component c,
720                                    final int info) {
721         if (a == null) return 0;
722         return invokeAndWait(() -> {
723             AccessibleContext ac = a.getAccessibleContext();
724             AccessibleTable table = ac.getAccessibleTable();
725             if (table != null) {
726                 if (info == JAVA_AX_COLS) {
727                     return table.getAccessibleColumnCount();
728                 } else if (info == JAVA_AX_ROWS) {
729                     return table.getAccessibleRowCount();
730                 }
731             }
732             return 0;
733         }, c);
734     }
735 
736     private static AccessibleRole getAccessibleRoleForLabel(JLabel l, AccessibleRole fallback) {
737         String text = l.getText();
738         if (text != null && text.length() > 0) {
739             return fallback;
740         }
741         Icon icon = l.getIcon();
742         if (icon != null) {
743             return AccessibleRole.ICON;
744         }
745         return fallback;
746     }
747 
748     private static AccessibleRole getAccessibleRole(Accessible a) {
749         AccessibleContext ac = a.getAccessibleContext();
750         AccessibleRole role = ac.getAccessibleRole();
751         Object component = CAccessible.getSwingAccessible(a);
752         if (role == null) return null;
753         String roleString = role.toString();
754         if ("label".equals(roleString) && component instanceof JLabel) {
755             return getAccessibleRoleForLabel((JLabel) component, role);
756         }
757         return role;
758     }
759 
760 
761     // Either gets the immediate children of a, or recursively gets all unignored children of a
762     private static void _addChildren(final Accessible a, final int whichChildren, final boolean allowIgnored, final ArrayList<Object> childrenAndRoles) {
763         if (a == null) return;
764 
765         final AccessibleContext ac = a.getAccessibleContext();
766         if (ac == null) return;
767 
768         final int numChildren = ac.getAccessibleChildrenCount();
769 
770         // each child takes up two entries in the array: itself, and its role
771         // so the array holds alternating Accessible and AccessibleRole objects
772         for (int i = 0; i < numChildren; i++) {
773             final Accessible child = ac.getAccessibleChild(i);
774             if (child == null) continue;
775 
776             final AccessibleContext context = child.getAccessibleContext();
777             if (context == null) continue;
778 
779             if (whichChildren == JAVA_AX_VISIBLE_CHILDREN) {
780                 AccessibleComponent acomp = context.getAccessibleComponent();
781                 if (acomp == null || !acomp.isVisible()) {
782                     continue;
783                 }
784             } else if (whichChildren == JAVA_AX_SELECTED_CHILDREN) {
785                 AccessibleSelection sel = ac.getAccessibleSelection();
786                 if (sel == null || !sel.isAccessibleChildSelected(i)) {
787                     continue;
788                 }
789             }
790 
791             if (!allowIgnored) {
792                 final AccessibleRole role = context.getAccessibleRole();
793                 if (role != null && ignoredRoles != null && ignoredRoles.contains(roleKey(role))) {
794                     // Get the child's unignored children.
795                     _addChildren(child, whichChildren, false, childrenAndRoles);
796                 } else {
797                     childrenAndRoles.add(child);
798                     childrenAndRoles.add(getAccessibleRole(child));
799                 }
800             } else {
801                 childrenAndRoles.add(child);
802                 childrenAndRoles.add(getAccessibleRole(child));
803             }
804 
805             // If there is an index, and we are beyond it, time to finish up
806             if ((whichChildren >= 0) && (childrenAndRoles.size() / 2) >= (whichChildren + 1)) {
807                 return;
808             }
809         }
810     }
811 
812     private static native String roleKey(AccessibleRole aRole);
813 
814     public static Object[] getChildren(final Accessible a, final Component c) {
815         if (a == null) return null;
816         return invokeAndWait(new Callable<Object[]>() {
817             public Object[] call() throws Exception {
818                 final AccessibleContext ac = a.getAccessibleContext();
819                 if (ac == null) return null;
820 
821                 final int numChildren = ac.getAccessibleChildrenCount();
822                 final Object[] children = new Object[numChildren];
823                 for (int i = 0; i < numChildren; i++) {
824                     children[i] = ac.getAccessibleChild(i);
825                 }
826                 return children;
827             }
828         }, c);
829     }
830 
831     /**
832      * @return AWTView ptr, a peer of the CPlatformView associated with the toplevel container of the Accessible, if any
833      */
834     private static long getAWTView(Accessible a) {
835         Accessible ax = CAccessible.getSwingAccessible(a);
836         if (!(ax instanceof Component)) return 0;
837 
838         return invokeAndWait(new Callable<Long>() {
839             public Long call() throws Exception {
840                 Component cont = (Component) ax;
841                 while (cont != null && !(cont instanceof Window)) {
842                     cont = cont.getParent();
843                 }
844                 if (cont != null) {
845                     LWWindowPeer peer = (LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer(cont);
846                     if (peer != null) {
847                         return ((CPlatformWindow) peer.getPlatformWindow()).getContentView().getAWTView();
848                     }
849                 }
850                 return 0L;
851             }
852         }, (Component)ax);
853     }
854 }
855