use alloc::borrow::ToOwned;
use core::cmp;
use core::ffi::c_void;
use core::fmt;
use core::panic::RefUnwindSafe;
use core::panic::UnwindSafe;
use core::ptr::NonNull;
use core::slice;
use core::str;
use std::os::raw::c_char;
use objc2::ffi;
use objc2::rc::DefaultId;
use objc2::rc::{autoreleasepool, AutoreleasePool};
use objc2::rc::{Id, Shared};
use objc2::runtime::{Class, Object};
use objc2::{msg_send, msg_send_bool};
use crate::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NSObject};
#[cfg(feature = "apple")]
const UTF8_ENCODING: usize = 4;
#[cfg(feature = "gnustep-1-7")]
const UTF8_ENCODING: i32 = 4;
#[allow(unused)]
#[allow(non_upper_case_globals)]
const NSNotFound: ffi::NSInteger = ffi::NSIntegerMax;
extern_class! {
#[derive(PartialEq, Eq, Hash)]
unsafe pub struct NSString: NSObject;
}
unsafe impl Sync for NSString {}
unsafe impl Send for NSString {}
impl UnwindSafe for NSString {}
impl RefUnwindSafe for NSString {}
impl NSString {
unsafe_def_fn! {
pub fn new -> Shared;
}
#[doc(alias = "lengthOfBytesUsingEncoding")]
#[doc(alias = "lengthOfBytesUsingEncoding:")]
pub fn len(&self) -> usize {
unsafe { msg_send![self, lengthOfBytesUsingEncoding: UTF8_ENCODING] }
}
#[doc(alias = "length")]
fn len_utf16(&self) -> usize {
unsafe { msg_send![self, length] }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "CFStringGetCStringPtr")]
#[allow(unused)]
fn as_str_wip(&self) -> Option<&str> {
type CFStringEncoding = u32;
#[allow(non_upper_case_globals)]
const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
extern "C" {
fn CFStringGetCStringPtr(s: &NSString, encoding: CFStringEncoding) -> *const c_char;
}
let bytes = unsafe { CFStringGetCStringPtr(self, kCFStringEncodingUTF8) };
NonNull::new(bytes as *mut u8).map(|bytes| {
let len = self.len();
let bytes: &[u8] = unsafe { slice::from_raw_parts(bytes.as_ptr(), len) };
str::from_utf8(bytes).unwrap()
})
}
#[allow(unused)]
fn as_utf16(&self) -> Option<&[u16]> {
extern "C" {
fn CFStringGetCharactersPtr(s: &NSString) -> *const u16;
}
let ptr = unsafe { CFStringGetCharactersPtr(self) };
NonNull::new(ptr as *mut u16)
.map(|ptr| unsafe { slice::from_raw_parts(ptr.as_ptr(), self.len_utf16()) })
}
#[doc(alias = "UTF8String")]
pub fn as_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: &'p AutoreleasePool) -> &'r str {
pool.__verify_is_inner();
let bytes: *const c_char = unsafe { msg_send![self, UTF8String] };
let bytes: *const u8 = bytes.cast();
let len = self.len();
let bytes: &'r [u8] = unsafe { slice::from_raw_parts(bytes, len) };
str::from_utf8(bytes).unwrap()
}
#[doc(alias = "initWithBytes")]
#[doc(alias = "initWithBytes:length:encoding:")]
#[allow(clippy::should_implement_trait)] pub fn from_str(string: &str) -> Id<Self, Shared> {
unsafe {
let obj = from_str(Self::class(), string);
Id::new(obj.cast()).unwrap()
}
}
#[doc(alias = "hasPrefix")]
#[doc(alias = "hasPrefix:")]
pub fn has_prefix(&self, prefix: &NSString) -> bool {
unsafe { msg_send_bool![self, hasPrefix: prefix] }
}
#[doc(alias = "hasSuffix")]
#[doc(alias = "hasSuffix:")]
pub fn has_suffix(&self, suffix: &NSString) -> bool {
unsafe { msg_send_bool![self, hasSuffix: suffix] }
}
}
pub(crate) fn from_str(cls: &Class, string: &str) -> *mut Object {
let bytes: *const c_void = string.as_ptr().cast();
unsafe {
let obj: *mut Object = msg_send![cls, alloc];
msg_send![
obj,
initWithBytes: bytes,
length: string.len(),
encoding: UTF8_ENCODING,
]
}
}
impl PartialOrd for NSString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NSString {
fn cmp(&self, other: &Self) -> cmp::Ordering {
let res: NSComparisonResult = unsafe { msg_send![self, compare: other] };
res.into()
}
}
impl DefaultId for NSString {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSString {
type Ownership = Shared;
type Output = NSString;
}
unsafe impl NSMutableCopying for NSString {
type Output = NSMutableString;
}
impl ToOwned for NSString {
type Owned = Id<NSString, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Display for NSString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
fmt::Display::fmt(&s, f)
}
}
impl fmt::Debug for NSString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
fmt::Debug::fmt(&s, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use core::ptr;
#[cfg(feature = "gnustep-1-7")]
#[test]
fn ensure_linkage() {
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
}
#[test]
fn test_equality() {
let s1 = NSString::from_str("abc");
let s2 = NSString::from_str("abc");
assert_eq!(s1, s1);
assert_eq!(s1, s2);
let s3 = NSString::from_str("def");
assert_ne!(s1, s3);
}
#[test]
fn test_debug_display() {
let s = "xyz123";
let obj = NSString::from_str(s);
assert_eq!(format!("{:?}", obj), format!("{:?}", s));
assert_eq!(format!("{}", obj), format!("{}", s));
}
#[test]
fn test_empty() {
let s1 = NSString::from_str("");
let s2 = NSString::new();
assert_eq!(s1.len(), 0);
assert_eq!(s2.len(), 0);
assert_eq!(s1, s2);
autoreleasepool(|pool| {
assert_eq!(s1.as_str(pool), "");
assert_eq!(s2.as_str(pool), "");
});
}
#[test]
fn test_utf8() {
let expected = "ประเทศไทย中华Việt Nam";
let s = NSString::from_str(expected);
assert_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_nul() {
let expected = "\0";
let s = NSString::from_str(expected);
assert_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_interior_nul() {
let expected = "Hello\0World";
let s = NSString::from_str(expected);
assert_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_copy() {
let s1 = NSString::from_str("abc");
let s2 = s1.copy();
assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
assert!(s2.is_kind_of(NSString::class()));
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
assert!(s3.is_kind_of(NSMutableString::class()));
}
#[test]
fn test_copy_nsstring_is_same() {
let string1 = NSString::from_str("Hello, world!");
let string2 = string1.copy();
assert!(
ptr::eq(&*string1, &*string2),
"Cloned NSString didn't have the same address"
);
}
#[test]
fn test_strips_first_leading_zero_width_no_break_space() {
let ns_string = NSString::from_str("\u{feff}");
let expected = "";
autoreleasepool(|pool| {
assert_eq!(ns_string.as_str(pool), expected);
});
assert_eq!(ns_string.len(), 0);
let s = "\u{feff}\u{feff}a\u{feff}";
#[cfg(feature = "apple")]
let expected = "\u{feff}a\u{feff}";
#[cfg(feature = "gnustep-1-7")]
let expected = "a\u{feff}";
let ns_string = NSString::from_str(s);
autoreleasepool(|pool| {
assert_eq!(ns_string.as_str(pool), expected);
});
assert_eq!(ns_string.len(), expected.len());
}
#[test]
fn test_hash() {
use core::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
let s1 = NSString::from_str("example string goes here");
let s2 = NSString::from_str("example string goes here");
let mut hashstate = DefaultHasher::new();
let mut hashstate2 = DefaultHasher::new();
s1.hash(&mut hashstate);
s2.hash(&mut hashstate2);
assert_eq!(hashstate.finish(), hashstate2.finish());
}
#[test]
fn test_prefix_suffix() {
let s = NSString::from_str("abcdef");
let prefix = NSString::from_str("abc");
let suffix = NSString::from_str("def");
assert!(s.has_prefix(&prefix));
assert!(s.has_suffix(&suffix));
assert!(!s.has_prefix(&suffix));
assert!(!s.has_suffix(&prefix));
}
#[test]
#[allow(clippy::nonminimal_bool)]
fn test_cmp() {
let s1 = NSString::from_str("aa");
assert!(s1 <= s1);
assert!(s1 >= s1);
let s2 = NSString::from_str("ab");
assert!(s1 < s2);
assert!(!(s1 > s2));
assert!(s1 <= s2);
assert!(!(s1 >= s2));
let s3 = NSString::from_str("ba");
assert!(s1 < s3);
assert!(!(s1 > s3));
assert!(s1 <= s3);
assert!(!(s1 >= s3));
assert!(s2 < s3);
assert!(!(s2 > s3));
assert!(s2 <= s3);
assert!(!(s2 >= s3));
let s = NSString::from_str("abc");
let shorter = NSString::from_str("a");
let longer = NSString::from_str("abcdef");
assert!(s > shorter);
assert!(s < longer);
}
}