1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/qstring.h>
5
6#ifndef QSTRINGBUILDER_H
7#define QSTRINGBUILDER_H
8
9#if 0
10// syncqt can not handle the templates in this file, and it doesn't need to
11// process them anyway because they are internal.
12#pragma qt_class(QStringBuilder)
13#pragma qt_sync_stop_processing
14#endif
15
16#include <QtCore/qbytearray.h>
17
18#include <string.h>
19
20QT_BEGIN_NAMESPACE
21
22
23struct Q_CORE_EXPORT QAbstractConcatenable
24{
25protected:
26 static void convertFromUtf8(QByteArrayView in, QChar *&out) noexcept;
27 static inline void convertFromAscii(char a, QChar *&out) noexcept
28 {
29 *out++ = QLatin1Char(a);
30 }
31 static void appendLatin1To(QLatin1StringView in, QChar *out) noexcept;
32};
33
34template <typename T> struct QConcatenable;
35
36template <typename T>
37using QConcatenableEx = QConcatenable<q20::remove_cvref_t<T>>;
38
39namespace QtStringBuilder {
40 template <typename A, typename B> struct ConvertToTypeHelper
41 { typedef A ConvertTo; };
42 template <typename T> struct ConvertToTypeHelper<T, QString>
43 { typedef QString ConvertTo; };
44
45 template <typename T> using HasIsNull = decltype(std::declval<const T &>().isNull());
46 template <typename T> bool isNull(const T &t)
47 {
48 if constexpr (qxp::is_detected_v<HasIsNull, T>)
49 return t.isNull();
50 else
51 return false;
52 }
53}
54
55template<typename Builder, typename T>
56struct QStringBuilderCommon
57{
58 T toUpper() const { return resolved().toUpper(); }
59 T toLower() const { return resolved().toLower(); }
60
61protected:
62 T resolved() const { return *static_cast<const Builder*>(this); }
63};
64
65template<typename Builder, typename T>
66struct QStringBuilderBase : public QStringBuilderCommon<Builder, T>
67{
68};
69
70template<typename Builder>
71struct QStringBuilderBase<Builder, QString> : public QStringBuilderCommon<Builder, QString>
72{
73 QByteArray toLatin1() const { return this->resolved().toLatin1(); }
74 QByteArray toUtf8() const { return this->resolved().toUtf8(); }
75 QByteArray toLocal8Bit() const { return this->resolved().toLocal8Bit(); }
76};
77
78template <typename A, typename B>
79class QStringBuilder : public QStringBuilderBase<QStringBuilder<A, B>,
80 typename QtStringBuilder::ConvertToTypeHelper<
81 typename QConcatenableEx<A>::ConvertTo,
82 typename QConcatenableEx<B>::ConvertTo
83 >::ConvertTo
84 >
85{
86public:
87 QStringBuilder(A &&a_, B &&b_) : a(std::forward<A>(a_)), b(std::forward<B>(b_)) {}
88
89 QStringBuilder(QStringBuilder &&) = default;
90 QStringBuilder(const QStringBuilder &) = default;
91 ~QStringBuilder() = default;
92
93private:
94 friend class QByteArray;
95 friend class QString;
96 template <typename T> T convertTo() const
97 {
98 if (isNull()) {
99 // appending two null strings must give back a null string,
100 // so we're special casing this one out, QTBUG-114206
101 return T();
102 }
103
104 const qsizetype len = Concatenable::size(*this);
105 T s(len, Qt::Uninitialized);
106
107 // Using data_ptr() here (private API) so we can bypass the
108 // isDetached() and the replacement of a null pointer with _empty in
109 // both QString and QByteArray's data() and constData(). The result is
110 // the same if len != 0.
111 auto d = reinterpret_cast<typename T::iterator>(s.data_ptr().data());
112 const auto start = d;
113 Concatenable::appendTo(*this, d);
114
115 if constexpr (Concatenable::ExactSize) {
116 Q_UNUSED(start)
117 } else {
118 if (len != d - start) {
119 // this resize is necessary since we allocate a bit too much
120 // when dealing with variable sized 8-bit encodings
121 s.resize(d - start);
122 }
123 }
124 return s;
125 }
126
127 typedef QConcatenable<QStringBuilder<A, B> > Concatenable;
128public:
129 typedef typename Concatenable::ConvertTo ConvertTo;
130 operator ConvertTo() const { return convertTo<ConvertTo>(); }
131
132 qsizetype size() const { return Concatenable::size(*this); }
133
134 bool isNull() const
135 {
136 return QtStringBuilder::isNull(a) && QtStringBuilder::isNull(b);
137 }
138
139 A a;
140 B b;
141
142private:
143 QStringBuilder &operator=(QStringBuilder &&) = delete;
144 QStringBuilder &operator=(const QStringBuilder &) = delete;
145};
146
147template <> struct QConcatenable<char> : private QAbstractConcatenable
148{
149 typedef char type;
150 typedef QByteArray ConvertTo;
151 enum { ExactSize = true };
152 static qsizetype size(const char) { return 1; }
153#ifndef QT_NO_CAST_FROM_ASCII
154 QT_ASCII_CAST_WARN static inline void appendTo(const char c, QChar *&out)
155 {
156 QAbstractConcatenable::convertFromAscii(c, out);
157 }
158#endif
159 static inline void appendTo(const char c, char *&out)
160 { *out++ = c; }
161};
162
163template <> struct QConcatenable<QByteArrayView> : private QAbstractConcatenable
164{
165 typedef QByteArrayView type;
166 typedef QByteArray ConvertTo;
167 enum { ExactSize = true };
168 static qsizetype size(QByteArrayView bav) { return bav.size(); }
169#ifndef QT_NO_CAST_FROM_ASCII
170 QT_ASCII_CAST_WARN static inline void appendTo(QByteArrayView bav, QChar *&out)
171 {
172 QAbstractConcatenable::convertFromUtf8(bav, out);
173 }
174#endif
175 static inline void appendTo(QByteArrayView bav, char *&out)
176 {
177 qsizetype n = bav.size();
178 if (n)
179 memcpy(dest: out, src: bav.data(), n: n);
180 out += n;
181 }
182};
183
184template <> struct QConcatenable<char16_t> : private QAbstractConcatenable
185{
186 typedef char16_t type;
187 typedef QString ConvertTo;
188 enum { ExactSize = true };
189 static constexpr qsizetype size(char16_t) { return 1; }
190 static inline void appendTo(char16_t c, QChar *&out)
191 { *out++ = c; }
192};
193
194template <> struct QConcatenable<QLatin1Char>
195{
196 typedef QLatin1Char type;
197 typedef QString ConvertTo;
198 enum { ExactSize = true };
199 static qsizetype size(const QLatin1Char) { return 1; }
200 static inline void appendTo(const QLatin1Char c, QChar *&out)
201 { *out++ = c; }
202 static inline void appendTo(const QLatin1Char c, char *&out)
203 { *out++ = c.toLatin1(); }
204};
205
206template <> struct QConcatenable<QChar> : private QAbstractConcatenable
207{
208 typedef QChar type;
209 typedef QString ConvertTo;
210 enum { ExactSize = true };
211 static qsizetype size(const QChar) { return 1; }
212 static inline void appendTo(const QChar c, QChar *&out)
213 { *out++ = c; }
214};
215
216template <> struct QConcatenable<QChar::SpecialCharacter> : private QAbstractConcatenable
217{
218 typedef QChar::SpecialCharacter type;
219 typedef QString ConvertTo;
220 enum { ExactSize = true };
221 static qsizetype size(const QChar::SpecialCharacter) { return 1; }
222 static inline void appendTo(const QChar::SpecialCharacter c, QChar *&out)
223 { *out++ = c; }
224};
225
226template <> struct QConcatenable<QLatin1StringView> : private QAbstractConcatenable
227{
228 typedef QLatin1StringView type;
229 typedef QString ConvertTo;
230 enum { ExactSize = true };
231 static qsizetype size(const QLatin1StringView a) { return a.size(); }
232 static inline void appendTo(const QLatin1StringView a, QChar *&out)
233 {
234 appendLatin1To(in: a, out);
235 out += a.size();
236 }
237 static inline void appendTo(const QLatin1StringView a, char *&out)
238 {
239 if (const char *data = a.data()) {
240 memcpy(dest: out, src: data, n: a.size());
241 out += a.size();
242 }
243 }
244};
245
246template <> struct QConcatenable<QString> : private QAbstractConcatenable
247{
248 typedef QString type;
249 typedef QString ConvertTo;
250 enum { ExactSize = true };
251 static qsizetype size(const QString &a) { return a.size(); }
252 static inline void appendTo(const QString &a, QChar *&out)
253 {
254 const qsizetype n = a.size();
255 if (n)
256 memcpy(dest: out, src: reinterpret_cast<const char*>(a.constData()), n: sizeof(QChar) * n);
257 out += n;
258 }
259};
260
261template <> struct QConcatenable<QStringView> : private QAbstractConcatenable
262{
263 typedef QStringView type;
264 typedef QString ConvertTo;
265 enum { ExactSize = true };
266 static qsizetype size(QStringView a) { return a.size(); }
267 static inline void appendTo(QStringView a, QChar *&out)
268 {
269 const auto n = a.size();
270 if (n)
271 memcpy(dest: out, src: a.data(), n: sizeof(QChar) * n);
272 out += n;
273 }
274};
275
276template <qsizetype N> struct QConcatenable<const char[N]> : private QAbstractConcatenable
277{
278 typedef const char type[N];
279 typedef QByteArray ConvertTo;
280 enum { ExactSize = false };
281 static qsizetype size(const char[N]) { return N - 1; }
282#ifndef QT_NO_CAST_FROM_ASCII
283 QT_ASCII_CAST_WARN static inline void appendTo(const char a[N], QChar *&out)
284 {
285 QAbstractConcatenable::convertFromUtf8(QByteArrayView(a, N - 1), out);
286 }
287#endif
288 static inline void appendTo(const char a[N], char *&out)
289 {
290 while (*a)
291 *out++ = *a++;
292 }
293};
294
295template <qsizetype N> struct QConcatenable<char[N]> : QConcatenable<const char[N]>
296{
297 typedef char type[N];
298};
299
300template <> struct QConcatenable<const char *> : private QAbstractConcatenable
301{
302 typedef const char *type;
303 typedef QByteArray ConvertTo;
304 enum { ExactSize = false };
305 static qsizetype size(const char *a) { return qstrlen(str: a); }
306#ifndef QT_NO_CAST_FROM_ASCII
307 QT_ASCII_CAST_WARN static inline void appendTo(const char *a, QChar *&out)
308 { QAbstractConcatenable::convertFromUtf8(QByteArrayView(a), out); }
309#endif
310 static inline void appendTo(const char *a, char *&out)
311 {
312 if (!a)
313 return;
314 while (*a)
315 *out++ = *a++;
316 }
317};
318
319template <> struct QConcatenable<char *> : QConcatenable<const char*>
320{
321 typedef char *type;
322};
323
324template <qsizetype N> struct QConcatenable<const char16_t[N]> : private QAbstractConcatenable
325{
326 using type = const char16_t[N];
327 using ConvertTo = QString;
328 enum { ExactSize = true };
329 static qsizetype size(const char16_t[N]) { return N - 1; }
330 static void appendTo(const char16_t a[N], QChar *&out)
331 {
332 memcpy(static_cast<void *>(out), a, (N - 1) * sizeof(char16_t));
333 out += N - 1;
334 }
335};
336
337template <qsizetype N> struct QConcatenable<char16_t[N]> : QConcatenable<const char16_t[N]>
338{
339 using type = char16_t[N];
340};
341
342template <> struct QConcatenable<const char16_t *> : private QAbstractConcatenable
343{
344 using type = const char16_t *;
345 using ConvertTo = QString;
346 enum { ExactSize = true };
347 static qsizetype size(const char16_t *a) { return QStringView(a).size(); }
348 QT_ASCII_CAST_WARN static inline void appendTo(const char16_t *a, QChar *&out)
349 {
350 if (!a)
351 return;
352 while (*a)
353 *out++ = *a++;
354 }
355};
356
357template <> struct QConcatenable<char16_t *> : QConcatenable<const char16_t*>
358{
359 typedef char16_t *type;
360};
361
362template <> struct QConcatenable<QByteArray> : private QAbstractConcatenable
363{
364 typedef QByteArray type;
365 typedef QByteArray ConvertTo;
366 enum { ExactSize = false };
367 static qsizetype size(const QByteArray &ba) { return ba.size(); }
368#ifndef QT_NO_CAST_FROM_ASCII
369 QT_ASCII_CAST_WARN static inline void appendTo(const QByteArray &ba, QChar *&out)
370 {
371 QAbstractConcatenable::convertFromUtf8(ba, out);
372 }
373#endif
374 static inline void appendTo(const QByteArray &ba, char *&out)
375 {
376 const char *a = ba.constData();
377 const char * const end = ba.end();
378 while (a != end)
379 *out++ = *a++;
380 }
381};
382
383
384template <typename A, typename B>
385struct QConcatenable< QStringBuilder<A, B> >
386{
387 typedef QStringBuilder<A, B> type;
388 using ConvertTo = typename QtStringBuilder::ConvertToTypeHelper<
389 typename QConcatenableEx<A>::ConvertTo,
390 typename QConcatenableEx<B>::ConvertTo
391 >::ConvertTo;
392 enum { ExactSize = QConcatenableEx<A>::ExactSize && QConcatenableEx<B>::ExactSize };
393 static qsizetype size(const type &p)
394 {
395 return QConcatenableEx<A>::size(p.a) + QConcatenableEx<B>::size(p.b);
396 }
397 template<typename T> static inline void appendTo(const type &p, T *&out)
398 {
399 QConcatenableEx<A>::appendTo(p.a, out);
400 QConcatenableEx<B>::appendTo(p.b, out);
401 }
402};
403
404template <typename A, typename B,
405 typename = std::void_t<typename QConcatenableEx<A>::type, typename QConcatenableEx<B>::type>>
406auto operator%(A &&a, B &&b)
407{
408 return QStringBuilder<A, B>(std::forward<A>(a), std::forward<B>(b));
409}
410
411// QT_USE_FAST_OPERATOR_PLUS was introduced in 4.7, QT_USE_QSTRINGBUILDER is to be used from 4.8 onwards
412// QT_USE_FAST_OPERATOR_PLUS does not remove the normal operator+ for QByteArray
413#if defined(QT_USE_FAST_OPERATOR_PLUS) || defined(QT_USE_QSTRINGBUILDER)
414template <typename A, typename B,
415 typename = std::void_t<typename QConcatenableEx<A>::type, typename QConcatenableEx<B>::type>>
416auto operator+(A &&a, B &&b)
417{
418 return std::forward<A>(a) % std::forward<B>(b);
419}
420#endif
421
422namespace QtStringBuilder {
423template <typename A, typename B>
424QByteArray &appendToByteArray(QByteArray &a, const QStringBuilder<A, B> &b, char)
425{
426 // append 8-bit data to a byte array
427 qsizetype len = a.size() + QConcatenable< QStringBuilder<A, B> >::size(b);
428 a.detach(); // a detach() in a.data() could reset a.capacity() to a.size()
429 if (len > a.data_ptr().freeSpaceAtEnd()) // capacity() was broken when prepend()-optimization landed
430 a.reserve(asize: qMax(a: len, b: 2 * a.capacity()));
431 char *it = a.data() + a.size();
432 QConcatenable< QStringBuilder<A, B> >::appendTo(b, it);
433 a.resize(size: len); //we need to resize after the appendTo for the case str+=foo+str
434 return a;
435}
436
437#ifndef QT_NO_CAST_TO_ASCII
438template <typename A, typename B>
439QByteArray &appendToByteArray(QByteArray &a, const QStringBuilder<A, B> &b, QChar)
440{
441 return a += QString(b).toUtf8();
442}
443#endif
444}
445
446template <typename A, typename B>
447QByteArray &operator+=(QByteArray &a, const QStringBuilder<A, B> &b)
448{
449 return QtStringBuilder::appendToByteArray(a, b,
450 typename QConcatenable< QStringBuilder<A, B> >::ConvertTo::value_type());
451}
452
453template <typename A, typename B>
454QString &operator+=(QString &a, const QStringBuilder<A, B> &b)
455{
456 qsizetype len = a.size() + QConcatenable< QStringBuilder<A, B> >::size(b);
457 a.detach(); // a detach() in a.data() could reset a.capacity() to a.size()
458 if (len > a.data_ptr().freeSpaceAtEnd()) // capacity() was broken when prepend()-optimization landed
459 a.reserve(asize: qMax(a: len, b: 2 * a.capacity()));
460 QChar *it = a.data() + a.size();
461 QConcatenable< QStringBuilder<A, B> >::appendTo(b, it);
462 // we need to resize after the appendTo for the case str+=foo+str
463 a.resize(size: it - a.constData()); //may be smaller than len if there was conversion from utf8
464 return a;
465}
466
467QT_END_NAMESPACE
468
469#endif // QSTRINGBUILDER_H
470