diff --git a/CHANGES.md b/CHANGES.md index cf3560a904..40442a5e41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Next Release (5.12.0) Features -------- +* [#1433](https://github.com/java-native-access/jna/pull/1433): Add `CFEqual`, `CFDictionaryRef.ByReference`, `CFStringRef.ByReference` to `c.s.j.p.mac.CoreFoundation` - [@shalupov](https://github.com/shalupov) Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java b/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java index 7301ae6cc9..f9bb3c10ba 100644 --- a/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java +++ b/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java @@ -377,6 +377,41 @@ public Pointer getBytePtr() { * A reference to an immutable {@code CFDictionary} object. */ class CFDictionaryRef extends CFTypeRef { + + /** + * Placeholder for a reference to a {@code CFDictionary} object. + */ + public static class ByReference extends PointerByReference { + public ByReference() { + this(null); + } + + public ByReference(CoreFoundation.CFDictionaryRef value) { + super(value != null ? value.getPointer() : null); + } + + @Override + public void setValue(Pointer value) { + if (value != null) { + CFTypeID typeId = INSTANCE.CFGetTypeID(value); + if (!DICTIONARY_TYPE_ID.equals(typeId)) { + throw new ClassCastException("Unable to cast to CFDictionary. Type ID: " + typeId); + } + } + + super.setValue(value); + } + + public CoreFoundation.CFDictionaryRef getDictionaryRefValue() { + Pointer value = super.getValue(); + if (value == null) { + return null; + } + + return new CoreFoundation.CFDictionaryRef(value); + } + } + public CFDictionaryRef() { super(); } @@ -460,6 +495,41 @@ public void setValue(PointerType key, PointerType value) { * the characteristics and behavior of {@code CFString} objects. */ class CFStringRef extends CFTypeRef { + + /** + * Placeholder for a reference to a {@code CFString} object. + */ + public static class ByReference extends PointerByReference { + public ByReference() { + this(null); + } + + public ByReference(CoreFoundation.CFStringRef value) { + super(value != null ? value.getPointer() : null); + } + + @Override + public void setValue(Pointer value) { + if (value != null) { + CFTypeID typeId = INSTANCE.CFGetTypeID(value); + if (!STRING_TYPE_ID.equals(typeId)) { + throw new ClassCastException("Unable to cast to CFString. Type ID: " + typeId); + } + } + + super.setValue(value); + } + + public CoreFoundation.CFStringRef getStringRefValue() { + Pointer value = super.getValue(); + if (value == null) { + return null; + } + + return new CoreFoundation.CFStringRef(value); + } + } + public CFStringRef() { super(); } @@ -974,6 +1044,14 @@ CFMutableDictionaryRef CFDictionaryCreateMutable(CFAllocatorRef alloc, CFIndex c */ CFIndex CFStringGetMaximumSizeForEncoding(CFIndex length, int encoding); + /** + * Determines whether two Core Foundation objects are considered equal. + * @param cf1 A CFType object to compare to cf2. + * @param cf2 A CFType object to compare to cf1. + * @return true if cf1 and cf2 are of the same type and considered equal, otherwise false. + */ + boolean CFEqual(CFTypeRef cf1, CFTypeRef cf2); + /** * Gets the default allocator object for the current thread. * @@ -1013,6 +1091,17 @@ CFMutableDictionaryRef CFDictionaryCreateMutable(CFAllocatorRef alloc, CFIndex c */ CFTypeID CFGetTypeID(CFTypeRef theObject); + /** + * Returns the type of a {@code CFType} object presented as a pointer. + * Allows to inspect object type without creating a {@link CFTypeRef} wrapper. + * + * @param theObject + * The pointer to {@code CFData} object to examine. + * @return A value of type {@link CFTypeID} that identifies the opaque type of + * {@code cf}. + */ + CFTypeID CFGetTypeID(Pointer theObject); + /** * @return The type identifier for the {@code CFArray} opaque type. */ diff --git a/contrib/platform/test/com/sun/jna/platform/mac/CoreFoundationTest.java b/contrib/platform/test/com/sun/jna/platform/mac/CoreFoundationTest.java index ee5b949d00..a046717a57 100644 --- a/contrib/platform/test/com/sun/jna/platform/mac/CoreFoundationTest.java +++ b/contrib/platform/test/com/sun/jna/platform/mac/CoreFoundationTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -38,6 +39,8 @@ import java.util.List; import java.util.Random; +import com.sun.jna.platform.mac.CoreFoundation.CFDictionaryRef; +import org.junit.Assert; import org.junit.Test; import com.sun.jna.Memory; @@ -248,4 +251,127 @@ public void testCFDictionary() { cfOne.release(); dict.release(); } + + @Test + public void testCFStringRefByReference() { + CFStringRef key = CFStringRef.createCFString("key"); + CFStringRef value = CFStringRef.createCFString("value"); + + CFMutableDictionaryRef dict = CF.CFDictionaryCreateMutable(null, new CFIndex(2), null, null); + dict.setValue(key, value); + + // test getStringRefValue() + CFStringRef.ByReference byRef = new CFStringRef.ByReference(); + assertTrue(dict.getValueIfPresent(key, byRef)); + assertTrue(CF.CFEqual(value, byRef.getStringRefValue())); + + // test constructor() + assertNull(new CFStringRef.ByReference().getValue()); + + // test constructor(null) + assertNull(new CFStringRef.ByReference(null).getValue()); + + // test setValue(null) + assertNotNull(byRef.getStringRefValue()); + byRef.setValue(null); + assertNull(byRef.getStringRefValue()); + + // test setValue(CFStringRef), getValue() + byRef.setValue(value.getPointer()); + assertTrue(CF.CFEqual(value, byRef.getStringRefValue())); + assertEquals(value.getPointer(), byRef.getValue()); + + // test setValue(CFDictionaryRef) + try { + byRef.setValue(dict.getPointer()); + Assert.fail("must fail"); + } catch (ClassCastException cce) { + // as it should be + } + + CF.CFRelease(key); + CF.CFRelease(value); + CF.CFRelease(dict); + } + + @Test + public void testCFDictionaryRefByReference() { + CFStringRef key = CFStringRef.createCFString("key"); + + CFMutableDictionaryRef value = CF.CFDictionaryCreateMutable(null, new CFIndex(2), null, null); + value.setValue(key, key); + + CFMutableDictionaryRef dict = CF.CFDictionaryCreateMutable(null, new CFIndex(2), null, null); + dict.setValue(key, value); + + // test getDictionaryRefValue() + CFDictionaryRef.ByReference byRef = new CFDictionaryRef.ByReference(); + assertTrue(dict.getValueIfPresent(key, byRef)); + assertTrue(CF.CFEqual(value, byRef.getDictionaryRefValue())); + + // test constructor() + assertNull(new CFDictionaryRef.ByReference().getValue()); + + // test constructor(null) + assertNull(new CFDictionaryRef.ByReference(null).getValue()); + + // test setValue(null) + assertNotNull(byRef.getDictionaryRefValue()); + byRef.setValue(null); + assertNull(byRef.getDictionaryRefValue()); + + // test setValue(CFDictionaryRef), getValue() + byRef.setValue(value.getPointer()); + assertTrue(CF.CFEqual(value, byRef.getDictionaryRefValue())); + assertEquals(value.getPointer(), byRef.getValue()); + + // test setValue(CFStringRef) + try { + byRef.setValue(key.getPointer()); + Assert.fail("must fail"); + } catch (ClassCastException cce) { + // as it should be + } + + CF.CFRelease(key); + CF.CFRelease(value); + CF.CFRelease(dict); + } + + @Test + public void testCFGetTypeID() { + CFStringRef s1 = CFStringRef.createCFString("s1"); + assertEquals(CF.CFStringGetTypeID(), CF.CFGetTypeID(s1.getPointer())); + assertEquals(CF.CFStringGetTypeID(), CF.CFGetTypeID(s1)); + s1.release(); + } + + @Test + public void testCFEqual() { + CFStringRef s1 = CFStringRef.createCFString("s1"); + CFStringRef s1_the_same = CFStringRef.createCFString("s1"); + CFStringRef s2 = CFStringRef.createCFString("s2"); + + assertTrue(CF.CFEqual(s1, s1)); + assertTrue(CF.CFEqual(s1, s1_the_same)); + + assertFalse(CF.CFEqual(s1, s2)); + + CFMutableDictionaryRef dict1 = CF.CFDictionaryCreateMutable(null, new CFIndex(2), null, null); + dict1.setValue(s1, s1); + CFMutableDictionaryRef dict2 = CF.CFDictionaryCreateMutable(null, new CFIndex(2), null, null); + dict2.setValue(s1, s1); + + assertNotEquals(dict1.getPointer(), dict2.getPointer()); + assertTrue(CF.CFEqual(dict1, dict2)); + + dict2.setValue(s1, s2); + assertFalse(CF.CFEqual(dict1, dict2)); + + CF.CFRelease(dict1); + CF.CFRelease(dict2); + CF.CFRelease(s1); + CF.CFRelease(s1_the_same); + CF.CFRelease(s2); + } }