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