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