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