Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#pragma once
28
29#include <AK/Forward.h>
30#include <AK/RefPtr.h>
31#include <AK/StringImpl.h>
32#include <AK/StringView.h>
33#include <AK/Traits.h>
34
35namespace AK {
36
37// String is a convenience wrapper around StringImpl, suitable for passing
38// around as a value type. It's basically the same as passing around a
39// RefPtr<StringImpl>, with a bit of syntactic sugar.
40//
41// Note that StringImpl is an immutable object that cannot shrink or grow.
42// Its allocation size is snugly tailored to the specific string it contains.
43// Copying a String is very efficient, since the internal StringImpl is
44// retainable and so copying only requires modifying the ref count.
45//
46// There are three main ways to construct a new String:
47//
48// s = String("some literal");
49//
50// s = String::format("%d little piggies", m_piggies);
51//
52// StringBuilder builder;
53// builder.append("abc");
54// builder.append("123");
55// s = builder.to_string();
56
57class String {
58public:
59 ~String() {}
60
61 String() {}
62
63 String(const StringView& view)
64 {
65 if (view.m_impl)
66 m_impl = *view.m_impl;
67 else
68 m_impl = StringImpl::create(view.characters_without_null_termination(), view.length());
69 }
70
71 String(const String& other)
72 : m_impl(const_cast<String&>(other).m_impl)
73 {
74 }
75
76 String(String&& other)
77 : m_impl(move(other.m_impl))
78 {
79 }
80
81 String(const char* cstring, ShouldChomp shouldChomp = NoChomp)
82 : m_impl(StringImpl::create(cstring, shouldChomp))
83 {
84 }
85
86 String(const char* cstring, size_t length, ShouldChomp shouldChomp = NoChomp)
87 : m_impl(StringImpl::create(cstring, length, shouldChomp))
88 {
89 }
90
91 String(const StringImpl& impl)
92 : m_impl(const_cast<StringImpl&>(impl))
93 {
94 }
95
96 String(const StringImpl* impl)
97 : m_impl(const_cast<StringImpl*>(impl))
98 {
99 }
100
101 String(RefPtr<StringImpl>&& impl)
102 : m_impl(move(impl))
103 {
104 }
105
106 String(NonnullRefPtr<StringImpl>&& impl)
107 : m_impl(move(impl))
108 {
109 }
110
111 enum class CaseSensitivity {
112 CaseInsensitive,
113 CaseSensitive,
114 };
115
116 static String repeated(char, size_t count);
117 bool matches(const StringView& pattern, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;
118
119 // FIXME: These should be shared between String and StringView somehow!
120 int to_int(bool& ok) const;
121 unsigned to_uint(bool& ok) const;
122
123 String to_lowercase() const
124 {
125 if (!m_impl)
126 return String();
127 return m_impl->to_lowercase();
128 }
129
130 String to_uppercase() const
131 {
132 if (!m_impl)
133 return String();
134 return m_impl->to_uppercase();
135 }
136
137 bool equals_ignoring_case(const StringView&) const;
138
139 bool contains(const String&) const;
140
141 Vector<String> split_limit(char separator, size_t limit, bool keep_empty = false) const;
142 Vector<String> split(char separator, bool keep_empty = false) const;
143 String substring(size_t start, size_t length) const;
144
145 Vector<StringView> split_view(char separator, bool keep_empty = false) const;
146 StringView substring_view(size_t start, size_t length) const;
147
148 bool is_null() const { return !m_impl; }
149 bool is_empty() const { return length() == 0; }
150 size_t length() const { return m_impl ? m_impl->length() : 0; }
151 const char* characters() const { return m_impl ? m_impl->characters() : nullptr; }
152 char operator[](size_t i) const
153 {
154 ASSERT(m_impl);
155 return (*m_impl)[i];
156 }
157
158 bool starts_with(const StringView&) const;
159 bool ends_with(const StringView&) const;
160 bool starts_with(char) const;
161 bool ends_with(char) const;
162
163 bool operator==(const String&) const;
164 bool operator!=(const String& other) const { return !(*this == other); }
165
166 bool operator==(const StringView&) const;
167 bool operator!=(const StringView& other) const { return !(*this == other); }
168
169 bool operator<(const String&) const;
170 bool operator<(const char*) const;
171 bool operator>=(const String& other) const { return !(*this < other); }
172 bool operator>=(const char* other) const { return !(*this < other); }
173
174 bool operator>(const String&) const;
175 bool operator>(const char*) const;
176 bool operator<=(const String& other) const { return !(*this > other); }
177 bool operator<=(const char* other) const { return !(*this > other); }
178
179 bool operator==(const char* cstring) const
180 {
181 if (is_null())
182 return !cstring;
183 if (!cstring)
184 return false;
185 return !strcmp(characters(), cstring);
186 }
187
188 bool operator!=(const char* cstring) const
189 {
190 return !(*this == cstring);
191 }
192
193 String isolated_copy() const;
194
195 static String empty();
196
197 StringImpl* impl() { return m_impl.ptr(); }
198 const StringImpl* impl() const { return m_impl.ptr(); }
199
200 String& operator=(String&& other)
201 {
202 if (this != &other)
203 m_impl = move(other.m_impl);
204 return *this;
205 }
206
207 String& operator=(const String& other)
208 {
209 if (this != &other)
210 m_impl = const_cast<String&>(other).m_impl;
211 return *this;
212 }
213
214 u32 hash() const
215 {
216 if (!m_impl)
217 return 0;
218 return m_impl->hash();
219 }
220
221 ByteBuffer to_byte_buffer() const;
222
223 template<typename BufferType>
224 static String copy(const BufferType& buffer, ShouldChomp should_chomp = NoChomp)
225 {
226 if (buffer.is_null())
227 return {};
228 if (buffer.is_empty())
229 return empty();
230 return String((const char*)buffer.data(), buffer.size(), should_chomp);
231 }
232
233 static String format(const char*, ...);
234 static String number(unsigned);
235 static String number(unsigned long);
236 static String number(unsigned long long);
237 static String number(int);
238 static String number(long);
239 static String number(long long);
240
241 StringView view() const
242 {
243 return { characters(), length() };
244 }
245
246private:
247 bool match_helper(const StringView& mask) const;
248 RefPtr<StringImpl> m_impl;
249};
250
251inline bool StringView::operator==(const String& string) const
252{
253 if (string.is_null())
254 return !m_characters;
255 if (!m_characters)
256 return false;
257 if (m_length != string.length())
258 return false;
259 if (m_characters == string.characters())
260 return true;
261 return !memcmp(m_characters, string.characters(), m_length);
262}
263
264template<>
265struct Traits<String> : public GenericTraits<String> {
266 static unsigned hash(const String& s) { return s.impl() ? s.impl()->hash() : 0; }
267};
268
269struct CaseInsensitiveStringTraits : public AK::Traits<String> {
270 static unsigned hash(const String& s) { return s.impl() ? s.to_lowercase().impl()->hash() : 0; }
271 static bool equals(const String& a, const String& b) { return a.to_lowercase() == b.to_lowercase(); }
272};
273
274inline bool operator<(const char* characters, const String& string)
275{
276 if (!characters)
277 return !string.is_null();
278
279 if (string.is_null())
280 return false;
281
282 return strcmp(characters, string.characters()) < 0;
283}
284
285inline bool operator>=(const char* characters, const String& string)
286{
287 return !(characters < string);
288}
289
290inline bool operator>(const char* characters, const String& string)
291{
292 if (!characters)
293 return !string.is_null();
294
295 if (string.is_null())
296 return false;
297
298 return strcmp(characters, string.characters()) > 0;
299}
300
301inline bool operator<=(const char* characters, const String& string)
302{
303 return !(characters > string);
304}
305
306String escape_html_entities(const StringView& html);
307
308}
309
310using AK::CaseInsensitiveStringTraits;
311using AK::String;
312using AK::escape_html_entities;