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