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