Newer
Older
libj / modules / j / base / src / string.cpp
#include <cstring>
#include <regex>

#include <j/lang/string.hpp>

// 入力ストリーム用バッファサイズ
static constexpr int MAX_ISTREAM_BUFFER_SIZE = 4096;

namespace j
{
    namespace lang
    {
        // [補足]
        // std::unique_ptr<char[]> value; において、
        // インデックスに対する操作も多々あるため、value.get() + index ではなく、
        // 直観的にわかりやすい, &value[index] の表現にて実装している。

        /**
         * String を構築します。
         *
         * @param str 文字列
         */
        String::String(const char *str) noexcept
        {
            setValue(str);
        }

        /**
         * String を構築します。
         *
         * @param str 文字列
         */
        String::String(const std::string &str) noexcept
        {
            setValue(str.c_str());
        }

        /**
         * String のコピーコンストラクタ。
         *
         * @param str コピー元 String
         */
        String::String(const String &str) noexcept : Object(str)
        {
            setValue(&str.value[0]);
        }

        /**
         * String のムーブコンストラクタ。
         *
         * @param str ムーブ元 String
         */
        String::String(String &&str) noexcept : Object(std::move(str)), value(std::move(str.value)), len(str.len)
        {
            str.value = nullptr;
            str.len = 0;
        }

        /**
         * デストラクタ。
         */
        String::~String() noexcept
        {
            // NOP
        }

        /**
         * コピー代入演算子。
         * コピーして代入します。
         *
         * @param str コピー元 String
         * @return 本オブジェクトへの参照
         */
        String &String::operator=(const String &str) noexcept
        {
            if (this != &str)
            {
                Object::operator=(str);
                setValue(&str.value[0]);
            }
            return *this;
        }

        /**
         * ムーブ代入演算子。
         *
         * @param obj ムーブ元オブジェクト
         * @return 本オブジェクトへの参照
         */
        String &String::operator=(String &&str) noexcept
        { // 特に移すものはない。
            if (this != &str)
            {
                Object::operator=(std::move(str));
                value = std::move(str.value);
                len = str.len;
                str.value = nullptr;
                str.len = 0;
            }
            return *this;
        }

        /**
         * 指定された文字列を結合します。
         */
        String &String::operator+=(const String &str) noexcept
        {
            int newLen = len + str.len;
            std::unique_ptr<char[]> newStr = std::make_unique<char[]>(newLen + 1);
            std::strncpy(&newStr[0], &value[0], len);
            std::strncpy(&newStr[len], &str.value[0], str.len);
            newStr[newLen] = '\0';
            value = std::move(newStr);
            len = newLen;
            return *this;
        }

        /**
         * const char* 型に変換します。
         */
        String::operator const char *() const
        {
            return value.get();
        }

        /**
         * 文字列の長さを返します。
         *
         * @return 文字列の長さ
         */
        int String::length() const noexcept
        {
            return len;
        }

        /**
         * 指定された位置の文字を返します。
         *
         * @param index 位置
         * @return 文字
         */
        char String::charAt(int index) const
        {
            if ((index < 0) || (index >= len))
            {
                // TODO: IndexOutOfBoundsException
            }
            return value[index];
        }

        /**
         * 指定された部分文字列を返します。
         *
         * @param beginIndex 開始位置
         * @param endIndex 終了位置
         * @return 部分文字列
         */
        String String::substring(int beginIndex, int endIndex) const
        {
            if ((0 <= beginIndex) && (beginIndex <= endIndex) && (endIndex <= len))
            {
                int subLen = endIndex - beginIndex;
                std::unique_ptr<char[]> subStr = std::make_unique<char[]>(subLen + 1);
                std::strncpy(&subStr[0], &value[beginIndex], subLen);
                subStr[subLen] = '\0';
                String result(&subStr[0]);
                return result;
            }
            else
            {
                // TODO: IndexOutOfBoundsException
                return nullptr;
            }
        }

        /**
         * 指定された文字列が含まれるか否かを返します。
         *
         * @param str 文字列
         * @return true/false (含まれる/含まれない)
         */
        bool String::contains(const String &str) const noexcept
        {
            return (std::strstr(&value[0], &str.value[0]) != nullptr);
        }

        /**
         * 指定された文字を置換します。
         *
         * @param oldChar 置換前文字
         * @param newChar 置換後文字
         * @return 置換された文字列
         */
        String String::replace(char oldChar, char newChar) const noexcept
        {
            String str(*this);
            for (int idx = 0; idx < len; idx++)
            {
                if (str.value[idx] == oldChar)
                {
                    str.value[idx] = newChar;
                }
            }
            return str;
        }

