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         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;
  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 
  94     private CAccessibility() {
  95         KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this);
  96     }
  97 
  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 
 115     private native void focusChanged();
 116 
 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 
 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 
 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 
 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 }