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