        // 文字列置換
        String String::replace(const String &regex, const String &replacement) const
        {
            std::regex re(&regex.value[0]);
            std::string res = std::regex_replace(
                &value[0], re, &replacement.value[0], std::regex_constants::match_continuous);
            String str(res.c_str());
            return str;
        }

        // 文字列置換
        String String::replaceAll(const String &regex, const String &replacement) const
        {
            std::regex re(&regex.value[0]);
            std::string res = std::regex_replace(
                &value[0], re, &replacement.value[0], std::regex_constants::match_any);
            String str(res.c_str());
            return str;
        }

        // 分割
        std::unique_ptr<String[]> String::split(const String &) const noexcept
        {
            return nullptr;
        }

        // 先頭の文字列が一致するか
        bool String::startsWith(const String &prefix) const noexcept
        {
            if (prefix.len > len)
            {
                return false;
            }
            for (int idx = 0; idx < prefix.len; idx++)
            {
                if (value[idx] != prefix.value[idx])
                {
                    return false;
                }
            }
            return true;
        }

        // 末尾の文字列が一致するか
        bool String::endsWith(const String &suffix) const noexcept
        {
            if (suffix.len > len)
            {
                return false;
            }
            int value_idx = (len - suffix.len);
            for (int idx = 0; idx < suffix.len; idx++)
            {
                if (value[value_idx] != suffix.value[idx])
                {
                    return false;
                }
                value_idx++;
            }
            return true;
        }

        // 小文字変換
        String String::toLowerCase() const noexcept
        {
            String str(*this);
            for (int idx = 0; idx < len; idx++)
            {
                str.value[idx] = std::tolower(str.value[idx]);
            }
            return str;
        }

        // 大文字変換
        String String::toUpperCase() const noexcept
        {
            String str(*this);
            for (int idx = 0; idx < len; idx++)
            {
                str.value[idx] = std::toupper(str.value[idx]);
            }
            return str;
        }

        // trim
        String String::trim() const noexcept
        {
            int beginIndex = 0;
            for (; beginIndex < len; beginIndex++)
            {
                if (value[beginIndex] > 0x20)
                {
                    break;
                }
            }
            int endIndex = len;
            for (; endIndex >= beginIndex; endIndex--)
            {
                if (value[endIndex] > 0x20)
                {
                    break;
                }
            }
            int trimedLen = endIndex - beginIndex;
            std::unique_ptr<char[]> trimedStr = std::make_unique<char[]>(trimedLen + 1);
            std::strncpy(&trimedStr[0], &value[beginIndex], trimedLen);
            trimedStr[trimedLen] = '\0';
            String result(&trimedStr[0]);
            return result;
        }

        /**
         * この文字列内で、指定された文字列が最初に出現する位置のインデックスを返します。
         * 存在しない場合、-1 を返します。
         *
         * @param 検索対象の部分文字列
         * @param fromIndex 検索開始位置のインデックス
         * @return 見つかった位置
         */
        int String::indexOf(int ch, int fromIndex) const noexcept
        {
            for (int idx = fromIndex; idx < this->len; idx++)
            {
                if (this->value[idx] == ch)
                {
                    return idx;
                }
            }
            return -1;
        }

        /**
         * この文字列内で、指定された部分文字列が最初に出現する位置のインデックスを返します。
         * 空の文字列「」が最後に出現する位置は、fromIndex とみなされます。
         * 見つからない場合 -1 を返します。
         *
         * @param str 検索する文字列
         * @param fromIndex 検索開始位置
         * @return 見つかった位置
         */
        int String::indexOf(const String &str, int fromIndex) const noexcept
        {
            if (str.len == 0)
            { // 空文字
                return fromIndex;
            }
            const char *ptr = std::strstr(&value[fromIndex], &str.value[0]);
            if (ptr != nullptr)
            {
                return (ptr - &value[0]);
            }
            return -1;
        }

        /**
         * この文字列内で、指定された文字が最後に出現する位置のインデックスを返します。
         * 見つからない場合 -1 を返します。
         *
         * @param ch 検索する文字
         * @return 見つかった位置
         */
        int String::lastIndexOf(int ch) const noexcept
        {
            return lastIndexOf(ch, this->len);
        }

