(&mut self, f: F) -> bool
+ where
+ F: FnMut(Self::Item) -> bool,
+ {
+ self.inner.any(f)
+ }
+
+ #[inline]
+ fn find(&mut self, predicate: P) -> Option
+ where
+ P: FnMut(&Self::Item) -> bool,
+ {
+ self.inner.find(predicate)
+ }
+
+ #[inline]
+ fn position(&mut self, predicate: P) -> Option
+ where
+ P: FnMut(Self::Item) -> bool,
+ {
+ self.inner.position(predicate)
+ }
+
+ $(
+ #[inline]
+ fn rposition(&mut self, predicate: P) -> Option
+ where
+ P: FnMut(Self::Item) -> bool,
+ {
+ let _test: $double_ended = ();
+ self.inner.rposition(predicate)
+ }
+ )?
+ }
+ };
+
+ (DoubleEndedIterator for $ty:ident $(<$($lt:lifetime),+>)?) => {
+ impl$(<$($lt),+>)? DoubleEndedIterator for $ty$(<$($lt),+>)? {
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next_back()
+ }
+
+ #[inline]
+ fn nth_back(&mut self, n: usize) -> Option {
+ self.inner.nth_back(n)
+ }
+
+ #[inline]
+ fn rfind(&mut self, predicate: P) -> Option
+ where
+ P: FnMut(&Self::Item) -> bool,
+ {
+ self.inner.rfind(predicate)
+ }
+ }
+ };
+
+ (ExactSizeIterator for $ty:ident $(<$($lt:lifetime),+>)?) => {
+ impl$(<$($lt),+>)? ExactSizeIterator for $ty$(<$($lt),+>)? {
+ #[inline]
+ fn len(&self) -> usize {
+ self.inner.len()
+ }
+ }
+ };
+
+ (FusedIterator for $ty:ident $(<$($lt:lifetime),+>)?) => {
+ impl$(<$($lt),+>)? FusedIterator for $ty$(<$($lt),+>)? {}
+ };
+
+ (Iterator, DoubleEndedIterator, ExactSizeIterator, FusedIterator for $ty:ident $(<$($lt:lifetime),+>)? => $item:ty) => {
+ delegate!(Iterator for $ty$(<$($lt),+>)? => $item, DoubleEnded = ());
+ delegate!(DoubleEndedIterator for $ty$(<$($lt),+>)?);
+ delegate!(ExactSizeIterator for $ty$(<$($lt),+>)?);
+ delegate!(FusedIterator for $ty$(<$($lt),+>)?);
+ };
+}
+
+#[must_use]
+#[derive(Clone, Debug)]
+pub struct Bytes<'a> {
+ pub(crate) inner: Copied>,
+}
+delegate!(Iterator, DoubleEndedIterator, ExactSizeIterator, FusedIterator for Bytes<'a> => u8);
+
+#[derive(Clone, Debug)]
+#[must_use]
+pub struct EscapeDebug<'a> {
+ #[allow(clippy::type_complexity)]
+ pub(crate) inner: Chain<
+ Flatten>,
+ FlatMap, CharEscapeIter, fn(JavaCodePoint) -> CharEscapeIter>,
+ >,
+}
+delegate!(Iterator for EscapeDebug<'a> => char);
+delegate!(FusedIterator for EscapeDebug<'a>);
+impl<'a> Display for EscapeDebug<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ self.clone().try_for_each(|c| f.write_char(c))
+ }
+}
+
+#[derive(Clone, Debug)]
+#[must_use]
+pub struct EscapeDefault<'a> {
+ pub(crate) inner: FlatMap, CharEscapeIter, fn(JavaCodePoint) -> CharEscapeIter>,
+}
+delegate!(Iterator for EscapeDefault<'a> => char);
+delegate!(FusedIterator for EscapeDefault<'a>);
+impl<'a> Display for EscapeDefault<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ self.clone().try_for_each(|c| f.write_char(c))
+ }
+}
+
+#[derive(Clone, Debug)]
+#[must_use]
+pub struct EscapeUnicode<'a> {
+ pub(crate) inner: FlatMap, CharEscapeIter, fn(JavaCodePoint) -> CharEscapeIter>,
+}
+delegate!(Iterator for EscapeUnicode<'a> => char);
+delegate!(FusedIterator for EscapeUnicode<'a>);
+impl<'a> Display for EscapeUnicode<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ self.clone().try_for_each(|c| f.write_char(c))
+ }
+}
+
+#[derive(Clone, Debug)]
+#[must_use]
+pub struct Lines<'a> {
+ pub(crate) inner: Map, fn(&JavaStr) -> &JavaStr>,
+}
+delegate!(Iterator for Lines<'a> => &'a JavaStr);
+delegate!(DoubleEndedIterator for Lines<'a>);
+delegate!(FusedIterator for Lines<'a>);
+
+#[derive(Clone)]
+#[must_use]
+pub struct Chars<'a> {
+ pub(crate) inner: slice::Iter<'a, u8>,
+}
+
+impl<'a> Iterator for Chars<'a> {
+ type Item = JavaCodePoint;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ // SAFETY: `JavaStr` invariant says `self.inner` is a semi-valid UTF-8 string
+ // and the resulting `ch` is a valid Unicode Scalar Value or surrogate
+ // code point.
+ unsafe { next_code_point(&mut self.inner).map(|ch| JavaCodePoint::from_u32_unchecked(ch)) }
+ }
+
+ // TODO: std has an optimized count impl
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option) {
+ let len = self.inner.len();
+ // `(len + 3)` can't overflow, because we know that the `slice::Iter`
+ // belongs to a slice in memory which has a maximum length of
+ // `isize::MAX` (that's well below `usize::MAX`).
+ ((len + 3) / 4, Some(len))
+ }
+
+ #[inline]
+ fn last(mut self) -> Option {
+ // No need to go through the entire string.
+ self.next_back()
+ }
+}
+
+impl Debug for Chars<'_> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Chars(")?;
+ f.debug_list().entries(self.clone()).finish()?;
+ write!(f, ")")?;
+ Ok(())
+ }
+}
+
+impl<'a> DoubleEndedIterator for Chars<'a> {
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ // SAFETY: `JavaStr` invariant says `self.inner` is a semi-valid UTF-8 string
+ // and the resulting `ch` is a valid Unicode Scalar Value or surrogate
+ // code point.
+ unsafe {
+ next_code_point_reverse(&mut self.inner).map(|ch| JavaCodePoint::from_u32_unchecked(ch))
+ }
+ }
+}
+
+impl FusedIterator for Chars<'_> {}
+
+impl<'a> Chars<'a> {
+ #[inline]
+ #[must_use]
+ pub fn as_str(&self) -> &'a JavaStr {
+ // SAFETY: `Chars` is only made from a JavaStr, which guarantees the iter is
+ // semi-valid UTF-8.
+ unsafe { JavaStr::from_semi_utf8_unchecked(self.inner.as_slice()) }
+ }
+}
+
+#[derive(Clone, Debug)]
+#[must_use]
+pub struct CharIndices<'a> {
+ pub(crate) front_offset: usize,
+ pub(crate) inner: Chars<'a>,
+}
+
+impl<'a> Iterator for CharIndices<'a> {
+ type Item = (usize, JavaCodePoint);
+
+ #[inline]
+ fn next(&mut self) -> Option<(usize, JavaCodePoint)> {
+ let pre_len = self.inner.inner.len();
+ match self.inner.next() {
+ None => None,
+ Some(ch) => {
+ let index = self.front_offset;
+ let len = self.inner.inner.len();
+ self.front_offset += pre_len - len;
+ Some((index, ch))
+ }
+ }
+ }
+
+ #[inline]
+ fn count(self) -> usize {
+ self.inner.count()
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option) {
+ self.inner.size_hint()
+ }
+
+ #[inline]
+ fn last(mut self) -> Option<(usize, JavaCodePoint)> {
+ // No need to go through the entire string.
+ self.next_back()
+ }
+}
+
+impl<'a> DoubleEndedIterator for CharIndices<'a> {
+ #[inline]
+ fn next_back(&mut self) -> Option<(usize, JavaCodePoint)> {
+ self.inner.next_back().map(|ch| {
+ let index = self.front_offset + self.inner.inner.len();
+ (index, ch)
+ })
+ }
+}
+
+impl FusedIterator for CharIndices<'_> {}
+
+impl<'a> CharIndices<'a> {
+ #[inline]
+ #[must_use]
+ pub fn as_str(&self) -> &'a JavaStr {
+ self.inner.as_str()
+ }
+}
+
+#[must_use]
+#[derive(Debug, Clone)]
+pub struct Matches<'a, P> {
+ pub(crate) str: &'a JavaStr,
+ pub(crate) pat: P,
+}
+
+impl<'a, P> Iterator for Matches<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ if let Some((index, len)) = self.pat.find_in(self.str) {
+ // SAFETY: pattern returns valid indices
+ let ret = unsafe { self.str.get_unchecked(index..index + len) };
+ self.str = unsafe { self.str.get_unchecked(index + len..) };
+ Some(ret)
+ } else {
+ self.str = Default::default();
+ None
+ }
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for Matches<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ if let Some((index, len)) = self.pat.rfind_in(self.str) {
+ // SAFETY: pattern returns valid indices
+ let ret = unsafe { self.str.get_unchecked(index..index + len) };
+ self.str = unsafe { self.str.get_unchecked(..index) };
+ Some(ret)
+ } else {
+ self.str = Default::default();
+ None
+ }
+ }
+}
+
+#[must_use]
+#[derive(Clone, Debug)]
+pub struct RMatches<'a, P> {
+ pub(crate) inner: Matches<'a, P>,
+}
+
+impl<'a, P> Iterator for RMatches<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next_back()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for RMatches<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next()
+ }
+}
+
+#[must_use]
+#[derive(Clone, Debug)]
+pub struct MatchIndices<'a, P> {
+ pub(crate) str: &'a JavaStr,
+ pub(crate) start: usize,
+ pub(crate) pat: P,
+}
+
+impl<'a, P> Iterator for MatchIndices<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = (usize, &'a JavaStr);
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ if let Some((index, len)) = self.pat.find_in(self.str) {
+ let full_index = self.start + index;
+ self.start = full_index + len;
+ // SAFETY: pattern returns valid indices
+ let ret = unsafe { self.str.get_unchecked(index..index + len) };
+ self.str = unsafe { self.str.get_unchecked(index + len..) };
+ Some((full_index, ret))
+ } else {
+ self.start += self.str.len();
+ self.str = Default::default();
+ None
+ }
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for MatchIndices<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ if let Some((index, len)) = self.pat.rfind_in(self.str) {
+ // SAFETY: pattern returns valid indices
+ let ret = unsafe { self.str.get_unchecked(index..index + len) };
+ self.str = unsafe { self.str.get_unchecked(..index) };
+ Some((self.start + index, ret))
+ } else {
+ self.str = Default::default();
+ None
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct RMatchIndices<'a, P> {
+ pub(crate) inner: MatchIndices<'a, P>,
+}
+
+impl<'a, P> Iterator for RMatchIndices<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = (usize, &'a JavaStr);
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next_back()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for RMatchIndices<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next()
+ }
+}
+
+#[derive(Clone, Debug)]
+struct SplitHelper<'a, P> {
+ start: usize,
+ end: usize,
+ haystack: &'a JavaStr,
+ pat: P,
+ allow_trailing_empty: bool,
+ finished: bool,
+ had_empty_match: bool,
+}
+
+impl<'a, P> SplitHelper<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn new(haystack: &'a JavaStr, pat: P, allow_trailing_empty: bool) -> Self {
+ Self {
+ start: 0,
+ end: haystack.len(),
+ haystack,
+ pat,
+ allow_trailing_empty,
+ finished: false,
+ had_empty_match: false,
+ }
+ }
+
+ #[inline]
+ fn get_end(&mut self) -> Option<&'a JavaStr> {
+ if !self.finished {
+ self.finished = true;
+
+ if self.allow_trailing_empty || self.end - self.start > 0 {
+ // SAFETY: `self.start` and `self.end` always lie on unicode boundaries.
+ let string = unsafe { self.haystack.get_unchecked(self.start..self.end) };
+ return Some(string);
+ }
+ }
+
+ None
+ }
+
+ #[inline]
+ fn next_match(&mut self) -> Option<(usize, usize)> {
+ // SAFETY: `self.start` always lies on a unicode boundary.
+ let substr = unsafe { self.haystack.get_unchecked(self.start..) };
+
+ let result = if self.had_empty_match {
+ // if we had an empty match before, we are going to find the empty match again.
+ // don't do that, search from the next index along.
+
+ if substr.is_empty() {
+ None
+ } else {
+ // SAFETY: we can pop the string because we already checked if the string is
+ // empty above
+ let first_char_len = unsafe { substr.chars().next().unwrap_unchecked().len_utf8() };
+ let popped_str = unsafe { substr.get_unchecked(first_char_len..) };
+
+ self.pat
+ .find_in(popped_str)
+ .map(|(index, len)| (index + first_char_len + self.start, len))
+ }
+ } else {
+ self.pat
+ .find_in(substr)
+ .map(|(index, len)| (index + self.start, len))
+ };
+
+ self.had_empty_match = result.is_some_and(|(_, len)| len == 0);
+
+ result
+ }
+
+ #[inline]
+ fn next(&mut self) -> Option<&'a JavaStr> {
+ if self.finished {
+ return None;
+ }
+
+ match self.next_match() {
+ Some((index, len)) => unsafe {
+ // SAFETY: pattern guarantees valid indices
+ let elt = self.haystack.get_unchecked(self.start..index);
+ self.start = index + len;
+ Some(elt)
+ },
+ None => self.get_end(),
+ }
+ }
+
+ #[inline]
+ fn next_inclusive(&mut self) -> Option<&'a JavaStr> {
+ if self.finished {
+ return None;
+ }
+
+ match self.next_match() {
+ Some((index, len)) => unsafe {
+ // SAFETY: pattern guarantees valid indices
+ let elt = self.haystack.get_unchecked(self.start..index + len);
+ self.start = index + len;
+ Some(elt)
+ },
+ None => self.get_end(),
+ }
+ }
+
+ #[inline]
+ fn next_match_back(&mut self) -> Option<(usize, usize)> {
+ // SAFETY: `self.end` always lies on a unicode boundary.
+ let substr = unsafe { self.haystack.get_unchecked(..self.end) };
+
+ let result = if self.had_empty_match {
+ // if we had an empty match before, we are going to find the empty match again.
+ // don't do that, search from the next index along.
+
+ if substr.is_empty() {
+ None
+ } else {
+ // SAFETY: we can pop the string because we already checked if the string is
+ // empty above
+ let last_char_len =
+ unsafe { substr.chars().next_back().unwrap_unchecked().len_utf8() };
+ let popped_str = unsafe { substr.get_unchecked(..substr.len() - last_char_len) };
+
+ self.pat.rfind_in(popped_str)
+ }
+ } else {
+ self.pat.rfind_in(substr)
+ };
+
+ self.had_empty_match = result.is_some_and(|(_, len)| len == 0);
+
+ result
+ }
+
+ #[inline]
+ fn next_back(&mut self) -> Option<&'a JavaStr> {
+ if self.finished {
+ return None;
+ }
+
+ if !self.allow_trailing_empty {
+ self.allow_trailing_empty = true;
+ match self.next_back() {
+ Some(elt) if !elt.is_empty() => return Some(elt),
+ _ => {
+ if self.finished {
+ return None;
+ }
+ }
+ }
+ }
+
+ match self.next_match_back() {
+ Some((index, len)) => unsafe {
+ // SAFETY: pattern guarantees valid indices
+ let elt = self.haystack.get_unchecked(index + len..self.end);
+ self.end = index;
+ Some(elt)
+ },
+ None => unsafe {
+ // SAFETY: `self.start` and `self.end` always lie on unicode boundaries.
+ self.finished = true;
+ Some(self.haystack.get_unchecked(self.start..self.end))
+ },
+ }
+ }
+
+ #[inline]
+ fn next_back_inclusive(&mut self) -> Option<&'a JavaStr> {
+ if self.finished {
+ return None;
+ }
+
+ if !self.allow_trailing_empty {
+ self.allow_trailing_empty = true;
+ match self.next_back_inclusive() {
+ Some(elt) if !elt.is_empty() => return Some(elt),
+ _ => {
+ if self.finished {
+ return None;
+ }
+ }
+ }
+ }
+
+ match self.next_match_back() {
+ Some((index, len)) => unsafe {
+ // SAFETY: pattern guarantees valid indices
+ let elt = self.haystack.get_unchecked(index + len..self.end);
+ self.end = index + len;
+ Some(elt)
+ },
+ None => unsafe {
+ // SAFETY: `self.start` and `self.end` always lie on unicode boundaries.
+ self.finished = true;
+ Some(self.haystack.get_unchecked(self.start..self.end))
+ },
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Split<'a, P> {
+ inner: SplitHelper<'a, P>,
+}
+
+impl<'a, P> Split<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P) -> Self {
+ Split {
+ inner: SplitHelper::new(haystack, pat, true),
+ }
+ }
+}
+
+impl<'a, P> Iterator for Split<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for Split<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next_back()
+ }
+}
+
+impl<'a, P> FusedIterator for Split<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct RSplit<'a, P> {
+ inner: SplitHelper<'a, P>,
+}
+
+impl<'a, P> RSplit<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P) -> Self {
+ RSplit {
+ inner: SplitHelper::new(haystack, pat, true),
+ }
+ }
+}
+
+impl<'a, P> Iterator for RSplit<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next_back()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for RSplit<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next()
+ }
+}
+
+impl<'a, P> FusedIterator for RSplit<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct SplitTerminator<'a, P> {
+ inner: SplitHelper<'a, P>,
+}
+
+impl<'a, P> SplitTerminator<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P) -> Self {
+ SplitTerminator {
+ inner: SplitHelper::new(haystack, pat, false),
+ }
+ }
+}
+
+impl<'a, P> Iterator for SplitTerminator<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for SplitTerminator<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next_back()
+ }
+}
+
+impl<'a, P> FusedIterator for SplitTerminator<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct RSplitTerminator<'a, P> {
+ inner: SplitHelper<'a, P>,
+}
+
+impl<'a, P> RSplitTerminator<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P) -> Self {
+ RSplitTerminator {
+ inner: SplitHelper::new(haystack, pat, false),
+ }
+ }
+}
+
+impl<'a, P> Iterator for RSplitTerminator<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next_back()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for RSplitTerminator<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next()
+ }
+}
+
+impl<'a, P> FusedIterator for RSplitTerminator<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct SplitInclusive<'a, P> {
+ inner: SplitHelper<'a, P>,
+}
+
+impl<'a, P> SplitInclusive<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P) -> Self {
+ SplitInclusive {
+ inner: SplitHelper::new(haystack, pat, false),
+ }
+ }
+}
+
+impl<'a, P> Iterator for SplitInclusive<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.inner.next_inclusive()
+ }
+}
+
+impl<'a, P> DoubleEndedIterator for SplitInclusive<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.inner.next_back_inclusive()
+ }
+}
+
+impl<'a, P> FusedIterator for SplitInclusive<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct SplitN<'a, P> {
+ inner: SplitHelper<'a, P>,
+ count: usize,
+}
+
+impl<'a, P> SplitN<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P, count: usize) -> Self {
+ SplitN {
+ inner: SplitHelper::new(haystack, pat, true),
+ count,
+ }
+ }
+}
+
+impl<'a, P> Iterator for SplitN<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ match self.count {
+ 0 => None,
+ 1 => {
+ self.count = 0;
+ self.inner.get_end()
+ }
+ _ => {
+ self.count -= 1;
+ self.inner.next()
+ }
+ }
+ }
+}
+
+impl<'a, P> FusedIterator for SplitN<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct RSplitN<'a, P> {
+ inner: SplitHelper<'a, P>,
+ count: usize,
+}
+
+impl<'a, P> RSplitN<'a, P>
+where
+ P: JavaStrPattern,
+{
+ #[inline]
+ pub(crate) fn new(haystack: &'a JavaStr, pat: P, count: usize) -> Self {
+ RSplitN {
+ inner: SplitHelper::new(haystack, pat, true),
+ count,
+ }
+ }
+}
+
+impl<'a, P> Iterator for RSplitN<'a, P>
+where
+ P: JavaStrPattern,
+{
+ type Item = &'a JavaStr;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ match self.count {
+ 0 => None,
+ 1 => {
+ self.count = 0;
+ self.inner.get_end()
+ }
+ _ => {
+ self.count -= 1;
+ self.inner.next_back()
+ }
+ }
+ }
+}
+
+impl<'a, P> FusedIterator for RSplitN<'a, P> where P: JavaStrPattern {}
+
+#[derive(Clone, Debug)]
+pub struct SplitAsciiWhitespace<'a> {
+ #[allow(clippy::type_complexity)]
+ pub(crate) inner: Map<
+ Filter bool>, fn(&&[u8]) -> bool>,
+ fn(&[u8]) -> &JavaStr,
+ >,
+}
+delegate!(Iterator for SplitAsciiWhitespace<'a> => &'a JavaStr);
+delegate!(DoubleEndedIterator for SplitAsciiWhitespace<'a>);
+delegate!(FusedIterator for SplitAsciiWhitespace<'a>);
+
+#[derive(Clone, Debug)]
+pub struct SplitWhitespace<'a> {
+ #[allow(clippy::type_complexity)]
+ pub(crate) inner: Filter bool>, fn(&&JavaStr) -> bool>,
+}
+delegate!(Iterator for SplitWhitespace<'a> => &'a JavaStr);
+delegate!(DoubleEndedIterator for SplitWhitespace<'a>);
+delegate!(FusedIterator for SplitWhitespace<'a>);
diff --git a/crates/java_string/src/lib.rs b/crates/java_string/src/lib.rs
new file mode 100644
index 000000000..57f035944
--- /dev/null
+++ b/crates/java_string/src/lib.rs
@@ -0,0 +1,27 @@
+#![doc = include_str!("../README.md")]
+
+mod cesu8;
+mod char;
+mod error;
+mod iter;
+mod owned;
+mod pattern;
+#[cfg(feature = "serde")]
+mod serde;
+mod slice;
+pub(crate) mod validations;
+
+pub use cesu8::*;
+pub use char::*;
+pub use error::*;
+pub use iter::*;
+pub use owned::*;
+pub use pattern::*;
+pub use slice::*;
+
+#[macro_export]
+macro_rules! format_java {
+ ($($arg:tt)*) => {
+ $crate::JavaString::from(::std::format!($($arg)*))
+ }
+}
diff --git a/crates/java_string/src/owned.rs b/crates/java_string/src/owned.rs
new file mode 100644
index 000000000..e03f82a7d
--- /dev/null
+++ b/crates/java_string/src/owned.rs
@@ -0,0 +1,1401 @@
+use std::borrow::{Borrow, BorrowMut, Cow};
+use std::collections::{Bound, TryReserveError};
+use std::convert::Infallible;
+use std::fmt::{Debug, Display, Formatter, Write};
+use std::hash::{Hash, Hasher};
+use std::iter::FusedIterator;
+use std::ops::{
+ Add, AddAssign, Deref, DerefMut, Index, IndexMut, Range, RangeBounds, RangeFrom, RangeFull,
+ RangeInclusive, RangeTo, RangeToInclusive,
+};
+use std::rc::Rc;
+use std::str::FromStr;
+use std::sync::Arc;
+use std::{ptr, slice};
+
+use crate::validations::{
+ run_utf8_full_validation_from_semi, run_utf8_semi_validation, to_range_checked,
+};
+use crate::{Chars, FromUtf8Error, JavaCodePoint, JavaStr, Utf8Error};
+
+#[derive(Default, PartialEq, PartialOrd, Eq, Ord)]
+pub struct JavaString {
+ vec: Vec,
+}
+
+impl JavaString {
+ #[inline]
+ #[must_use]
+ pub const fn new() -> JavaString {
+ JavaString { vec: Vec::new() }
+ }
+
+ #[inline]
+ #[must_use]
+ pub fn with_capacity(capacity: usize) -> JavaString {
+ JavaString {
+ vec: Vec::with_capacity(capacity),
+ }
+ }
+
+ /// Converts `vec` to a `JavaString` if it is fully-valid UTF-8, i.e. UTF-8
+ /// without surrogate code points. See [String::from_utf8].
+ #[inline]
+ pub fn from_full_utf8(vec: Vec) -> Result {
+ match std::str::from_utf8(&vec) {
+ Ok(..) => Ok(JavaString { vec }),
+ Err(e) => Err(FromUtf8Error {
+ bytes: vec,
+ error: e.into(),
+ }),
+ }
+ }
+
+ /// Converts `vec` to a `JavaString` if it is semi-valid UTF-8, i.e. UTF-8
+ /// with surrogate code points.
+ ///
+ /// ```
+ /// # use java_string::{JavaCodePoint, JavaString};
+ ///
+ /// assert_eq!(
+ /// JavaString::from_semi_utf8(b"Hello World!".to_vec()).unwrap(),
+ /// "Hello World!"
+ /// );
+ /// assert_eq!(
+ /// JavaString::from_semi_utf8(vec![0xf0, 0x9f, 0x92, 0x96]).unwrap(),
+ /// "💖"
+ /// );
+ /// assert_eq!(
+ /// JavaString::from_semi_utf8(vec![0xed, 0xa0, 0x80]).unwrap(),
+ /// JavaString::from(JavaCodePoint::from_u32(0xd800).unwrap())
+ /// );
+ /// assert!(JavaString::from_semi_utf8(vec![0xed]).is_err());
+ /// ```
+ pub fn from_semi_utf8(vec: Vec) -> Result {
+ match run_utf8_semi_validation(&vec) {
+ Ok(..) => Ok(JavaString { vec }),
+ Err(err) => Err(FromUtf8Error {
+ bytes: vec,
+ error: err,
+ }),
+ }
+ }
+
+ /// Converts `v` to a `Cow`, replacing invalid semi-UTF-8 with the
+ /// replacement character �.
+ ///
+ /// ```
+ /// # use std::borrow::Cow;
+ /// # use java_string::{JavaStr, JavaString};
+ ///
+ /// let sparkle_heart = [0xf0, 0x9f, 0x92, 0x96];
+ /// let result = JavaString::from_semi_utf8_lossy(&sparkle_heart);
+ /// assert!(matches!(result, Cow::Borrowed(_)));
+ /// assert_eq!(result, JavaStr::from_str("💖"));
+ ///
+ /// let foobar_with_error = [b'f', b'o', b'o', 0xed, b'b', b'a', b'r'];
+ /// let result = JavaString::from_semi_utf8_lossy(&foobar_with_error);
+ /// assert!(matches!(result, Cow::Owned(_)));
+ /// assert_eq!(result, JavaStr::from_str("foo�bar"));
+ /// ```
+ #[must_use]
+ pub fn from_semi_utf8_lossy(v: &[u8]) -> Cow<'_, JavaStr> {
+ const REPLACEMENT: &str = "\u{FFFD}";
+
+ match run_utf8_semi_validation(v) {
+ Ok(()) => unsafe {
+ // SAFETY: validation succeeded
+ Cow::Borrowed(JavaStr::from_semi_utf8_unchecked(v))
+ },
+ Err(error) => {
+ let mut result = unsafe {
+ // SAFETY: validation succeeded up to this index
+ JavaString::from_semi_utf8_unchecked(
+ v.get_unchecked(..error.valid_up_to).to_vec(),
+ )
+ };
+ result.push_str(REPLACEMENT);
+ let mut index = error.valid_up_to + error.error_len.unwrap_or(1) as usize;
+ loop {
+ match run_utf8_semi_validation(&v[index..]) {
+ Ok(()) => {
+ unsafe {
+ // SAFETY: validation succeeded
+ result
+ .push_java_str(JavaStr::from_semi_utf8_unchecked(&v[index..]));
+ }
+ return Cow::Owned(result);
+ }
+ Err(error) => {
+ unsafe {
+ // SAFETY: validation succeeded up to this index
+ result.push_java_str(JavaStr::from_semi_utf8_unchecked(
+ v.get_unchecked(index..index + error.valid_up_to),
+ ));
+ }
+ result.push_str(REPLACEMENT);
+ index += error.valid_up_to + error.error_len.unwrap_or(1) as usize;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// # Safety
+ ///
+ /// The parameter must be in semi-valid UTF-8 format, that is, UTF-8 plus
+ /// surrogate code points.
+ #[inline]
+ #[must_use]
+ pub unsafe fn from_semi_utf8_unchecked(bytes: Vec) -> JavaString {
+ JavaString { vec: bytes }
+ }
+
+ /// See [String::into_bytes].
+ #[inline]
+ #[must_use]
+ pub fn into_bytes(self) -> Vec {
+ self.vec
+ }
+
+ /// See [String::as_str].
+ #[inline]
+ #[must_use]
+ pub fn as_java_str(&self) -> &JavaStr {
+ unsafe {
+ // SAFETY: this str has semi-valid UTF-8
+ JavaStr::from_semi_utf8_unchecked(&self.vec)
+ }
+ }
+
+ /// See [String::as_mut_str].
+ #[inline]
+ #[must_use]
+ pub fn as_mut_java_str(&mut self) -> &mut JavaStr {
+ unsafe {
+ // SAFETY: this str has semi-valid UTF-8
+ JavaStr::from_semi_utf8_unchecked_mut(&mut self.vec)
+ }
+ }
+
+ /// Tries to convert this `JavaString` to a `String`, returning an error if
+ /// it is not fully valid UTF-8, i.e. has no surrogate code points.
+ ///
+ /// ```
+ /// # use java_string::{JavaCodePoint, JavaString};
+ ///
+ /// assert_eq!(
+ /// JavaString::from("Hello World!").into_string().unwrap(),
+ /// "Hello World!"
+ /// );
+ /// assert_eq!(
+ /// JavaString::from("abc\0ℝ💣").into_string().unwrap(),
+ /// "abc\0ℝ💣"
+ /// );
+ ///
+ /// let string_with_error = JavaString::from("abc")
+ /// + JavaString::from(JavaCodePoint::from_u32(0xd800).unwrap()).as_java_str();
+ /// assert!(string_with_error.into_string().is_err());
+ /// ```
+ pub fn into_string(self) -> Result {
+ run_utf8_full_validation_from_semi(self.as_bytes()).map(|_| unsafe {
+ // SAFETY: validation succeeded
+ self.into_string_unchecked()
+ })
+ }
+
+ /// # Safety
+ ///
+ /// This string must be fully valid UTF-8, i.e. have no surrogate code
+ /// points.
+ #[inline]
+ #[must_use]
+ pub unsafe fn into_string_unchecked(self) -> String {
+ // SAFETY: preconditions checked by caller
+ String::from_utf8_unchecked(self.vec)
+ }
+
+ /// See [String::push_str].
+ #[inline]
+ pub fn push_java_str(&mut self, string: &JavaStr) {
+ self.vec.extend_from_slice(string.as_bytes())
+ }
+
+ /// See [String::push_str].
+ #[inline]
+ pub fn push_str(&mut self, string: &str) {
+ self.vec.extend_from_slice(string.as_bytes())
+ }
+
+ /// See [String::capacity].
+ #[inline]
+ #[must_use]
+ pub fn capacity(&self) -> usize {
+ self.vec.capacity()
+ }
+
+ /// See [String::reserve].
+ #[inline]
+ pub fn reserve(&mut self, additional: usize) {
+ self.vec.reserve(additional)
+ }
+
+ /// See [String::reserve_exact].
+ #[inline]
+ pub fn reserve_exact(&mut self, additional: usize) {
+ self.vec.reserve_exact(additional)
+ }
+
+ /// See [String::try_reserve].
+ #[inline]
+ pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
+ self.vec.try_reserve(additional)
+ }
+
+ /// See [String::try_reserve_exact].
+ #[inline]
+ pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
+ self.vec.try_reserve_exact(additional)
+ }
+
+ /// See [String::shrink_to_fit].
+ #[inline]
+ pub fn shrink_to_fit(&mut self) {
+ self.vec.shrink_to_fit()
+ }
+
+ /// See [String::shrink_to].
+ #[inline]
+ pub fn shrink_to(&mut self, min_capacity: usize) {
+ self.vec.shrink_to(min_capacity)
+ }
+
+ /// See [String::push].
+ #[inline]
+ pub fn push(&mut self, ch: char) {
+ match ch.len_utf8() {
+ 1 => self.vec.push(ch as u8),
+ _ => self
+ .vec
+ .extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()),
+ }
+ }
+
+ /// See [String::push].
+ #[inline]
+ pub fn push_java(&mut self, ch: JavaCodePoint) {
+ match ch.len_utf8() {
+ 1 => self.vec.push(ch.as_u32() as u8),
+ _ => self.vec.extend_from_slice(ch.encode_semi_utf8(&mut [0; 4])),
+ }
+ }
+
+ /// See [String::as_bytes].
+ #[inline]
+ #[must_use]
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.vec
+ }
+
+ /// See [String::truncate].
+ #[inline]
+ pub fn truncate(&mut self, new_len: usize) {
+ if new_len <= self.len() {
+ assert!(self.is_char_boundary(new_len));
+ self.vec.truncate(new_len)
+ }
+ }
+
+ /// See [String::pop].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ ///
+ /// let mut str = JavaString::from("Hello World!");
+ /// assert_eq!(str.pop().unwrap(), '!');
+ /// assert_eq!(str, "Hello World");
+ ///
+ /// let mut str = JavaString::from("東京");
+ /// assert_eq!(str.pop().unwrap(), '京');
+ /// assert_eq!(str, "東");
+ ///
+ /// assert!(JavaString::new().pop().is_none());
+ /// ```
+ #[inline]
+ pub fn pop(&mut self) -> Option {
+ let ch = self.chars().next_back()?;
+ let newlen = self.len() - ch.len_utf8();
+ unsafe {
+ self.vec.set_len(newlen);
+ }
+ Some(ch)
+ }
+
+ /// See [String::remove].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ ///
+ /// let mut str = JavaString::from("Hello World!");
+ /// assert_eq!(str.remove(5), ' ');
+ /// assert_eq!(str, "HelloWorld!");
+ ///
+ /// let mut str = JavaString::from("Hello 🦀 World!");
+ /// assert_eq!(str.remove(6), '🦀');
+ /// assert_eq!(str, "Hello World!");
+ /// ```
+ /// ```should_panic
+ /// # use java_string::JavaString;
+ /// // Should panic
+ /// JavaString::new().remove(0);
+ /// ```
+ /// ```should_panic
+ /// # use java_string::JavaString;
+ /// // Should panic
+ /// JavaString::from("🦀").remove(1);
+ /// ```
+ #[inline]
+ pub fn remove(&mut self, idx: usize) -> JavaCodePoint {
+ let ch = match self[idx..].chars().next() {
+ Some(ch) => ch,
+ None => panic!("cannot remove a char from the end of a string"),
+ };
+
+ let next = idx + ch.len_utf8();
+ let len = self.len();
+ unsafe {
+ ptr::copy(
+ self.vec.as_ptr().add(next),
+ self.vec.as_mut_ptr().add(idx),
+ len - next,
+ );
+ self.vec.set_len(len - (next - idx));
+ }
+ ch
+ }
+
+ /// See [String::retain].
+ ///
+ /// ```
+ /// # use java_string::{JavaCodePoint, JavaString};
+ ///
+ /// let mut str = JavaString::from("Hello 🦀 World!");
+ /// str.retain(|ch| !ch.is_ascii_uppercase());
+ /// assert_eq!(str, "ello 🦀 orld!");
+ /// str.retain(JavaCodePoint::is_ascii);
+ /// assert_eq!(str, "ello orld!");
+ /// ```
+ #[inline]
+ pub fn retain(&mut self, mut f: F)
+ where
+ F: FnMut(JavaCodePoint) -> bool,
+ {
+ struct SetLenOnDrop<'a> {
+ s: &'a mut JavaString,
+ idx: usize,
+ del_bytes: usize,
+ }
+
+ impl<'a> Drop for SetLenOnDrop<'a> {
+ #[inline]
+ fn drop(&mut self) {
+ let new_len = self.idx - self.del_bytes;
+ debug_assert!(new_len <= self.s.len());
+ unsafe { self.s.vec.set_len(new_len) };
+ }
+ }
+
+ let len = self.len();
+ let mut guard = SetLenOnDrop {
+ s: self,
+ idx: 0,
+ del_bytes: 0,
+ };
+
+ while guard.idx < len {
+ // SAFETY: `guard.idx` is positive-or-zero and less that len so the
+ // `get_unchecked` is in bound. `self` is valid UTF-8 like string
+ // and the returned slice starts at a unicode code point so the
+ // `Chars` always return one character.
+ let ch = unsafe {
+ guard
+ .s
+ .get_unchecked(guard.idx..len)
+ .chars()
+ .next()
+ .unwrap_unchecked()
+ };
+ let ch_len = ch.len_utf8();
+
+ if !f(ch) {
+ guard.del_bytes += ch_len;
+ } else if guard.del_bytes > 0 {
+ // SAFETY: `guard.idx` is in bound and `guard.del_bytes` represent the number of
+ // bytes that are erased from the string so the resulting `guard.idx -
+ // guard.del_bytes` always represent a valid unicode code point.
+ //
+ // `guard.del_bytes` >= `ch.len_utf8()`, so taking a slice with `ch.len_utf8()`
+ // len is safe.
+ ch.encode_semi_utf8(unsafe {
+ slice::from_raw_parts_mut(
+ guard.s.as_mut_ptr().add(guard.idx - guard.del_bytes),
+ ch.len_utf8(),
+ )
+ });
+ }
+
+ // Point idx to the next char
+ guard.idx += ch_len;
+ }
+
+ drop(guard);
+ }
+
+ /// See [String::insert].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ /// let mut s = JavaString::from("foo");
+ /// s.insert(3, 'a');
+ /// s.insert(4, 'r');
+ /// s.insert(3, 'b');
+ /// assert_eq!(s, "foobar");
+ /// ```
+ #[inline]
+ pub fn insert(&mut self, idx: usize, ch: char) {
+ assert!(self.is_char_boundary(idx));
+ let mut bits = [0; 4];
+ let bits = ch.encode_utf8(&mut bits).as_bytes();
+
+ unsafe {
+ self.insert_bytes(idx, bits);
+ }
+ }
+
+ /// See [String::insert].
+ #[inline]
+ pub fn insert_java(&mut self, idx: usize, ch: JavaCodePoint) {
+ assert!(self.is_char_boundary(idx));
+ let mut bits = [0; 4];
+ let bits = ch.encode_semi_utf8(&mut bits);
+
+ unsafe {
+ self.insert_bytes(idx, bits);
+ }
+ }
+
+ #[inline]
+ unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) {
+ let len = self.len();
+ let amt = bytes.len();
+ self.vec.reserve(amt);
+
+ unsafe {
+ ptr::copy(
+ self.vec.as_ptr().add(idx),
+ self.vec.as_mut_ptr().add(idx + amt),
+ len - idx,
+ );
+ ptr::copy_nonoverlapping(bytes.as_ptr(), self.vec.as_mut_ptr().add(idx), amt);
+ self.vec.set_len(len + amt);
+ }
+ }
+
+ /// See [String::insert_str].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ /// let mut s = JavaString::from("bar");
+ /// s.insert_str(0, "foo");
+ /// assert_eq!(s, "foobar");
+ /// ```
+ #[inline]
+ pub fn insert_str(&mut self, idx: usize, string: &str) {
+ assert!(self.is_char_boundary(idx));
+
+ unsafe {
+ self.insert_bytes(idx, string.as_bytes());
+ }
+ }
+
+ /// See [String::insert_str].
+ pub fn insert_java_str(&mut self, idx: usize, string: &JavaStr) {
+ assert!(self.is_char_boundary(idx));
+
+ unsafe {
+ self.insert_bytes(idx, string.as_bytes());
+ }
+ }
+
+ /// See [String::as_mut_vec].
+ ///
+ /// # Safety
+ ///
+ /// The returned `Vec` must not have invalid UTF-8 written to it, besides
+ /// surrogate pairs.
+ #[inline]
+ pub unsafe fn as_mut_vec(&mut self) -> &mut Vec {
+ &mut self.vec
+ }
+
+ /// See [String::len].
+ #[inline]
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.vec.len()
+ }
+
+ /// See [String::is_empty].
+ #[inline]
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// See [String::split_off].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ /// let mut hello = JavaString::from("Hello World!");
+ /// let world = hello.split_off(6);
+ /// assert_eq!(hello, "Hello ");
+ /// assert_eq!(world, "World!");
+ /// ```
+ /// ```should_panic
+ /// # use java_string::JavaString;
+ /// let mut s = JavaString::from("🦀");
+ /// // Should panic
+ /// let _ = s.split_off(1);
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn split_off(&mut self, at: usize) -> JavaString {
+ assert!(self.is_char_boundary(at));
+ let other = self.vec.split_off(at);
+ unsafe { JavaString::from_semi_utf8_unchecked(other) }
+ }
+
+ /// See [String::clear].
+ #[inline]
+ pub fn clear(&mut self) {
+ self.vec.clear();
+ }
+
+ /// See [String::drain].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ ///
+ /// let mut s = JavaString::from("α is alpha, β is beta");
+ /// let beta_offset = s.find('β').unwrap_or(s.len());
+ ///
+ /// // Remove the range up until the β from the string
+ /// let t: JavaString = s.drain(..beta_offset).collect();
+ /// assert_eq!(t, "α is alpha, ");
+ /// assert_eq!(s, "β is beta");
+ ///
+ /// // A full range clears the string, like `clear()` does
+ /// s.drain(..);
+ /// assert_eq!(s, "");
+ /// ```
+ #[inline]
+ pub fn drain(&mut self, range: R) -> Drain<'_>
+ where
+ R: RangeBounds,
+ {
+ // Memory safety: see String::drain
+ let Range { start, end } = to_range_checked(range, ..self.len());
+ assert!(self.is_char_boundary(start));
+ assert!(self.is_char_boundary(end));
+
+ // Take out two simultaneous borrows. The &mut String won't be accessed
+ // until iteration is over, in Drop.
+ let self_ptr = self as *mut _;
+ // SAFETY: `to_range_checked` and `is_char_boundary` do the appropriate bounds
+ // checks.
+ let chars_iter = unsafe { self.get_unchecked(start..end) }.chars();
+
+ Drain {
+ start,
+ end,
+ iter: chars_iter,
+ string: self_ptr,
+ }
+ }
+
+ /// See [String::replace_range].
+ ///
+ /// ```
+ /// # use java_string::JavaString;
+ ///
+ /// let mut s = JavaString::from("α is alpha, β is beta");
+ /// let beta_offset = s.find('β').unwrap_or(s.len());
+ ///
+ /// // Replace the range up until the β from the string
+ /// s.replace_range(..beta_offset, "Α is capital alpha; ");
+ /// assert_eq!(s, "Α is capital alpha; β is beta");
+ /// ```
+ /// ```should_panic
+ /// # use java_string::JavaString;
+ /// let mut s = JavaString::from("α is alpha, β is beta");
+ /// // Should panic
+ /// s.replace_range(..1, "Α is capital alpha; ");
+ /// ```
+ pub fn replace_range(&mut self, range: R, replace_with: &str)
+ where
+ R: RangeBounds,
+ {
+ self.replace_range_java(range, JavaStr::from_str(replace_with))
+ }
+
+ /// See [String::replace_range].
+ pub fn replace_range_java(&mut self, range: R, replace_with: &JavaStr)
+ where
+ R: RangeBounds,
+ {
+ let start = range.start_bound();
+ match start {
+ Bound::Included(&n) => assert!(self.is_char_boundary(n)),
+ Bound::Excluded(&n) => assert!(self.is_char_boundary(n + 1)),
+ Bound::Unbounded => {}
+ };
+ let end = range.end_bound();
+ match end {
+ Bound::Included(&n) => assert!(self.is_char_boundary(n + 1)),
+ Bound::Excluded(&n) => assert!(self.is_char_boundary(n)),
+ Bound::Unbounded => {}
+ };
+
+ unsafe { self.as_mut_vec() }.splice((start, end), replace_with.bytes());
+ }
+
+ /// See [String::into_boxed_str].
+ #[inline]
+ #[must_use]
+ pub fn into_boxed_str(self) -> Box {
+ let slice = self.vec.into_boxed_slice();
+ unsafe { JavaStr::from_boxed_semi_utf8_unchecked(slice) }
+ }
+
+ /// See [String::leak].
+ #[inline]
+ pub fn leak<'a>(self) -> &'a mut JavaStr {
+ let slice = self.vec.leak();
+ unsafe { JavaStr::from_semi_utf8_unchecked_mut(slice) }
+ }
+}
+
+impl Add<&str> for JavaString {
+ type Output = JavaString;
+
+ #[inline]
+ fn add(mut self, rhs: &str) -> Self::Output {
+ self.push_str(rhs);
+ self
+ }
+}
+
+impl Add<&JavaStr> for JavaString {
+ type Output = JavaString;
+
+ #[inline]
+ fn add(mut self, rhs: &JavaStr) -> Self::Output {
+ self.push_java_str(rhs);
+ self
+ }
+}
+
+impl AddAssign<&str> for JavaString {
+ #[inline]
+ fn add_assign(&mut self, rhs: &str) {
+ self.push_str(rhs);
+ }
+}
+
+impl AddAssign<&JavaStr> for JavaString {
+ #[inline]
+ fn add_assign(&mut self, rhs: &JavaStr) {
+ self.push_java_str(rhs);
+ }
+}
+
+impl AsMut for JavaString {
+ #[inline]
+ fn as_mut(&mut self) -> &mut JavaStr {
+ self.as_mut_java_str()
+ }
+}
+
+impl AsRef<[u8]> for JavaString {
+ #[inline]
+ fn as_ref(&self) -> &[u8] {
+ self.as_bytes()
+ }
+}
+
+impl AsRef for JavaString {
+ #[inline]
+ fn as_ref(&self) -> &JavaStr {
+ self.as_java_str()
+ }
+}
+
+impl Borrow for JavaString {
+ #[inline]
+ fn borrow(&self) -> &JavaStr {
+ self.as_java_str()
+ }
+}
+
+impl BorrowMut for JavaString {
+ #[inline]
+ fn borrow_mut(&mut self) -> &mut JavaStr {
+ self.as_mut_java_str()
+ }
+}
+
+impl Clone for JavaString {
+ #[inline]
+ fn clone(&self) -> Self {
+ JavaString {
+ vec: self.vec.clone(),
+ }
+ }
+
+ #[inline]
+ fn clone_from(&mut self, source: &Self) {
+ self.vec.clone_from(&source.vec)
+ }
+}
+
+impl Debug for JavaString {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&**self, f)
+ }
+}
+
+impl Deref for JavaString {
+ type Target = JavaStr;
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.as_java_str()
+ }
+}
+
+impl DerefMut for JavaString {
+ #[inline]
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.as_mut_java_str()
+ }
+}
+
+impl Display for JavaString {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&**self, f)
+ }
+}
+
+impl Extend for JavaString {
+ fn extend>(&mut self, iter: T) {
+ let iterator = iter.into_iter();
+ let (lower_bound, _) = iterator.size_hint();
+ self.reserve(lower_bound);
+ iterator.for_each(move |c| self.push(c));
+ }
+}
+
+impl Extend for JavaString {
+ fn extend>(&mut self, iter: T) {
+ let iterator = iter.into_iter();
+ let (lower_bound, _) = iterator.size_hint();
+ self.reserve(lower_bound);
+ iterator.for_each(move |c| self.push_java(c));
+ }
+}
+
+impl Extend for JavaString {
+ fn extend>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_str(&s));
+ }
+}
+
+impl Extend for JavaString {
+ fn extend>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_java_str(&s));
+ }
+}
+
+impl<'a> Extend<&'a char> for JavaString {
+ fn extend>(&mut self, iter: T) {
+ self.extend(iter.into_iter().cloned())
+ }
+}
+
+impl<'a> Extend<&'a JavaCodePoint> for JavaString {
+ fn extend>(&mut self, iter: T) {
+ self.extend(iter.into_iter().cloned())
+ }
+}
+
+impl<'a> Extend<&'a str> for JavaString {
+ fn extend>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_str(s));
+ }
+}
+
+impl<'a> Extend<&'a JavaStr> for JavaString {
+ fn extend>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_java_str(s));
+ }
+}
+
+impl Extend> for JavaString {
+ fn extend>>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_str(&s));
+ }
+}
+
+impl Extend> for JavaString {
+ fn extend>>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_java_str(&s));
+ }
+}
+
+impl<'a> Extend> for JavaString {
+ fn extend>>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_str(&s));
+ }
+}
+
+impl<'a> Extend> for JavaString {
+ fn extend>>(&mut self, iter: T) {
+ iter.into_iter().for_each(move |s| self.push_java_str(&s));
+ }
+}
+
+impl From for JavaString {
+ #[inline]
+ fn from(value: String) -> Self {
+ unsafe {
+ // SAFETY: value is valid UTF-8
+ JavaString::from_semi_utf8_unchecked(value.into_bytes())
+ }
+ }
+}
+
+impl From<&String> for JavaString {
+ #[inline]
+ fn from(value: &String) -> Self {
+ Self::from(value.clone())
+ }
+}
+
+impl From<&JavaString> for JavaString {
+ #[inline]
+ fn from(value: &JavaString) -> Self {
+ value.clone()
+ }
+}
+
+impl From<&mut str> for JavaString {
+ #[inline]
+ fn from(value: &mut str) -> Self {
+ Self::from(&*value)
+ }
+}
+
+impl From<&str> for JavaString {
+ #[inline]
+ fn from(value: &str) -> Self {
+ Self::from(value.to_owned())
+ }
+}
+
+impl From<&mut JavaStr> for JavaString {
+ #[inline]
+ fn from(value: &mut JavaStr) -> Self {
+ Self::from(&*value)
+ }
+}
+
+impl From<&JavaStr> for JavaString {
+ #[inline]
+ fn from(value: &JavaStr) -> Self {
+ value.to_owned()
+ }
+}
+
+impl From> for JavaString {
+ #[inline]
+ fn from(value: Box) -> Self {
+ Self::from(value.into_string())
+ }
+}
+
+impl From> for JavaString {
+ #[inline]
+ fn from(value: Box) -> Self {
+ value.into_string()
+ }
+}
+
+impl<'a> From> for JavaString {
+ #[inline]
+ fn from(value: Cow<'a, str>) -> Self {
+ Self::from(value.into_owned())
+ }
+}
+
+impl<'a> From> for JavaString {
+ #[inline]
+ fn from(value: Cow<'a, JavaStr>) -> Self {
+ value.into_owned()
+ }
+}
+
+impl From for Arc {
+ #[inline]
+ fn from(value: JavaString) -> Self {
+ Arc::from(&value[..])
+ }
+}
+
+impl<'a> From for Cow<'a, JavaStr> {
+ #[inline]
+ fn from(value: JavaString) -> Self {
+ Cow::Owned(value)
+ }
+}
+
+impl From for Rc {
+ #[inline]
+ fn from(value: JavaString) -> Self {
+ Rc::from(&value[..])
+ }
+}
+
+impl From for Vec {
+ #[inline]
+ fn from(value: JavaString) -> Self {
+ value.into_bytes()
+ }
+}
+
+impl From for JavaString {
+ #[inline]
+ fn from(value: char) -> Self {
+ Self::from(value.encode_utf8(&mut [0; 4]))
+ }
+}
+
+impl From for JavaString {
+ #[inline]
+ fn from(value: JavaCodePoint) -> Self {
+ unsafe {
+ // SAFETY: we're encoding into semi-valid UTF-8
+ JavaString::from_semi_utf8_unchecked(value.encode_semi_utf8(&mut [0; 4]).to_vec())
+ }
+ }
+}
+
+impl FromIterator for JavaString {
+ #[inline]
+ fn from_iter>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl<'a> FromIterator<&'a char> for JavaString {
+ #[inline]
+ fn from_iter>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl FromIterator for JavaString {
+ #[inline]
+ fn from_iter>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl<'a> FromIterator<&'a JavaCodePoint> for JavaString {
+ #[inline]
+ fn from_iter>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl<'a> FromIterator<&'a str> for JavaString {
+ #[inline]
+ fn from_iter>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl FromIterator for JavaString {
+ fn from_iter>(iter: T) -> Self {
+ let mut iterator = iter.into_iter();
+
+ match iterator.next() {
+ None => JavaString::new(),
+ Some(buf) => {
+ let mut buf = JavaString::from(buf);
+ buf.extend(iterator);
+ buf
+ }
+ }
+ }
+}
+
+impl FromIterator for JavaString {
+ fn from_iter>(iter: T) -> Self {
+ let mut iterator = iter.into_iter();
+
+ match iterator.next() {
+ None => JavaString::new(),
+ Some(mut buf) => {
+ buf.extend(iterator);
+ buf
+ }
+ }
+ }
+}
+
+impl FromIterator> for JavaString {
+ #[inline]
+ fn from_iter>>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl FromIterator> for JavaString {
+ #[inline]
+ fn from_iter>>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl<'a> FromIterator> for JavaString {
+ #[inline]
+ fn from_iter>>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl<'a> FromIterator> for JavaString {
+ #[inline]
+ fn from_iter>>(iter: T) -> Self {
+ let mut buf = JavaString::new();
+ buf.extend(iter);
+ buf
+ }
+}
+
+impl FromStr for JavaString {
+ type Err = Infallible;
+
+ #[inline]
+ fn from_str(s: &str) -> Result {
+ Ok(Self::from(s))
+ }
+}
+
+impl Hash for JavaString {
+ #[inline]
+ fn hash(&self, state: &mut H) {
+ (**self).hash(state)
+ }
+}
+
+impl Index> for JavaString {
+ type Output = JavaStr;
+
+ #[inline]
+ fn index(&self, index: Range) -> &Self::Output {
+ &self[..][index]
+ }
+}
+
+impl Index> for JavaString {
+ type Output = JavaStr;
+
+ #[inline]
+ fn index(&self, index: RangeFrom) -> &Self::Output {
+ &self[..][index]
+ }
+}
+
+impl Index for JavaString {
+ type Output = JavaStr;
+
+ #[inline]
+ fn index(&self, _index: RangeFull) -> &Self::Output {
+ self.as_java_str()
+ }
+}
+
+impl Index> for JavaString {
+ type Output = JavaStr;
+
+ #[inline]
+ fn index(&self, index: RangeInclusive) -> &Self::Output {
+ &self[..][index]
+ }
+}
+
+impl Index> for JavaString {
+ type Output = JavaStr;
+
+ #[inline]
+ fn index(&self, index: RangeTo) -> &Self::Output {
+ &self[..][index]
+ }
+}
+
+impl Index> for JavaString {
+ type Output = JavaStr;
+
+ #[inline]
+ fn index(&self, index: RangeToInclusive) -> &Self::Output {
+ &self[..][index]
+ }
+}
+
+impl IndexMut> for JavaString {
+ #[inline]
+ fn index_mut(&mut self, index: Range) -> &mut Self::Output {
+ &mut self[..][index]
+ }
+}
+
+impl IndexMut> for JavaString {
+ #[inline]
+ fn index_mut(&mut self, index: RangeFrom) -> &mut Self::Output {
+ &mut self[..][index]
+ }
+}
+
+impl IndexMut for JavaString {
+ #[inline]
+ fn index_mut(&mut self, _index: RangeFull) -> &mut Self::Output {
+ self.as_mut_java_str()
+ }
+}
+
+impl IndexMut> for JavaString {
+ #[inline]
+ fn index_mut(&mut self, index: RangeInclusive) -> &mut Self::Output {
+ &mut self[..][index]
+ }
+}
+
+impl IndexMut> for JavaString {
+ #[inline]
+ fn index_mut(&mut self, index: RangeTo) -> &mut Self::Output {
+ &mut self[..][index]
+ }
+}
+
+impl IndexMut> for JavaString {
+ #[inline]
+ fn index_mut(&mut self, index: RangeToInclusive) -> &mut Self::Output {
+ &mut self[..][index]
+ }
+}
+
+impl PartialEq for JavaString {
+ #[inline]
+ fn eq(&self, other: &str) -> bool {
+ self[..] == other
+ }
+}
+
+impl PartialEq for str {
+ #[inline]
+ fn eq(&self, other: &JavaString) -> bool {
+ self == other[..]
+ }
+}
+
+impl<'a> PartialEq<&'a str> for JavaString {
+ #[inline]
+ fn eq(&self, other: &&'a str) -> bool {
+ self == *other
+ }
+}
+
+impl<'a> PartialEq for &'a str {
+ #[inline]
+ fn eq(&self, other: &JavaString) -> bool {
+ *self == other
+ }
+}
+
+impl PartialEq for JavaString {
+ #[inline]
+ fn eq(&self, other: &String) -> bool {
+ &self[..] == other
+ }
+}
+
+impl PartialEq for String {
+ #[inline]
+ fn eq(&self, other: &JavaString) -> bool {
+ self == &other[..]
+ }
+}
+
+impl PartialEq for JavaString {
+ #[inline]
+ fn eq(&self, other: &JavaStr) -> bool {
+ self[..] == other
+ }
+}
+
+impl<'a> PartialEq<&'a JavaStr> for JavaString {
+ #[inline]
+ fn eq(&self, other: &&'a JavaStr) -> bool {
+ self == *other
+ }
+}
+
+impl<'a> PartialEq> for JavaString {
+ #[inline]
+ fn eq(&self, other: &Cow<'a, str>) -> bool {
+ &self[..] == other
+ }
+}
+
+impl<'a> PartialEq for Cow<'a, str> {
+ #[inline]
+ fn eq(&self, other: &JavaString) -> bool {
+ self == &other[..]
+ }
+}
+
+impl<'a> PartialEq> for JavaString {
+ #[inline]
+ fn eq(&self, other: &Cow<'a, JavaStr>) -> bool {
+ &self[..] == other
+ }
+}
+
+impl<'a> PartialEq for Cow<'a, JavaStr> {
+ #[inline]
+ fn eq(&self, other: &JavaString) -> bool {
+ self == &other[..]
+ }
+}
+
+impl Write for JavaString {
+ #[inline]
+ fn write_str(&mut self, s: &str) -> std::fmt::Result {
+ self.push_str(s);
+ Ok(())
+ }
+
+ #[inline]
+ fn write_char(&mut self, c: char) -> std::fmt::Result {
+ self.push(c);
+ Ok(())
+ }
+}
+
+pub struct Drain<'a> {
+ string: *mut JavaString,
+ start: usize,
+ end: usize,
+ iter: Chars<'a>,
+}
+
+impl Debug for Drain<'_> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_tuple("Drain").field(&self.as_str()).finish()
+ }
+}
+
+unsafe impl Sync for Drain<'_> {}
+unsafe impl Send for Drain<'_> {}
+
+impl Drop for Drain<'_> {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ // Use Vec::drain. "Reaffirm" the bounds checks to avoid
+ // panic code being inserted again.
+ let self_vec = (*self.string).as_mut_vec();
+ if self.start <= self.end && self.end <= self_vec.len() {
+ self_vec.drain(self.start..self.end);
+ }
+ }
+ }
+}
+
+impl AsRef for Drain<'_> {
+ #[inline]
+ fn as_ref(&self) -> &JavaStr {
+ self.as_str()
+ }
+}
+
+impl AsRef<[u8]> for Drain<'_> {
+ #[inline]
+ fn as_ref(&self) -> &[u8] {
+ self.as_str().as_bytes()
+ }
+}
+
+impl Drain<'_> {
+ #[inline]
+ #[must_use]
+ pub fn as_str(&self) -> &JavaStr {
+ self.iter.as_str()
+ }
+}
+
+impl Iterator for Drain<'_> {
+ type Item = JavaCodePoint;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.iter.next()
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option) {
+ self.iter.size_hint()
+ }
+
+ #[inline]
+ fn last(mut self) -> Option {
+ self.next_back()
+ }
+}
+
+impl DoubleEndedIterator for Drain<'_> {
+ #[inline]
+ fn next_back(&mut self) -> Option {
+ self.iter.next_back()
+ }
+}
+
+impl FusedIterator for Drain<'_> {}
diff --git a/crates/java_string/src/pattern.rs b/crates/java_string/src/pattern.rs
new file mode 100644
index 000000000..06cc78041
--- /dev/null
+++ b/crates/java_string/src/pattern.rs
@@ -0,0 +1,402 @@
+use crate::{JavaCodePoint, JavaStr};
+
+mod private_pattern {
+ use crate::{JavaCodePoint, JavaStr};
+
+ pub trait Sealed {}
+
+ impl Sealed for char {}
+ impl Sealed for JavaCodePoint {}
+ impl Sealed for &str {}
+ impl Sealed for &JavaStr {}
+ impl Sealed for F where F: FnMut(JavaCodePoint) -> bool {}
+ impl Sealed for &[char] {}
+ impl Sealed for &[JavaCodePoint] {}
+ impl Sealed for &char {}
+ impl Sealed for &JavaCodePoint {}
+ impl Sealed for &&str {}
+ impl Sealed for &&JavaStr {}
+}
+
+/// # Safety
+///
+/// Methods in this trait must only return indexes that are on char boundaries
+pub unsafe trait JavaStrPattern: private_pattern::Sealed {
+ fn prefix_len_in(&mut self, haystack: &JavaStr) -> Option;
+ fn suffix_len_in(&mut self, haystack: &JavaStr) -> Option;
+ fn find_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)>;
+ fn rfind_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)>;
+}
+
+unsafe impl JavaStrPattern for char {
+ #[inline]
+ fn prefix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ let ch = haystack.chars().next()?;
+ if ch == *self {
+ Some(ch.len_utf8())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn suffix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ let ch = haystack.chars().next_back()?;
+ if ch == *self {
+ Some(ch.len_utf8())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn find_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ let mut encoded = [0; 4];
+ let encoded = self.encode_utf8(&mut encoded).as_bytes();
+ find(haystack.as_bytes(), encoded).map(|index| (index, encoded.len()))
+ }
+
+ #[inline]
+ fn rfind_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ let mut encoded = [0; 4];
+ let encoded = self.encode_utf8(&mut encoded).as_bytes();
+ rfind(haystack.as_bytes(), encoded).map(|index| (index, encoded.len()))
+ }
+}
+
+unsafe impl JavaStrPattern for JavaCodePoint {
+ #[inline]
+ fn prefix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ let ch = haystack.chars().next()?;
+ if ch == *self {
+ Some(ch.len_utf8())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn suffix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ let ch = haystack.chars().next_back()?;
+ if ch == *self {
+ Some(ch.len_utf8())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn find_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ let mut encoded = [0; 4];
+ let encoded = self.encode_semi_utf8(&mut encoded);
+ find(haystack.as_bytes(), encoded).map(|index| (index, encoded.len()))
+ }
+
+ #[inline]
+ fn rfind_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ let mut encoded = [0; 4];
+ let encoded = self.encode_semi_utf8(&mut encoded);
+ rfind(haystack.as_bytes(), encoded).map(|index| (index, encoded.len()))
+ }
+}
+
+unsafe impl JavaStrPattern for &str {
+ #[inline]
+ fn prefix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ if haystack.as_bytes().starts_with(self.as_bytes()) {
+ Some(self.len())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn suffix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ if haystack.as_bytes().ends_with(self.as_bytes()) {
+ Some(self.len())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn find_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ find(haystack.as_bytes(), self.as_bytes()).map(|index| (index, self.len()))
+ }
+
+ #[inline]
+ fn rfind_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ rfind(haystack.as_bytes(), self.as_bytes()).map(|index| (index, self.len()))
+ }
+}
+
+unsafe impl JavaStrPattern for &JavaStr {
+ #[inline]
+ fn prefix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ if haystack.as_bytes().starts_with(self.as_bytes()) {
+ Some(self.len())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn suffix_len_in(&mut self, haystack: &JavaStr) -> Option {
+ if haystack.as_bytes().ends_with(self.as_bytes()) {
+ Some(self.len())
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn find_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ find(haystack.as_bytes(), self.as_bytes()).map(|index| (index, self.len()))
+ }
+
+ #[inline]
+ fn rfind_in(&mut self, haystack: &JavaStr) -> Option<(usize, usize)> {
+ rfind(haystack.as_bytes(), self.as_bytes()).map(|index| (index, self.len()))
+ }
+}
+
+unsafe impl JavaStrPattern for F
+where
+ F: FnMut(JavaCodePoint) -> bool,
+{
+ #[inline]
+ fn prefix_len_in(&mut self, haystack: &JavaStr) -> Option