1 /*
   2  * Copyright (c) 2011, 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 // External Java Accessibility links:
  27 //
  28 // <http://java.sun.com/j2se/1.4.2/docs/guide/access/index.html>
  29 // <http://www-106.ibm.com/developerworks/library/j-access/?n-j-10172>
  30 // <http://archives.java.sun.com/archives/java-access.html> (Sun's mailing list for Java accessibility)
  31 
  32 #import "JavaComponentAccessibility.h"
  33 
  34 #import "sun_lwawt_macosx_CAccessibility.h"
  35 
  36 #import <AppKit/AppKit.h>
  37 
  38 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  39 #import <JavaRuntimeSupport/JavaRuntimeSupport.h>
  40 
  41 #import <dlfcn.h>
  42 
  43 #import "JavaAccessibilityAction.h"
  44 #import "JavaAccessibilityUtilities.h"
  45 #import "JavaTextAccessibility.h"
  46 #import "ThreadUtilities.h"
  47 #import "AWTView.h"
  48 
  49 
  50 // these constants are duplicated in CAccessibility.java
  51 #define JAVA_AX_ALL_CHILDREN (-1)
  52 #define JAVA_AX_SELECTED_CHILDREN (-2)
  53 #define JAVA_AX_VISIBLE_CHILDREN (-3)
  54 // If the value is >=0, it's an index
  55 
  56 static JNF_STATIC_MEMBER_CACHE(jm_getChildrenAndRoles, sjc_CAccessibility, "getChildrenAndRoles", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;IZ)[Ljava/lang/Object;");
  57 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleComponent, sjc_CAccessibility, "getAccessibleComponent", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleComponent;");
  58 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleValue, sjc_CAccessibility, "getAccessibleValue", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleValue;");
  59 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleName, sjc_CAccessibility, "getAccessibleName", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;");
  60 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleDescription, sjc_CAccessibility, "getAccessibleDescription", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;");
  61 static JNF_STATIC_MEMBER_CACHE(sjm_isFocusTraversable, sjc_CAccessibility, "isFocusTraversable", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Z");
  62 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleIndexInParent, sjc_CAccessibility, "getAccessibleIndexInParent", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)I");
  63 
  64 static JNF_CLASS_CACHE(sjc_CAccessible, "sun/lwawt/macosx/CAccessible");
  65 
  66 static JNF_MEMBER_CACHE(jf_ptr, sjc_CAccessible, "ptr", "J");
  67 static JNF_STATIC_MEMBER_CACHE(sjm_getCAccessible, sjc_CAccessible, "getCAccessible", "(Ljavax/accessibility/Accessible;)Lsun/lwawt/macosx/CAccessible;");
  68 
  69 
  70 static jobject sAccessibilityClass = NULL;
  71 
  72 // sAttributeNamesForRoleCache holds the names of the attributes to which each java
  73 // AccessibleRole responds (see AccessibleRole.java).
  74 // This cache is queried before attempting to access a given attribute for a particular role.
  75 static NSMutableDictionary *sAttributeNamesForRoleCache = nil;
  76 static NSObject *sAttributeNamesLOCK = nil;
  77 
  78 
  79 @interface TabGroupAccessibility : JavaComponentAccessibility {
  80     NSInteger _numTabs;
  81 }
  82 
  83 - (id)currentTabWithEnv:(JNIEnv *)env withAxContext:(jobject)axContext;
  84 - (NSArray *)tabControlsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored;
  85 - (NSArray *)contentsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored;
  86 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env;
  87 
  88 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount;
  89 - (NSArray *)accessibilityChildrenAttribute;
  90 - (id) accessibilityTabsAttribute;
  91 - (BOOL)accessibilityIsTabsAttributeSettable;
  92 - (NSArray *)accessibilityContentsAttribute;
  93 - (BOOL)accessibilityIsContentsAttributeSettable;
  94 - (id) accessibilityValueAttribute;
  95 
  96 @end
  97 
  98 
  99 @interface TabGroupControlAccessibility : JavaComponentAccessibility {
 100     jobject fTabGroupAxContext;
 101 }
 102 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withTabGroup:(jobject)tabGroup withView:(NSView *)view withJavaRole:(NSString *)javaRole;
 103 - (jobject)tabGroup;
 104 - (void)getActionsWithEnv:(JNIEnv *)env;
 105 
 106 - (id)accessibilityValueAttribute;
 107 @end
 108 
 109 
 110 @interface ScrollAreaAccessibility : JavaComponentAccessibility {
 111 
 112 }
 113 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env;
 114 - (NSArray *)accessibilityContentsAttribute;
 115 - (BOOL)accessibilityIsContentsAttributeSettable;
 116 - (id)accessibilityVerticalScrollBarAttribute;
 117 - (BOOL)accessibilityIsVerticalScrollBarAttributeSettable;
 118 - (id)accessibilityHorizontalScrollBarAttribute;
 119 - (BOOL)accessibilityIsHorizontalScrollBarAttributeSettable;
 120 @end
 121 
 122 
 123 @implementation JavaComponentAccessibility
 124 
 125 - (NSString *)description
 126 {
 127     return [NSString stringWithFormat:@"%@(title:'%@', desc:'%@', value:'%@')", [self accessibilityRoleAttribute],
 128         [self accessibilityTitleAttribute], [self accessibilityRoleDescriptionAttribute], [self accessibilityValueAttribute]];
 129 }
 130 
 131 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withView:(NSView *)view withJavaRole:(NSString *)javaRole
 132 {
 133     self = [super init];
 134     if (self)
 135     {
 136         fParent = [parent retain];
 137         fView = [view retain];
 138         fJavaRole = [javaRole retain];
 139 
 140         fAccessible = JNFNewGlobalRef(env, accessible);
 141         fComponent = JNFNewGlobalRef(env, [(AWTView *)fView awtComponent:env]);
 142 
 143         fIndex = index;
 144 
 145         fActions = nil;
 146         fActionsLOCK = [[NSObject alloc] init];
 147     }
 148     return self;
 149 }
 150 
 151 - (void)unregisterFromCocoaAXSystem
 152 {
 153     AWT_ASSERT_APPKIT_THREAD;
 154     static dispatch_once_t initialize_unregisterUniqueId_once;
 155     static void (*unregisterUniqueId)(id);
 156     dispatch_once(&initialize_unregisterUniqueId_once, ^{
 157         void *jrsFwk = dlopen("/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaRuntimeSupport.framework/JavaRuntimeSupport", RTLD_LAZY | RTLD_LOCAL);
 158         unregisterUniqueId = dlsym(jrsFwk, "JRSAccessibilityUnregisterUniqueIdForUIElement");
 159     });
 160     if (unregisterUniqueId) unregisterUniqueId(self);
 161 }
 162 
 163 - (void)dealloc
 164 {
 165     [self unregisterFromCocoaAXSystem];
 166 
 167     JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
 168 
 169     JNFDeleteGlobalRef(env, fAccessible);
 170     fAccessible = NULL;
 171 
 172     JNFDeleteGlobalRef(env, fComponent);
 173     fComponent = NULL;
 174 
 175     [fParent release];
 176     fParent = nil;
 177 
 178     [fNSRole release];
 179     fNSRole = nil;
 180 
 181     [fJavaRole release];
 182     fJavaRole = nil;
 183 
 184     [fView release];
 185     fView = nil;
 186 
 187     [fActions release];
 188     fActions = nil;
 189 
 190     [fActionsLOCK release];
 191     fActionsLOCK = nil;
 192 
 193     [super dealloc];
 194 }
 195 - (void)finalize
 196 {
 197     [self unregisterFromCocoaAXSystem];
 198 
 199     JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
 200 
 201     JNFDeleteGlobalRef(env, fAccessible);
 202     fAccessible = NULL;
 203 
 204     JNFDeleteGlobalRef(env, fComponent);
 205     fComponent = NULL;
 206 
 207     [super finalize];
 208 }
 209 
 210 - (void)postValueChanged
 211 {
 212     AWT_ASSERT_APPKIT_THREAD;
 213     NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification);
 214 }
 215 
 216 - (void)postSelectionChanged
 217 {
 218     AWT_ASSERT_APPKIT_THREAD;
 219     NSAccessibilityPostNotification(self, NSAccessibilitySelectedTextChangedNotification);
 220 }
 221 
 222 - (BOOL)isEqual:(id)anObject
 223 {
 224     if (![anObject isKindOfClass:[self class]]) return NO;
 225     JavaComponentAccessibility *accessibility = (JavaComponentAccessibility *)anObject;
 226 
 227     JNIEnv* env = [ThreadUtilities getJNIEnv];
 228     return (*env)->IsSameObject(env, accessibility->fAccessible, fAccessible);
 229 }
 230 
 231 - (BOOL)isAccessibleWithEnv:(JNIEnv *)env forAccessible:(jobject)accessible
 232 {
 233     return (*env)->IsSameObject(env, fAccessible, accessible);
 234 }
 235 
 236 + (void)initialize
 237 {
 238     if (sAttributeNamesForRoleCache == nil) {
 239         sAttributeNamesLOCK = [[NSObject alloc] init];
 240         sAttributeNamesForRoleCache = [[NSMutableDictionary alloc] initWithCapacity:10];
 241     }
 242 
 243     if (sRoles == nil) {
 244         initializeRoles();
 245     }
 246 
 247     if (sAccessibilityClass == NULL) {
 248         JNF_STATIC_MEMBER_CACHE(jm_getAccessibility, sjc_CAccessibility, "getAccessibility", "([Ljava/lang/String;)Lsun/lwawt/macosx/CAccessibility;");
 249 
 250 #ifdef JAVA_AX_NO_IGNORES
 251         NSArray *ignoredKeys = [NSArray array];
 252 #else
 253         NSArray *ignoredKeys = [sRoles allKeysForObject:JavaAccessibilityIgnore];
 254 #endif
 255         jobjectArray result = NULL;
 256         jsize count = [ignoredKeys count];
 257 
 258         JNIEnv *env = [ThreadUtilities getJNIEnv];
 259         jclass clazz = (*env)->FindClass(env, "java/lang/String");
 260         result = (*env)->NewObjectArray(env, count, clazz, NULL); // AWT_THREADING Safe (known object)
 261         (*env)->DeleteLocalRef(env, clazz);
 262 
 263         NSUInteger i;
 264         for (i = 0; i < count; i++) {
 265             jstring jString = JNFNSToJavaString(env, [ignoredKeys objectAtIndex:i]);
 266             (*env)->SetObjectArrayElement(env, result, i, jString);
 267             (*env)->DeleteLocalRef(env, jString);
 268         }
 269 
 270         sAccessibilityClass = JNFCallStaticObjectMethod(env, jm_getAccessibility, result); // AWT_THREADING Safe (known object)
 271     }
 272 }
 273 
 274 + (void)postFocusChanged:(id)message
 275 {
 276     AWT_ASSERT_APPKIT_THREAD;
 277     NSAccessibilityPostNotification([NSApp accessibilityFocusedUIElement], NSAccessibilityFocusedUIElementChangedNotification);
 278 }
 279 
 280 + (jobject) getCAccessible:(jobject)jaccessible withEnv:(JNIEnv *)env {
 281     if (JNFIsInstanceOf(env, jaccessible, &sjc_CAccessible)) {
 282         return jaccessible;
 283     }
 284     else if (JNFIsInstanceOf(env, jaccessible, &sjc_Accessible)) {
 285         return JNFCallStaticObjectMethod(env, sjm_getCAccessible, jaccessible);
 286     }
 287     return NULL;
 288 }
 289 
 290 + (NSArray *)childrenOfParent:(JavaComponentAccessibility *)parent withEnv:(JNIEnv *)env withChildrenCode:(NSInteger)whichChildren allowIgnored:(BOOL)allowIgnored
 291 {
 292     jobjectArray jchildrenAndRoles = JNFCallStaticObjectMethod(env, jm_getChildrenAndRoles, parent->fAccessible, parent->fComponent, whichChildren, allowIgnored); // AWT_THREADING Safe (AWTRunLoop)
 293     if (jchildrenAndRoles == NULL) return nil;
 294 
 295     jsize arrayLen = (*env)->GetArrayLength(env, jchildrenAndRoles);
 296     NSMutableArray *children = [NSMutableArray arrayWithCapacity:arrayLen/2]; //childrenAndRoles array contains two elements (child, role) for each child
 297 
 298     NSUInteger i;
 299     NSUInteger childIndex = (whichChildren >= 0) ? whichChildren : 0; // if we're getting one particular child, make sure to set its index correctly
 300     for(i = 0; i < arrayLen; i+=2)
 301     {
 302         jobject /* Accessible */ jchild = (*env)->GetObjectArrayElement(env, jchildrenAndRoles, i);
 303         jobject /* String */ jchildJavaRole = (*env)->GetObjectArrayElement(env, jchildrenAndRoles, i+1);
 304 
 305         NSString *childJavaRole = nil;
 306         if (jchildJavaRole != NULL) {
 307             childJavaRole = JNFJavaToNSString(env, JNFGetObjectField(env, jchildJavaRole, sjf_key));
 308         }
 309 
 310         JavaComponentAccessibility *child = [self createWithParent:parent accessible:jchild role:childJavaRole index:childIndex withEnv:env withView:parent->fView];
 311         [children addObject:child];
 312         childIndex++;
 313     }
 314 
 315     return children;
 316 }
 317 
 318 + (JavaComponentAccessibility *)createWithAccessible:(jobject)jaccessible withEnv:(JNIEnv *)env withView:(NSView *)view
 319 {
 320     jobject jcomponent = [(AWTView *)view awtComponent:env];
 321     jint index = JNFCallStaticIntMethod(env, sjm_getAccessibleIndexInParent, jaccessible, jcomponent);
 322     NSString *javaRole = getJavaRole(env, jaccessible, jcomponent);
 323 
 324     return [self createWithAccessible:jaccessible role:javaRole index:index withEnv:env withView:view];
 325 }
 326 
 327 + (JavaComponentAccessibility *) createWithAccessible:(jobject)jaccessible role:(NSString *)javaRole index:(jint)index withEnv:(JNIEnv *)env withView:(NSView *)view
 328 {
 329     return [self createWithParent:nil accessible:jaccessible role:javaRole index:index withEnv:env withView:view];
 330 }
 331 
 332 + (JavaComponentAccessibility *) createWithParent:(JavaComponentAccessibility *)parent accessible:(jobject)jaccessible role:(NSString *)javaRole index:(jint)index withEnv:(JNIEnv *)env withView:(NSView *)view
 333 {
 334     // try to fetch the jCAX from Java, and return autoreleased
 335     jobject jCAX = [JavaComponentAccessibility getCAccessible:jaccessible withEnv:env];
 336     if (jCAX == NULL) return nil;
 337     JavaComponentAccessibility *value = (JavaComponentAccessibility *) jlong_to_ptr(JNFGetLongField(env, jCAX, jf_ptr));
 338     if (value != nil) return [[value retain] autorelease];
 339 
 340     // otherwise, create a new instance
 341     JavaComponentAccessibility *newChild = nil;
 342     if ([javaRole isEqualToString:@"pagetablist"]) {
 343         newChild = [TabGroupAccessibility alloc];
 344     } else if ([javaRole isEqualToString:@"scrollpane"]) {
 345         newChild = [ScrollAreaAccessibility alloc];
 346     } else {
 347         NSString *nsRole = [sRoles objectForKey:javaRole];
 348         if ([nsRole isEqualToString:NSAccessibilityStaticTextRole] || [nsRole isEqualToString:NSAccessibilityTextAreaRole] || [nsRole isEqualToString:NSAccessibilityTextFieldRole]) {
 349             newChild = [JavaTextAccessibility alloc];
 350         } else {
 351             newChild = [JavaComponentAccessibility alloc];
 352         }
 353     }
 354 
 355     // must init freshly -alloc'd object
 356     [newChild initWithParent:parent withEnv:env withAccessible:jCAX withIndex:index withView:view withJavaRole:javaRole]; // must init new instance
 357 
 358     // must hard CFRetain() pointer poked into Java object
 359     CFRetain(newChild);
 360     JNFSetLongField(env, jCAX, jf_ptr, ptr_to_jlong(newChild));
 361 
 362     // return autoreleased instance
 363     return [newChild autorelease];
 364 }
 365 
 366 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env
 367 {
 368     static JNF_STATIC_MEMBER_CACHE(jm_getInitialAttributeStates, sjc_CAccessibility, "getInitialAttributeStates", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)[Z");
 369 
 370     NSMutableArray *attributeNames = [NSMutableArray arrayWithCapacity:10];
 371     [attributeNames retain];
 372 
 373     // all elements respond to parent, role, role description, window, topLevelUIElement, help
 374     [attributeNames addObject:NSAccessibilityParentAttribute];
 375     [attributeNames addObject:NSAccessibilityRoleAttribute];
 376     [attributeNames addObject:NSAccessibilityRoleDescriptionAttribute];
 377     [attributeNames addObject:NSAccessibilityHelpAttribute];
 378 
 379     // cmcnote: AXMenu usually doesn't respond to window / topLevelUIElement. But menus within a Java app's window
 380     // probably should. Should we use some role other than AXMenu / AXMenuBar for Java menus?
 381     [attributeNames addObject:NSAccessibilityWindowAttribute];
 382     [attributeNames addObject:NSAccessibilityTopLevelUIElementAttribute];
 383 
 384     // set accessible subrole
 385     NSString *javaRole = [self javaRole];
 386     if (javaRole != nil && [javaRole isEqualToString:@"passwordtext"]) {
 387         //cmcnote: should turn this into a constant
 388         [attributeNames addObject:NSAccessibilitySubroleAttribute];
 389     }
 390 
 391     // Get all the other accessibility attributes states we need in one swell foop.
 392     // javaRole isn't pulled in because we need protected access to AccessibleRole.key
 393     jbooleanArray attributeStates = JNFCallStaticObjectMethod(env, jm_getInitialAttributeStates, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 394     if (attributeStates == NULL) return NULL;
 395     jboolean *attributeStatesArray = (*env)->GetBooleanArrayElements(env, attributeStates, 0);
 396 
 397     // if there's a component, it can be enabled and it has a size/position
 398     if (attributeStatesArray[0]) {
 399         [attributeNames addObject:NSAccessibilityEnabledAttribute];
 400         [attributeNames addObject:NSAccessibilitySizeAttribute];
 401         [attributeNames addObject:NSAccessibilityPositionAttribute];
 402     }
 403 
 404     // According to javadoc, a component that is focusable will return true from isFocusTraversable,
 405     // as well as having AccessibleState.FOCUSABLE in it's AccessibleStateSet.
 406     // We use the former heuristic; if the component focus-traversable, add a focused attribute
 407     // See also: accessibilityIsFocusedAttributeSettable
 408     if (attributeStatesArray[1])
 409     {
 410         [attributeNames addObject:NSAccessibilityFocusedAttribute];
 411     }
 412 
 413     // if it's a pagetab / radiobutton, it has a value but no min/max value.
 414     BOOL hasAxValue = attributeStatesArray[2];
 415     if ([javaRole isEqualToString:@"pagetab"] || [javaRole isEqualToString:@"radiobutton"]) {
 416         [attributeNames addObject:NSAccessibilityValueAttribute];
 417     } else {
 418         // if not a pagetab/radio button, and it has a value, it has a min/max/current value.
 419         if (hasAxValue) {
 420             // er, it has a min/max/current value if it's not a button.
 421             // See AppKit/NSButtonCellAccessibility.m
 422             if (![javaRole isEqualToString:@"pushbutton"]) {
 423                 //cmcnote: make this (and "passwordtext") constants instead of magic strings
 424                 [attributeNames addObject:NSAccessibilityMinValueAttribute];
 425                 [attributeNames addObject:NSAccessibilityMaxValueAttribute];
 426                 [attributeNames addObject:NSAccessibilityValueAttribute];
 427             }
 428         }
 429     }
 430 
 431     // does it have an orientation?
 432     if (attributeStatesArray[4]) {
 433         [attributeNames addObject:NSAccessibilityOrientationAttribute];
 434     }
 435 
 436     // name
 437     if (attributeStatesArray[5]) {
 438         [attributeNames addObject:NSAccessibilityTitleAttribute];
 439     }
 440 
 441     // children
 442     if (attributeStatesArray[6]) {
 443         [attributeNames addObject:NSAccessibilityChildrenAttribute];
 444 //        [attributeNames addObject:NSAccessibilitySelectedChildrenAttribute];
 445 //        [attributeNames addObject:NSAccessibilityVisibleChildrenAttribute];
 446                 //According to AXRoles.txt:
 447                 //VisibleChildren: radio group, list, row, table row subrole
 448                 //SelectedChildren: list
 449     }
 450 
 451     // Cleanup
 452     (*env)->ReleaseBooleanArrayElements(env, attributeStates, attributeStatesArray, JNI_ABORT);
 453 
 454     return attributeNames;
 455 }
 456 
 457 - (NSDictionary *)getActions:(JNIEnv *)env
 458 {
 459     @synchronized(fActionsLOCK) {
 460         if (fActions == nil) {
 461             fActions = [[NSMutableDictionary alloc] initWithCapacity:3];
 462             [self getActionsWithEnv:env];
 463         }
 464     }
 465 
 466     return fActions;
 467 }
 468 
 469 - (void)getActionsWithEnv:(JNIEnv *)env
 470 {
 471     static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleAction, sjc_CAccessibility, "getAccessibleAction", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleAction;");
 472 
 473     // On MacOSX, text doesn't have actions, in java it does.
 474     // cmcnote: NOT TRUE - Editable text has AXShowMenu. Textfields have AXConfirm. Static text has no actions.
 475     jobject axAction = JNFCallStaticObjectMethod(env, jm_getAccessibleAction, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 476     if (axAction != NULL) {
 477         //+++gdb NOTE: In MacOSX, there is just a single Action, not multiple. In java,
 478         //  the first one seems to be the most basic, so this will be used.
 479         // cmcnote: NOT TRUE - Sometimes there are multiple actions, eg sliders have AXDecrement AND AXIncrement (radr://3893192)
 480         JavaAxAction *action = [[JavaAxAction alloc] initWithEnv:env withAccessibleAction:axAction withIndex:0 withComponent:fComponent];
 481         [fActions setObject:action forKey:[self isMenu] ? NSAccessibilityPickAction : NSAccessibilityPressAction];
 482         [action release];
 483     }
 484 }
 485 
 486 - (jobject)axContextWithEnv:(JNIEnv *)env
 487 {
 488     return getAxContext(env, fAccessible, fComponent);
 489 }
 490 
 491 - (id)parent
 492 {
 493     static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleParent, sjc_CAccessibility, "getAccessibleParent", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/Accessible;");
 494 
 495     if(fParent == nil) {
 496         JNIEnv* env = [ThreadUtilities getJNIEnv];
 497 
 498         jobject jparent = JNFCallStaticObjectMethod(env, sjm_getAccessibleParent, fAccessible, fComponent);
 499 
 500         if (jparent == NULL) {
 501             fParent = fView;
 502         } else {
 503             fParent = [JavaComponentAccessibility createWithAccessible:jparent withEnv:env withView:fView];
 504             if (fParent == nil) {
 505                 fParent = fView;
 506             }
 507         }
 508         [fParent retain];
 509     }
 510     return fParent;
 511 }
 512 
 513 - (NSView *)view
 514 {
 515     return fView;
 516 }
 517 
 518 - (NSWindow *)window
 519 {
 520     return [[self view] window];
 521 }
 522 
 523 - (NSString *)javaRole
 524 {
 525     if(fJavaRole == nil) {
 526         JNIEnv* env = [ThreadUtilities getJNIEnv];
 527         fJavaRole = getJavaRole(env, fAccessible, fComponent);
 528         [fJavaRole retain];
 529     }
 530     return fJavaRole;
 531 }
 532 
 533 - (BOOL)isMenu
 534 {
 535     id role = [self accessibilityRoleAttribute];
 536     return [role isEqualToString:NSAccessibilityMenuBarRole] || [role isEqualToString:NSAccessibilityMenuRole] || [role isEqualToString:NSAccessibilityMenuItemRole];
 537 }
 538 
 539 - (BOOL)isSelected:(JNIEnv *)env
 540 {
 541     if (fIndex == -1) {
 542         return NO;
 543     }
 544 
 545     return isChildSelected(env, ((JavaComponentAccessibility *)[self parent])->fAccessible, fIndex, fComponent);
 546 }
 547 
 548 - (BOOL)isVisible:(JNIEnv *)env
 549 {
 550     if (fIndex == -1) {
 551         return NO;
 552     }
 553 
 554     return isShowing(env, [self axContextWithEnv:env], fComponent);
 555 }
 556 
 557 // the array of names for each role is cached in the sAttributeNamesForRoleCache
 558 - (NSArray *)accessibilityAttributeNames
 559 {
 560     JNIEnv* env = [ThreadUtilities getJNIEnv];
 561 
 562     @synchronized(sAttributeNamesLOCK) {
 563         NSString *javaRole = [self javaRole];
 564         NSArray *names = (NSArray *)[sAttributeNamesForRoleCache objectForKey:javaRole];
 565         if (names != nil) return names;
 566 
 567         names = [self initializeAttributeNamesWithEnv:env];
 568         if (names != nil) {
 569 #ifdef JAVA_AX_DEBUG
 570             NSLog(@"Initializing: %s for %@: %@", __FUNCTION__, javaRole, names);
 571 #endif
 572             [sAttributeNamesForRoleCache setObject:names forKey:javaRole];
 573             return names;
 574         }
 575     }
 576 
 577 #ifdef JAVA_AX_DEBUG
 578     NSLog(@"Warning in %s: could not find attribute names for role: %@", __FUNCTION__, [self javaRole]);
 579 #endif
 580 
 581     return nil;
 582 }
 583 
 584 // -- accessibility attributes --
 585 
 586 - (BOOL)accessibilityShouldUseUniqueId {
 587     return YES;
 588 }
 589 
 590 - (BOOL)accessibilitySupportsOverriddenAttributes {
 591     return YES;
 592 }
 593 
 594 
 595 // generic getters & setters
 596 // cmcnote: it would make more sense if these generic getters/setters were in JavaAccessibilityUtilities
 597 - (id)accessibilityAttributeValue:(NSString *)attribute
 598 {
 599     AWT_ASSERT_APPKIT_THREAD;
 600 
 601     // turns attribute "NSAccessibilityEnabledAttribute" into getter "accessibilityEnabledAttribute",
 602     // calls getter on self
 603     return JavaAccessibilityAttributeValue(self, attribute);
 604 }
 605 
 606 - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute
 607 {
 608     AWT_ASSERT_APPKIT_THREAD;
 609 
 610     // turns attribute "NSAccessibilityParentAttribute" into selector "accessibilityIsParentAttributeSettable",
 611     // calls selector on self
 612     return JavaAccessibilityIsAttributeSettable(self, attribute);
 613 }
 614 
 615 - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
 616 {
 617     AWT_ASSERT_APPKIT_THREAD;
 618 
 619     if ([self accessibilityIsAttributeSettable:attribute]) {
 620         // turns attribute "NSAccessibilityFocusAttribute" into setter "accessibilitySetFocusAttribute",
 621         // calls setter on self
 622         JavaAccessibilitySetAttributeValue(self, attribute, value);
 623     }
 624 }
 625 
 626 
 627 // specific attributes, in alphabetical order a la
 628 // http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Protocols/NSAccessibility.html
 629 
 630 // Elements that current element contains (NSArray)
 631 - (NSArray *)accessibilityChildrenAttribute
 632 {
 633     JNIEnv* env = [ThreadUtilities getJNIEnv];
 634     NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_VISIBLE_CHILDREN allowIgnored:NO];
 635 
 636     NSArray *value = nil;
 637     if ([children count] > 0) {
 638         value = children;
 639     }
 640 
 641     return value;
 642 }
 643 - (BOOL)accessibilityIsChildrenAttributeSettable
 644 {
 645     return NO;
 646 }
 647 
 648 - (NSUInteger)accessibilityIndexOfChild:(id)child
 649 {
 650     // Only special-casing for Lists, for now. This allows lists to be accessible, fixing radr://3856139 "JLists are broken".
 651     // Will probably want to special-case for Tables when we implement them (radr://3096643 "Accessibility: Table").
 652     // In AppKit, NSMatrixAccessibility (which uses NSAccessibilityListRole), NSTableRowAccessibility, and NSTableViewAccessibility are the
 653     // only ones that override the default implementation in NSAccessibility
 654     if (![[self accessibilityRoleAttribute] isEqualToString:NSAccessibilityListRole]) {
 655         return [super accessibilityIndexOfChild:child];
 656     }
 657 
 658     return JNFCallStaticIntMethod([ThreadUtilities getJNIEnv], sjm_getAccessibleIndexInParent, ((JavaComponentAccessibility *)child)->fAccessible, ((JavaComponentAccessibility *)child)->fComponent);
 659 }
 660 
 661 // Without this optimization accessibilityChildrenAttribute is called in order to get the entire array of children.
 662 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount {
 663     if ( (maxCount == 1) && [attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
 664         // Children codes for ALL, SELECTED, VISIBLE are <0. If the code is >=0, we treat it as an index to a single child
 665         NSArray *child = [JavaComponentAccessibility childrenOfParent:self withEnv:[ThreadUtilities getJNIEnv] withChildrenCode:(NSInteger)index allowIgnored:NO];
 666         if ([child count] > 0) {
 667             return child;
 668         }
 669     }
 670     return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount];
 671 }
 672 
 673 // Flag indicating enabled state of element (NSNumber)
 674 - (NSNumber *)accessibilityEnabledAttribute
 675 {
 676     static JNF_STATIC_MEMBER_CACHE(jm_isEnabled, sjc_CAccessibility, "isEnabled", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Z");
 677 
 678     JNIEnv* env = [ThreadUtilities getJNIEnv];
 679     NSNumber *value = [NSNumber numberWithBool:JNFCallStaticBooleanMethod(env, jm_isEnabled, fAccessible, fComponent)]; // AWT_THREADING Safe (AWTRunLoop)
 680     if (value == nil) {
 681         NSLog(@"WARNING: %s called on component that has no accessible component: %@", __FUNCTION__, self);
 682     }
 683     return value;
 684 }
 685 
 686 - (BOOL)accessibilityIsEnabledAttributeSettable
 687 {
 688     return NO;
 689 }
 690 
 691 // Flag indicating presence of keyboard focus (NSNumber)
 692 - (NSNumber *)accessibilityFocusedAttribute
 693 {
 694     if ([self accessibilityIsFocusedAttributeSettable]) {
 695         return [NSNumber numberWithBool:[self isEqual:[NSApp accessibilityFocusedUIElement]]];
 696     }
 697     return [NSNumber numberWithBool:NO];
 698 }
 699 
 700 - (BOOL)accessibilityIsFocusedAttributeSettable
 701 {
 702     JNIEnv* env = [ThreadUtilities getJNIEnv];
 703     // According to javadoc, a component that is focusable will return true from isFocusTraversable,
 704     // as well as having AccessibleState.FOCUSABLE in its AccessibleStateSet.
 705     // We use the former heuristic; if the component focus-traversable, add a focused attribute
 706     // See also initializeAttributeNamesWithEnv:
 707     if (JNFCallStaticBooleanMethod(env, sjm_isFocusTraversable, fAccessible, fComponent)) { // AWT_THREADING Safe (AWTRunLoop)
 708         return YES;
 709     }
 710 
 711     return NO;
 712 }
 713 
 714 - (void)accessibilitySetFocusedAttribute:(id)value
 715 {
 716     static JNF_STATIC_MEMBER_CACHE(jm_requestFocus, sjc_CAccessibility, "requestFocus", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)V");
 717 
 718     if ([(NSNumber*)value boolValue])
 719     {
 720         JNIEnv* env = [ThreadUtilities getJNIEnv];
 721         JNFCallStaticVoidMethod(env, jm_requestFocus, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 722     }
 723 }
 724 
 725 // Instance description, such as a help tag string (NSString)
 726 - (NSString *)accessibilityHelpAttribute
 727 {
 728     JNIEnv* env = [ThreadUtilities getJNIEnv];
 729 
 730     jobject val = JNFCallStaticObjectMethod(env, sjm_getAccessibleDescription, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 731     return JNFJavaToNSString(env, val);
 732 }
 733 
 734 - (BOOL)accessibilityIsHelpAttributeSettable
 735 {
 736     return NO;
 737 }
 738 
 739 // Element's maximum value (id)
 740 - (id)accessibilityMaxValueAttribute
 741 {
 742     static JNF_STATIC_MEMBER_CACHE(jm_getMaximumAccessibleValue, sjc_CAccessibility, "getMaximumAccessibleValue", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/Number;");
 743 
 744     JNIEnv* env = [ThreadUtilities getJNIEnv];
 745 
 746     jobject axValue = JNFCallStaticObjectMethod(env, jm_getMaximumAccessibleValue, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 747     return JNFJavaToNSNumber(env, axValue);
 748 }
 749 
 750 - (BOOL)accessibilityIsMaxValueAttributeSettable
 751 {
 752     return NO;
 753 }
 754 
 755 // Element's minimum value (id)
 756 - (id)accessibilityMinValueAttribute
 757 {
 758     static JNF_STATIC_MEMBER_CACHE(jm_getMinimumAccessibleValue, sjc_CAccessibility, "getMinimumAccessibleValue", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/Number;");
 759 
 760     JNIEnv* env = [ThreadUtilities getJNIEnv];
 761 
 762     jobject axValue = JNFCallStaticObjectMethod(env, jm_getMinimumAccessibleValue, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 763     return JNFJavaToNSNumber(env, axValue);
 764 }
 765 
 766 - (BOOL)accessibilityIsMinValueAttributeSettable
 767 {
 768     return NO;
 769 }
 770 
 771 - (id)accessibilityOrientationAttribute
 772 {
 773     JNIEnv* env = [ThreadUtilities getJNIEnv];
 774     jobject axContext = [self axContextWithEnv:env];
 775 
 776     // cmcnote - should batch these two calls into one that returns an array of two bools, one for vertical and one for horiz
 777     if (isVertical(env, axContext, fComponent)) {
 778         return NSAccessibilityVerticalOrientationValue;
 779     }
 780 
 781     if (isHorizontal(env, axContext, fComponent)) {
 782         return NSAccessibilityHorizontalOrientationValue;
 783     }
 784 
 785     return nil;
 786 }
 787 
 788 - (BOOL)accessibilityIsOrientationAttributeSettable
 789 {
 790     return NO;
 791 }
 792 
 793 // Element containing current element (id)
 794 - (id)accessibilityParentAttribute
 795 {
 796     return NSAccessibilityUnignoredAncestor([self parent]);
 797 }
 798 
 799 - (BOOL)accessibilityIsParentAttributeSettable
 800 {
 801     return NO;
 802 }
 803 
 804 // Screen position of element's lower-left corner in lower-left relative screen coordinates (NSValue)
 805 - (NSValue *)accessibilityPositionAttribute
 806 {
 807     JNIEnv* env = [ThreadUtilities getJNIEnv];
 808     jobject axComponent = JNFCallStaticObjectMethod(env, sjm_getAccessibleComponent, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 809 
 810     // NSAccessibility wants the bottom left point of the object in
 811     // bottom left based screen coords
 812 
 813     // Get the java screen coords, and make a NSPoint of the bottom left of the AxComponent.
 814     NSSize size = getAxComponentSize(env, axComponent, fComponent);
 815     NSPoint point = getAxComponentLocationOnScreen(env, axComponent, fComponent);
 816 
 817     point.y += size.height;
 818 
 819     // Now make it into Cocoa screen coords.
 820     point.y = [[[[self view] window] screen] frame].size.height - point.y;
 821 
 822     return [NSValue valueWithPoint:point];
 823 }
 824 
 825 - (BOOL)accessibilityIsPositionAttributeSettable
 826 {
 827     // In AppKit, position is only settable for a window (NSAccessibilityWindowRole). Our windows are taken care of natively, so we don't need to deal with this here
 828     // We *could* make use of Java's AccessibleComponent.setLocation() method. Investigate. radr://3953869
 829     return NO;
 830 }
 831 
 832 // Element type, such as NSAccessibilityRadioButtonRole (NSString). See the role table
 833 // at http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Protocols/NSAccessibility.html
 834 - (NSString *)accessibilityRoleAttribute
 835 {
 836     if (fNSRole == nil) {
 837         NSString *javaRole = [self javaRole];
 838         fNSRole = [sRoles objectForKey:javaRole];
 839         if (fNSRole == nil) {
 840             // this component has assigned itself a custom AccessibleRole not in the sRoles array
 841             fNSRole = javaRole;
 842         }
 843         [fNSRole retain];
 844     }
 845     return fNSRole;
 846 }
 847 - (BOOL)accessibilityIsRoleAttributeSettable
 848 {
 849     return NO;
 850 }
 851 
 852 // Localized, user-readable description of role, such as radio button (NSString)
 853 - (NSString *)accessibilityRoleDescriptionAttribute
 854 {
 855     // first ask AppKit for its accessible role description for a given AXRole
 856     NSString *value = NSAccessibilityRoleDescription([self accessibilityRoleAttribute], nil);
 857 
 858     if (value == nil) {
 859         // query java if necessary
 860         static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleRoleDisplayString, sjc_CAccessibility, "getAccessibleRoleDisplayString", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;");
 861 
 862         JNIEnv* env = [ThreadUtilities getJNIEnv];
 863 
 864         jobject axRole = JNFCallStaticObjectMethod(env, jm_getAccessibleRoleDisplayString, fAccessible, fComponent);
 865         if(axRole != NULL) {
 866             value = JNFJavaToNSString(env, axRole);
 867         } else {
 868             value = @"unknown";
 869         }
 870     }
 871 
 872     return value;
 873 }
 874 
 875 - (BOOL)accessibilityIsRoleDescriptionAttributeSettable
 876 {
 877     return NO;
 878 }
 879 
 880 // Currently selected children (NSArray)
 881 - (NSArray *)accessibilitySelectedChildrenAttribute
 882 {
 883     JNIEnv* env = [ThreadUtilities getJNIEnv];
 884     NSArray *selectedChildren = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_SELECTED_CHILDREN allowIgnored:NO];
 885     if ([selectedChildren count] > 0) {
 886         return selectedChildren;
 887     }
 888 
 889     return nil;
 890 }
 891 
 892 - (BOOL)accessibilityIsSelectedChildrenAttributeSettable
 893 {
 894     return NO; // cmcnote: actually it should be. so need to write accessibilitySetSelectedChildrenAttribute also
 895 }
 896 
 897 // Element size (NSValue)
 898 - (NSValue *)accessibilitySizeAttribute {
 899     JNIEnv* env = [ThreadUtilities getJNIEnv];
 900     jobject axComponent = JNFCallStaticObjectMethod(env, sjm_getAccessibleComponent, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 901     return [NSValue valueWithSize:getAxComponentSize(env, axComponent, fComponent)];
 902 }
 903 
 904 - (BOOL)accessibilityIsSizeAttributeSettable
 905 {
 906     // SIZE is settable in windows if [self styleMask] & NSResizableWindowMask - but windows are heavyweight so we're ok here
 907     // SIZE is settable in columns if [[self tableValue] allowsColumnResizing - haven't dealt with columns yet
 908     return NO;
 909 }
 910 
 911 // Element subrole type, such as NSAccessibilityTableRowSubrole (NSString). See the subrole attribute table at
 912 // http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Protocols/NSAccessibility.html
 913 - (NSString *)accessibilitySubroleAttribute
 914 {
 915     NSString *value = nil;
 916     if ([[self javaRole] isEqualToString:@"passwordtext"])
 917     {
 918         value = NSAccessibilitySecureTextFieldSubrole;
 919     }
 920     /*
 921     // other subroles. TableRow and OutlineRow may be relevant to us
 922      NSAccessibilityCloseButtonSubrole // no, heavyweight window takes care of this
 923      NSAccessibilityMinimizeButtonSubrole // "
 924      NSAccessibilityOutlineRowSubrole    // maybe?
 925      NSAccessibilitySecureTextFieldSubrole // currently used
 926      NSAccessibilityTableRowSubrole        // maybe?
 927      NSAccessibilityToolbarButtonSubrole // maybe?
 928      NSAccessibilityUnknownSubrole
 929      NSAccessibilityZoomButtonSubrole    // no, heavyweight window takes care of this
 930      NSAccessibilityStandardWindowSubrole// no, heavyweight window takes care of this
 931      NSAccessibilityDialogSubrole        // maybe?
 932      NSAccessibilitySystemDialogSubrole    // no
 933      NSAccessibilityFloatingWindowSubrole // in 1.5 if we implement these, heavyweight will take care of them anyway
 934      NSAccessibilitySystemFloatingWindowSubrole
 935      NSAccessibilityIncrementArrowSubrole  // no
 936      NSAccessibilityDecrementArrowSubrole  // no
 937      NSAccessibilityIncrementPageSubrole   // no
 938      NSAccessibilityDecrementPageSubrole   // no
 939      NSAccessibilitySearchFieldSubrole    //no
 940      */
 941     return value;
 942 }
 943 
 944 - (BOOL)accessibilityIsSubroleAttributeSettable
 945 {
 946     return NO;
 947 }
 948 
 949 // Title of element, such as button text (NSString)
 950 - (NSString *)accessibilityTitleAttribute
 951 {
 952     // Return empty string for labels, since their value and tile end up being the same thing and this leads to repeated text.
 953     if ([[self accessibilityRoleAttribute] isEqualToString:NSAccessibilityStaticTextRole]) {
 954         return @"";
 955     }
 956 
 957     JNIEnv* env = [ThreadUtilities getJNIEnv];
 958 
 959     jobject val = JNFCallStaticObjectMethod(env, sjm_getAccessibleName, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 960     return JNFJavaToNSString(env, val);
 961 }
 962 
 963 - (BOOL)accessibilityIsTitleAttributeSettable
 964 {
 965     return NO;
 966 }
 967 
 968 - (NSWindow *)accessibilityTopLevelUIElementAttribute
 969 {
 970     return [self window];
 971 }
 972 
 973 - (BOOL)accessibilityIsTopLevelUIElementAttributeSettable
 974 {
 975     return NO;
 976 }
 977 
 978 // Element's value (id)
 979 // note that the appKit meaning of "accessibilityValue" is different from the java
 980 // meaning of "accessibleValue", which is specific to numerical values
 981 // (http://java.sun.com/j2se/1.3/docs/api/javax/accessibility/AccessibleValue.html#setCurrentAccessibleValue(java.lang.Number))
 982 - (id)accessibilityValueAttribute
 983 {
 984     static JNF_STATIC_MEMBER_CACHE(jm_getCurrentAccessibleValue, sjc_CAccessibility, "getCurrentAccessibleValue", "(Ljavax/accessibility/AccessibleValue;Ljava/awt/Component;)Ljava/lang/Number;");
 985 
 986     JNIEnv* env = [ThreadUtilities getJNIEnv];
 987 
 988     // ask Java for the component's accessibleValue. In java, the "accessibleValue" just means a numerical value
 989     // a text value is taken care of in JavaTextAccessibility
 990 
 991     // cmcnote should coalesce these calls into one java call
 992     jobject axValue = JNFCallStaticObjectMethod(env, sjm_getAccessibleValue, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
 993     return JNFJavaToNSNumber(env, JNFCallStaticObjectMethod(env, jm_getCurrentAccessibleValue, axValue, fComponent)); // AWT_THREADING Safe (AWTRunLoop)
 994 }
 995 
 996 - (BOOL)accessibilityIsValueAttributeSettable
 997 {
 998     // according ot AppKit sources, in general the value attribute is not settable, except in the cases
 999     // of an NSScroller, an NSSplitView, and text that's both enabled & editable
1000     BOOL isSettable = NO;
1001     NSString *role = [self accessibilityRoleAttribute];
1002 
1003     if ([role isEqualToString:NSAccessibilityScrollBarRole] || // according to NSScrollerAccessibility
1004         [role isEqualToString:NSAccessibilitySplitGroupRole] ) // according to NSSplitViewAccessibility
1005     {
1006         isSettable = YES;
1007     }
1008     return isSettable;
1009 }
1010 
1011 - (void)accessibilitySetValueAttribute:(id)value
1012 {
1013 #ifdef JAVA_AX_DEBUG
1014     NSLog(@"Not yet implemented: %s\n", __FUNCTION__); // radr://3954018
1015 #endif
1016 }
1017 
1018 
1019 // Child elements that are visible (NSArray)
1020 - (NSArray *)accessibilityVisibleChildrenAttribute
1021 {
1022     JNIEnv *env = [ThreadUtilities getJNIEnv];
1023     NSArray *visibleChildren = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_VISIBLE_CHILDREN allowIgnored:NO];
1024     if ([visibleChildren count] <= 0) return nil;
1025     return visibleChildren;
1026 }
1027 
1028 - (BOOL)accessibilityIsVisibleChildrenAttributeSettable
1029 {
1030     return NO;
1031 }
1032 
1033 // Window containing current element (id)
1034 - (id)accessibilityWindowAttribute
1035 {
1036     return [self window];
1037 }
1038 
1039 - (BOOL)accessibilityIsWindowAttributeSettable
1040 {
1041     return NO;
1042 }
1043 
1044 
1045 // -- accessibility actions --
1046 - (NSArray *)accessibilityActionNames
1047 {
1048     JNIEnv *env = [ThreadUtilities getJNIEnv];
1049     return [[self getActions:env] allKeys];
1050 }
1051 
1052 - (NSString *)accessibilityActionDescription:(NSString *)action
1053 {
1054     AWT_ASSERT_APPKIT_THREAD;
1055 
1056     JNIEnv *env = [ThreadUtilities getJNIEnv];
1057     return [(id <JavaAccessibilityAction>)[[self getActions:env] objectForKey:action] getDescription];
1058 }
1059 
1060 - (void)accessibilityPerformAction:(NSString *)action
1061 {
1062     AWT_ASSERT_APPKIT_THREAD;
1063 
1064     JNIEnv *env = [ThreadUtilities getJNIEnv];
1065     [(id <JavaAccessibilityAction>)[[self getActions:env] objectForKey:action] perform];
1066 }
1067 
1068 
1069 // -- misc accessibility --
1070 - (BOOL)accessibilityIsIgnored
1071 {
1072 #ifdef JAVA_AX_NO_IGNORES
1073     return NO;
1074 #else
1075     return [[self accessibilityRoleAttribute] isEqualToString:JavaAccessibilityIgnore];
1076 #endif /* JAVA_AX_NO_IGNORES */
1077 }
1078 
1079 - (id)accessibilityHitTest:(NSPoint)point withEnv:(JNIEnv *)env
1080 {
1081     static JNF_CLASS_CACHE(jc_Container, "java/awt/Container");
1082     static JNF_STATIC_MEMBER_CACHE(jm_accessibilityHitTest, sjc_CAccessibility, "accessibilityHitTest", "(Ljava/awt/Container;FF)Ljavax/accessibility/Accessible;");
1083 
1084     // Make it into java screen coords
1085     point.y = [[[[self view] window] screen] frame].size.height - point.y;
1086 
1087     jobject jparent = fComponent;
1088 
1089     id value = nil;
1090     if (JNFIsInstanceOf(env, jparent, &jc_Container)) {
1091         jobject jaccessible = JNFCallStaticObjectMethod(env, jm_accessibilityHitTest, jparent, (jfloat)point.x, (jfloat)point.y); // AWT_THREADING Safe (AWTRunLoop)
1092         value = [JavaComponentAccessibility createWithAccessible:jaccessible withEnv:env withView:fView];
1093     }
1094 
1095     if (value == nil) {
1096         value = self;
1097     }
1098 
1099     if ([value accessibilityIsIgnored]) {
1100         value = NSAccessibilityUnignoredAncestor(value);
1101     }
1102 
1103 #ifdef JAVA_AX_DEBUG
1104     NSLog(@"%s: %@", __FUNCTION__, value);
1105 #endif
1106     return value;
1107 }
1108 
1109 - (id)accessibilityFocusedUIElement
1110 {
1111     static JNF_STATIC_MEMBER_CACHE(jm_getFocusOwner, sjc_CAccessibility, "getFocusOwner", "(Ljava/awt/Component;)Ljavax/accessibility/Accessible;");
1112 
1113     JNIEnv *env = [ThreadUtilities getJNIEnv];
1114     id value = nil;
1115 
1116     // This code frequently gets called indirectly by Java when VoiceOver is active.
1117     // Basically, we just have to know when we going to be a bad state, and do something "special".
1118     // Note that while NSApplication isn't technically correct, we post a focus changed notification
1119     // (which will call this method, but with the correct codepath) shortly afterwards. See +postFocusChanged.
1120     if (sInPerformFromJava) {
1121         return [NSApplication sharedApplication];
1122     } else {
1123         jobject focused = JNFCallStaticObjectMethod(env, jm_getFocusOwner, fComponent); // AWT_THREADING Safe (AWTRunLoop)
1124         if (focused != NULL) {
1125             if (JNFIsInstanceOf(env, focused, &sjc_Accessible)) {
1126                 value = [JavaComponentAccessibility createWithAccessible:focused withEnv:env withView:fView];
1127             }
1128         }
1129     }
1130 
1131     if (value == nil) {
1132         value = self;
1133     }
1134 #ifdef JAVA_AX_DEBUG
1135     NSLog(@"%s: %@", __FUNCTION__, value);
1136 #endif
1137     return value;
1138 }
1139 
1140 @end
1141 
1142 /*
1143  * Class:     sun_lwawt_macosx_CAccessibility
1144  * Method:    focusChanged
1145  * Signature: ()V
1146  */
1147 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessibility_focusChanged
1148 (JNIEnv *env, jobject jthis)
1149 {
1150 
1151 JNF_COCOA_ENTER(env);
1152     [ThreadUtilities performOnMainThread:@selector(postFocusChanged:) onObject:[JavaComponentAccessibility class] withObject:nil waitUntilDone:NO awtMode:NO];
1153 JNF_COCOA_EXIT(env);
1154 }
1155 
1156 
1157 
1158 /*
1159  * Class:     sun_lwawt_macosx_CAccessible
1160  * Method:    valueChanged
1161  * Signature: (I)V
1162  */
1163 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_valueChanged
1164 (JNIEnv *env, jclass jklass, jlong element)
1165 {
1166 JNF_COCOA_ENTER(env);
1167     [ThreadUtilities performOnMainThread:@selector(postValueChanged) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO];
1168 JNF_COCOA_EXIT(env);
1169 }
1170 
1171 /*
1172  * Class:     sun_lwawt_macosx_CAccessible
1173  * Method:    selectionChanged
1174  * Signature: (I)V
1175  */
1176 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_selectionChanged
1177 (JNIEnv *env, jclass jklass, jlong element)
1178 {
1179 JNF_COCOA_ENTER(env);
1180     [ThreadUtilities performOnMainThread:@selector(postSelectionChanged) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO];
1181 JNF_COCOA_EXIT(env);
1182 }
1183 
1184 
1185 /*
1186  * Class:     sun_lwawt_macosx_CAccessible
1187  * Method:    unregisterFromCocoaAXSystem
1188  * Signature: (I)V
1189  */
1190 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_unregisterFromCocoaAXSystem
1191 (JNIEnv *env, jclass jklass, jlong element)
1192 {
1193 JNF_COCOA_ENTER(env);
1194     [ThreadUtilities performOnMainThread:@selector(unregisterFromCocoaAXSystem) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO];
1195 JNF_COCOA_EXIT(env);
1196 }
1197 
1198 @implementation TabGroupAccessibility
1199 
1200 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withView:(NSView *)view withJavaRole:(NSString *)javaRole
1201 {
1202     self = [super initWithParent:parent withEnv:env withAccessible:accessible withIndex:index withView:view withJavaRole:javaRole];
1203     if (self) {
1204         _numTabs = -1; //flag for uninitialized numTabs
1205     }
1206     return self;
1207 }
1208 
1209 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env
1210 {
1211     NSMutableArray *names = (NSMutableArray *)[super initializeAttributeNamesWithEnv:env];
1212 
1213     [names addObject:NSAccessibilityTabsAttribute];
1214     [names addObject:NSAccessibilityContentsAttribute];
1215     [names addObject:NSAccessibilityValueAttribute];
1216 
1217     return names;
1218 }
1219 
1220 - (id)currentTabWithEnv:(JNIEnv *)env withAxContext:(jobject)axContext
1221 {
1222     NSArray *tabs = [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO];
1223 
1224     // Looking at the JTabbedPane sources, there is always one AccessibleSelection.
1225     jobject selAccessible = getAxContextSelection(env, axContext, 0, fComponent);
1226     if (selAccessible == NULL) return nil;
1227 
1228     // Go through the tabs and find selAccessible
1229     _numTabs = [tabs count];
1230     JavaComponentAccessibility *aTab;
1231     NSUInteger i;
1232     for (i = 0; i < _numTabs; i++) {
1233         aTab = (JavaComponentAccessibility *)[tabs objectAtIndex:i];
1234         if ([aTab isAccessibleWithEnv:env forAccessible:selAccessible]) {
1235             return aTab;
1236         }
1237     }
1238 
1239     return nil;
1240 }
1241 
1242 - (NSArray *)tabControlsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored
1243 {
1244     jobjectArray jtabsAndRoles = JNFCallStaticObjectMethod(env, jm_getChildrenAndRoles, fAccessible, fComponent, whichTabs, allowIgnored); // AWT_THREADING Safe (AWTRunLoop)
1245     if(jtabsAndRoles == NULL) return nil;
1246 
1247     jsize arrayLen = (*env)->GetArrayLength(env, jtabsAndRoles);
1248     if (arrayLen == 0) return nil;
1249 
1250     NSMutableArray *tabs = [NSMutableArray arrayWithCapacity:(arrayLen/2)];
1251 
1252     // all of the tabs have the same role, so we can just find out what that is here and use it for all the tabs
1253     jobject jtabJavaRole = (*env)->GetObjectArrayElement(env, jtabsAndRoles, 1); // the array entries alternate between tab/role, starting with tab. so the first role is entry 1.
1254     if (jtabJavaRole == NULL) return nil;
1255 
1256     NSString *tabJavaRole = JNFJavaToNSString(env, JNFGetObjectField(env, jtabJavaRole, sjf_key));
1257 
1258     NSUInteger i;
1259     NSUInteger tabIndex = (whichTabs >= 0) ? whichTabs : 0; // if we're getting one particular child, make sure to set its index correctly
1260     for(i = 0; i < arrayLen; i+=2) {
1261         jobject jtab = (*env)->GetObjectArrayElement(env, jtabsAndRoles, i);
1262         JavaComponentAccessibility *tab = [[[TabGroupControlAccessibility alloc] initWithParent:self withEnv:env withAccessible:jtab withIndex:tabIndex withTabGroup:axContext withView:[self view] withJavaRole:tabJavaRole] autorelease];
1263         [tabs addObject:tab];
1264         tabIndex++;
1265     }
1266 
1267     return tabs;
1268 }
1269 
1270 - (NSArray *)contentsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored
1271 {
1272     // Contents are the children of the selected tab.
1273     id currentTab = [self currentTabWithEnv:env withAxContext:axContext];
1274     if (currentTab == nil) return nil;
1275 
1276     NSArray *contents = [JavaComponentAccessibility childrenOfParent:currentTab withEnv:env withChildrenCode:whichTabs allowIgnored:allowIgnored];
1277     if ([contents count] <= 0) return nil;
1278     return contents;
1279 }
1280 
1281 - (id) accessibilityTabsAttribute
1282 {
1283     JNIEnv *env = [ThreadUtilities getJNIEnv];
1284     jobject axContext = [self axContextWithEnv:env];
1285     return [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO];
1286 }
1287 
1288 - (BOOL)accessibilityIsTabsAttributeSettable
1289 {
1290     return NO; //cmcnote: not sure.
1291 }
1292 
1293 - (NSInteger)numTabs
1294 {
1295     if (_numTabs == -1) {
1296         _numTabs = [[self accessibilityTabsAttribute] count];
1297     }
1298     return _numTabs;
1299 }
1300 
1301 - (NSArray *) accessibilityContentsAttribute
1302 {
1303     JNIEnv *env = [ThreadUtilities getJNIEnv];
1304     jobject axContext = [self axContextWithEnv:env];
1305     return [self contentsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO];
1306 }
1307 
1308 - (BOOL)accessibilityIsContentsAttributeSettable
1309 {
1310     return NO;
1311 }
1312 
1313 // axValue is the currently selected tab
1314 -(id) accessibilityValueAttribute
1315 {
1316     JNIEnv *env = [ThreadUtilities getJNIEnv];
1317     jobject axContext = [self axContextWithEnv:env];
1318     return [self currentTabWithEnv:env withAxContext:axContext];
1319 }
1320 
1321 - (BOOL)accessibilityIsValueAttributeSettable
1322 {
1323     return YES;
1324 }
1325 
1326 - (void)accessibilitySetValueAttribute:(id)value //cmcnote: not certain this is ever actually called. investigate.
1327 {
1328     // set the current tab
1329     NSNumber *number = (NSNumber *)value;
1330     if (![number boolValue]) return;
1331 
1332     JNIEnv *env = [ThreadUtilities getJNIEnv];
1333     jobject axContext = [self axContextWithEnv:env];
1334     setAxContextSelection(env, axContext, fIndex, fComponent);
1335 }
1336 
1337 - (NSArray *)accessibilityChildrenAttribute
1338 {
1339     //children = AXTabs + AXContents
1340     NSArray *tabs = [self accessibilityTabsAttribute];
1341     NSArray *contents = [self accessibilityContentsAttribute];
1342 
1343     NSMutableArray *children = [NSMutableArray arrayWithCapacity:[tabs count] + [contents count]];
1344     [children addObjectsFromArray:tabs];
1345     [children addObjectsFromArray:contents];
1346 
1347     return (NSArray *)children;
1348 }
1349 
1350 // Without this optimization accessibilityChildrenAttribute is called in order to get the entire array of children.
1351 // See similar optimization in JavaComponentAccessibility. We have to extend the base implementation here, since
1352 // children of tabs are AXTabs + AXContents
1353 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount {
1354     NSArray *result = nil;
1355     if ( (maxCount == 1) && [attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
1356         // Children codes for ALL, SELECTED, VISIBLE are <0. If the code is >=0, we treat it as an index to a single child
1357         JNIEnv *env = [ThreadUtilities getJNIEnv];
1358         jobject axContext = [self axContextWithEnv:env];
1359 
1360         //children = AXTabs + AXContents
1361         NSArray *children = [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:index allowIgnored:NO]; // first look at the tabs
1362         if ([children count] > 0) {
1363             result = children;
1364          } else {
1365             children= [self contentsWithEnv:env withTabGroupAxContext:axContext withTabCode:(index-[self numTabs]) allowIgnored:NO];
1366             if ([children count] > 0) {
1367                 result = children;
1368             }
1369         }
1370     } else {
1371         result = [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount];
1372     }
1373     return result;
1374 }
1375 
1376 @end
1377 
1378 
1379 static BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component);
1380 
1381 @implementation TabGroupControlAccessibility
1382 
1383 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withTabGroup:(jobject)tabGroup withView:(NSView *)view withJavaRole:(NSString *)javaRole
1384 {
1385     self = [super initWithParent:parent withEnv:env withAccessible:accessible withIndex:index withView:view withJavaRole:javaRole];
1386     if (self) {
1387         if (tabGroup != NULL) {
1388             fTabGroupAxContext = JNFNewGlobalRef(env, tabGroup);
1389         } else {
1390             fTabGroupAxContext = NULL;
1391         }
1392     }
1393     return self;
1394 }
1395 
1396 - (void)dealloc
1397 {
1398     JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
1399 
1400     if (fTabGroupAxContext != NULL) {
1401         JNFDeleteGlobalRef(env, fTabGroupAxContext);
1402         fTabGroupAxContext = NULL;
1403     }
1404 
1405     [super dealloc];
1406 }
1407 
1408 - (void)finalize
1409 {
1410     JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
1411 
1412     if (fTabGroupAxContext != NULL) {
1413         JNFDeleteGlobalRef(env, fTabGroupAxContext);
1414         fTabGroupAxContext = NULL;
1415     }
1416 
1417     [super finalize];
1418 }
1419 
1420 - (id)accessibilityValueAttribute
1421 {
1422     JNIEnv *env = [ThreadUtilities getJNIEnv];
1423     jobject axContext = [self axContextWithEnv:env];
1424 
1425     // Returns the current selection of the page tab list
1426     return [NSNumber numberWithBool:ObjectEquals(env, axContext, getAxContextSelection(env, [self tabGroup], fIndex, fComponent), fComponent)];
1427 }
1428 
1429 - (void)getActionsWithEnv:(JNIEnv *)env
1430 {
1431     TabGroupAction *action = [[TabGroupAction alloc] initWithEnv:env withTabGroup:[self tabGroup] withIndex:fIndex withComponent:fComponent];
1432     [fActions setObject:action forKey:NSAccessibilityPressAction];
1433     [action release];
1434 }
1435 
1436 - (jobject)tabGroup
1437 {
1438     if (fTabGroupAxContext == NULL) {
1439         JNIEnv* env = [ThreadUtilities getJNIEnv];
1440         jobject tabGroupAxContext = [(JavaComponentAccessibility *)[self parent] axContextWithEnv:env];
1441         fTabGroupAxContext = JNFNewGlobalRef(env, tabGroupAxContext);
1442     }
1443     return fTabGroupAxContext;
1444 }
1445 
1446 @end
1447 
1448 
1449 @implementation ScrollAreaAccessibility
1450 
1451 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env
1452 {
1453     NSMutableArray *names = (NSMutableArray *)[super initializeAttributeNamesWithEnv:env];
1454 
1455     [names addObject:NSAccessibilityHorizontalScrollBarAttribute];
1456     [names addObject:NSAccessibilityVerticalScrollBarAttribute];
1457     [names addObject:NSAccessibilityContentsAttribute];
1458 
1459     return names;
1460 }
1461 
1462 - (id)accessibilityHorizontalScrollBarAttribute
1463 {
1464     JNIEnv *env = [ThreadUtilities getJNIEnv];
1465 
1466     NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES];
1467     if ([children count] <= 0) return nil;
1468 
1469     // The scroll bars are in the children.
1470     JavaComponentAccessibility *aElement;
1471     NSEnumerator *enumerator = [children objectEnumerator];
1472     while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) {
1473         if ([[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) {
1474             jobject elementAxContext = [aElement axContextWithEnv:env];
1475             if (isHorizontal(env, elementAxContext, fComponent)) {
1476                 return aElement;
1477             }
1478         }
1479     }
1480 
1481     return nil;
1482 }
1483 
1484 - (BOOL)accessibilityIsHorizontalScrollBarAttributeSettable
1485 {
1486     return NO;
1487 }
1488 
1489 - (id)accessibilityVerticalScrollBarAttribute
1490 {
1491     JNIEnv *env = [ThreadUtilities getJNIEnv];
1492 
1493     NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES];
1494     if ([children count] <= 0) return nil;
1495 
1496     // The scroll bars are in the children.
1497     NSEnumerator *enumerator = [children objectEnumerator];
1498     JavaComponentAccessibility *aElement;
1499     while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) {
1500         if ([[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) {
1501             jobject elementAxContext = [aElement axContextWithEnv:env];
1502             if (isVertical(env, elementAxContext, fComponent)) {
1503                 return aElement;
1504             }
1505         }
1506     }
1507 
1508     return nil;
1509 }
1510 
1511 - (BOOL)accessibilityIsVerticalScrollBarAttributeSettable
1512 {
1513     return NO;
1514 }
1515 
1516 - (NSArray *)accessibilityContentsAttribute
1517 {
1518     JNIEnv *env = [ThreadUtilities getJNIEnv];
1519     NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES];
1520 
1521     if ([children count] <= 0) return nil;
1522     NSArray *contents = [NSMutableArray arrayWithCapacity:[children count]];
1523 
1524     // The scroll bars are in the children. children less the scroll bars is the contents
1525     NSEnumerator *enumerator = [children objectEnumerator];
1526     JavaComponentAccessibility *aElement;
1527     while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) {
1528         if (![[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) {
1529             // no scroll bars in contents
1530             [(NSMutableArray *)contents addObject:aElement];
1531         }
1532     }
1533 
1534     return contents;
1535 }
1536 
1537 - (BOOL)accessibilityIsContentsAttributeSettable
1538 {
1539     return NO;
1540 }
1541 
1542 @end
1543 
1544 /*
1545  * Returns Object.equals for the two items
1546  * This may use LWCToolkit.invokeAndWait(); don't call while holding fLock
1547  * and try to pass a component so the event happens on the correct thread.
1548  */
1549 static JNF_CLASS_CACHE(sjc_Object, "java/lang/Object");
1550 static BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component)
1551 {
1552     static JNF_MEMBER_CACHE(jm_equals, sjc_Object, "equals", "(Ljava/lang/Object;)Z");
1553 
1554     if ((a == NULL) && (b == NULL)) return YES;
1555     if ((a == NULL) || (b == NULL)) return NO;
1556 
1557     if (pthread_main_np() != 0) {
1558         // If we are on the AppKit thread
1559         static JNF_CLASS_CACHE(sjc_LWCToolkit, "sun/lwawt/macosx/LWCToolkit");
1560         static JNF_STATIC_MEMBER_CACHE(jm_doEquals, sjc_LWCToolkit, "doEquals", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/awt/Component;)Z");
1561         return JNFCallStaticBooleanMethod(env, jm_doEquals, a, b, component); // AWT_THREADING Safe (AWTRunLoopMode)
1562     }
1563 
1564     return JNFCallBooleanMethod(env, a, jm_equals, b); // AWT_THREADING Safe (!appKit)
1565 }