        /**
         * この文字列内で、指定された文字が最後に出現する位置のインデックスを返します。
         * 見つからない場合 -1 を返します。
         *
         * @param ch 検索する文字
         * @param fromIndex 検索開始位置
         * @return 見つかった位置
         */
        int String::lastIndexOf(int ch, int fromIndex) const noexcept
        {
            for (int idx = fromIndex; idx >= 0; idx--)
            {
                if (this->value[idx] == ch)
                {
                    return idx;
                }
            }
            return -1;
        }

        /**
         * この文字列内で、指定された部分文字列が最後に出現する位置のインデックスを返します。
         * 空の文字列「」が最後に出現する位置は、length() とみなされます。
         * 見つからない場合 -1 を返します。
         *
         * @param str 検索する文字列
         * @return 見つかった位置
         */
        int String::lastIndexOf(const String &str) const noexcept
        {
            return lastIndexOf(str, this->len);
        }

        /**
         * この文字列内で、指定された部分文字列が最後に出現する位置のインデックスを返します。
         * 空の文字列「」が最後に出現する位置は、fromIndex とみなされます。
         * 見つからない場合 -1 を返します。
         *
         * @param str 検索する文字列
         * @param fromIndex 検索開始位置
         * @return 見つかったインデックス
         */
        int String::lastIndexOf(const String &str, int fromIndex) const noexcept
        {
            if (str.len == 0)
            { // 空文字
                return fromIndex;
            }
            if (str.len > fromIndex)
            { // 部分文字列の方が長いためマッチしない (-1 を返す)
                return -1;
            }
            for (int idx = fromIndex - str.len; idx >= 0; idx--)
            {
                if (std::strncmp(&this->value[idx], &str.value[0], str.len) == 0)
                {
                    return idx;
                }
            }
            return -1;
        }

        /**
         * 本オブジェクトの文字列表現を返します。
         *
         * @return 本オブジェクトの文字列表現
         */
        String String::toString() const noexcept
        {
            String str(*this);
            return str;
        }

        /**
         * 指定されたオブジェクトと合致するか否かを返します。
         *
         * @param obj 比較するオブジェクト
         * @return true/false (合致する/しない)
         */
        bool String::equals(const Object &obj) const noexcept
        {
            bool isSame = isSameClass(obj);
            if (isSame)
            {
                const String &str = dynamic_cast<const String &>(obj);
                if (len == str.len)
                {
                    return (std::strcmp(&value[0], &str.value[0]) == 0);
                }
            }
            return false;
        }

        /**
         * ハッシュコードを取得します。
         *
         * @return ハッシュコード
         */
        int String::hashCode() const noexcept
        {
            int hash = 0;
            for (int idx = 0; idx < len; idx++)
            {
                hash = 31 * hash + value[idx];
            }
            return hash;
        }

        /**
         * 2つの文字列を結合します。
         *
         * @param str1 文字列
         * @param str2 文字列
         * @return 結合後の文字列
         */
        String operator+(const String &str1, const String &str2) noexcept
        {
            String str = str1;
            str += str2;
            return str;
        }

        /**
         * 出力用
         *
         * @param os output stream
         * @param str 出力文字列
         * @return output stream
         */
        std::ostream &operator<<(std::ostream &os, const String &str)
        {
            if (str.value != nullptr)
            {
                os << &str.value[0];
            }
            return os;
        }

        /**
         * 入力用
         *
         * @param is input stream
         * @param str 入力先 String
         * @return input stream
         */
        std::istream &operator>>(std::istream &is, String &str)
        {
            char buff[MAX_ISTREAM_BUFFER_SIZE];
            is >> buff;
            str = String(buff);
            return is;
        }

        ////////////////////////////////////////////////////////////////////////////
        //
        // protected
        //

        /**
         * 文字列データを設定します。
         *
         * @param str 設定する文字列
         */
        void String::setValue(const char *str)
        {
            if (str)
            {
                len = std::strlen(str);
                value = std::make_unique<char[]>(len + 1);
                std::strcpy(&value[0], str);
            }
            else
            {
                len = 0;
                value = std::make_unique<char[]>(1);
                value[0] = '\0';
            }
        }

        /**
         * 本オブジェクトの複製を取得します。
         *
         * [備考]
         * 派生クラスが、ポリモーフィズムをサポートするために、
         * unique_ptr<Object> を返すようにしています。
         *
         * @return 本オブジェクトの複製
         */
        std::unique_ptr<Object> String::clone() const noexcept
        {
            return std::make_unique<String>(*this);
        }

    } // namespace lang
} // namespace j