//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// #pragma once #include "Prerequisites/BsPlatformDefines.h" #include "String/BsString.h" #include "Utility/BsUtil.h" // For hash_combine() namespace bs { /** @addtogroup Filesystem * @{ */ /** * Class for storing and manipulating file paths. Paths may be parsed from and to raw strings according to various * platform specific path types. * * @note * In order to allow the system to easily distinguish between file and directory paths, try to ensure that all directory * paths end with a separator (\ or / depending on platform). System won't fail if you don't but it will be easier to * misuse. */ class BS_UTILITY_EXPORT Path { public: enum class PathType { Windows, Unix, Default }; public: Path(); /** * Constructs a path by parsing the provided path string. Throws exception if provided path is not valid. * * @param[in] pathStr String containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the application * is being compiled to. Otherwise it will be parsed according to provided type. */ Path(const WString& pathStr, PathType type = PathType::Default); /** * Constructs a path by parsing the provided path string. Throws exception if provided path is not valid. * * @param[in] pathStr String containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the application * is being compiled to. Otherwise it will be parsed according to provided type. */ Path(const String& pathStr, PathType type = PathType::Default); /** * Constructs a path by parsing the provided path null terminated string. Throws exception if provided path is not * valid. * * @param[in] pathStr Null-terminated string containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the application * is being compiled to. Otherwise it will be parsed according to provided type. */ Path(const wchar_t* pathStr, PathType type = PathType::Default); /** * Constructs a path by parsing the provided path null terminated string. Throws exception if provided path is * not valid. * * @param[in] pathStr Null-terminated string containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the application * is being compiled to. Otherwise it will be parsed according to provided type. */ Path(const char* pathStr, PathType type = PathType::Default); Path(const Path& other); /** * Assigns a path by parsing the provided path string. Path will be parsed according to the rules of the platform * the application is being compiled to. */ Path& operator= (const WString& pathStr); /** * Assigns a path by parsing the provided path string. Path will be parsed according to the rules of the platform * the application is being compiled to. */ Path& operator= (const String& pathStr); /** * Assigns a path by parsing the provided path null terminated string. Path will be parsed according to the rules * of the platform the application is being compiled to. */ Path& operator= (const wchar_t* pathStr); /** * Assigns a path by parsing the provided path null terminated string. Path will be parsed according to the rules * of the platform the application is being compiled to. */ Path& operator= (const char* pathStr); Path& operator= (const Path& path); /** * Compares two paths and returns true if they match. Comparison is case insensitive and paths will be compared * as-is, without canonization. */ bool operator== (const Path& path) const { return equals(path); } /** * Compares two paths and returns true if they don't match. Comparison is case insensitive and paths will be * compared as-is, without canonization. */ bool operator!= (const Path& path) const { return !equals(path); } /** Gets a directory name with the specified index from the path. */ const WString& operator[] (UINT32 idx) const { return getWDirectory(idx); } /** Swap internal data with another Path object. */ void swap(Path& path); /** Create a path from another Path object. */ void assign(const Path& path); /** * Constructs a path by parsing the provided path string. Throws exception if provided path is not valid. * * @param[in] pathStr String containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the application * is being compiled to. Otherwise it will be parsed according to provided type. */ void assign(const WString& pathStr, PathType type = PathType::Default); /** * Constructs a path by parsing the provided path string. Throws exception if provided path is not valid. * * @param[in] pathStr String containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the application * is being compiled to. Otherwise it will be parsed according to provided type. */ void assign(const String& pathStr, PathType type = PathType::Default); /** * Constructs a path by parsing the provided path null terminated string. Throws exception if provided path is not * valid. * * @param[in] pathStr Null-terminated string containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the * application is being compiled to. Otherwise it will be parsed according to provided * type. */ void assign(const wchar_t* pathStr, PathType type = PathType::Default); /** * Constructs a path by parsing the provided path null terminated string. Throws exception if provided path is not * valid. * * @param[in] pathStr Null-terminated string containing the path. * @param[in] type If set to default path will be parsed according to the rules of the platform the * application is being compiled to. Otherwise it will be parsed according to provided * type. */ void assign(const char* pathStr, PathType type = PathType::Default); /** * Converts the path in a string according to platform path rules. * * @param[in] type If set to default path will be parsed according to the rules of the platform the application is * being compiled to. Otherwise it will be parsed according to provided type. * @return String representing the path using the wide string encoding. */ WString toWString(PathType type = PathType::Default) const; /** * Converts the path in a string according to platform path rules. * * @param[in] type If set to default path will be parsed according to the rules of the platform the application is * being compiled to. Otherwise it will be parsed according to provided type. * @return String representing the path using the UTF8 string encoding. */ String toString(PathType type = PathType::Default) const; /** * Converts the path to either a string or a wstring, doing The Right Thing for the current platform. * * This method is equivalent to toWString() on Windows, and to toString() elsewhere. */ #if BS_PLATFORM == BS_PLATFORM_WIN32 WString toPlatformString() const { return toWString(); } #else String toPlatformString() const { return toString(); } #endif /** Checks is the path a directory (contains no file-name). */ bool isDirectory() const { return mFilename.empty(); } /** Checks does the path point to a file. */ bool isFile() const { return !mFilename.empty(); } /** Checks is the contained path absolute. */ bool isAbsolute() const { return mIsAbsolute; } /** * Returns parent path. If current path points to a file the parent path will be the folder where the file is located. * Or if it contains a directory the parent will be the parent directory. If no parent exists, same path will be * returned. */ Path getParent() const; /** * Returns an absolute path by appending the current path to the provided base. If path was already absolute no * changes are made and copy of current path is returned. If base is not absolute, then the returned path will be * made relative to base, but will not be absolute. */ Path getAbsolute(const Path& base) const; /** * Returns a relative path by making the current path relative to the provided base. Base must be a part of the * current path. If base is not a part of the path no changes are made and a copy of the current path is returned. */ Path getRelative(const Path& base) const; /** * Returns the path as a path to directory. If path was pointing to a file, the filename is removed, otherwise no * changes are made and exact copy is returned. */ Path getDirectory() const; /** * Makes the path the parent of the current path. If current path points to a file the parent path will be the * folder where the file is located. Or if it contains a directory the parent will be the parent directory. If no * parent exists, same path will be returned. */ Path& makeParent(); /** * Makes the current path absolute by appending it to base. If path was already absolute no changes are made and * copy of current path is returned. If base is not absolute, then the returned path will be made relative to base, * but will not be absolute. */ Path& makeAbsolute(const Path& base); /** * Makes the current path relative to the provided base. Base must be a part of the current path. If base is not * a part of the path no changes are made and a copy of the current path is returned. */ Path& makeRelative(const Path& base); /** Appends another path to the end of this path. */ Path& append(const Path& path); /** * Checks if the current path contains the provided path. Comparison is case insensitive and paths will be compared * as-is, without canonization. */ bool includes(const Path& child) const; /** * Compares two paths and returns true if they match. Comparison is case insensitive and paths will be compared * as-is, without canonization. */ bool equals(const Path& other) const; /** Change or set the filename in the path. */ void setFilename(const WString& filename) { mFilename = filename; } /** Change or set the filename in the path. */ void setFilename(const String& filename) { mFilename = bs::toWString(filename); } /** * Change or set the base name in the path. Base name changes the filename by changing its base to the provided * value but keeping extension intact. */ void setBasename(const WString& basename); /** * Change or set the base name in the path. Base name changes the filename by changing its base to the provided * value but keeping extension intact. */ void setBasename(const String& basename); /** * Change or set the extension of the filename in the path. * * @param[in] extension Extension with a leading ".". */ void setExtension(const WString& extension); /** * Change or set the extension of the filename in the path. * * @param[in] extension Extension with a leading ".". */ void setExtension(const String& extension); /** * Returns a filename in the path. * * @param[in] extension If true, returned filename will contain an extension. */ WString getWFilename(bool extension = true) const; /** * Returns a filename in the path. * * @param[in] extension If true, returned filename will contain an extension. */ String getFilename(bool extension = true) const; /** Returns file extension with the leading ".". */ WString getWExtension() const; /** Returns file extension with the leading ".". */ String getExtension() const; /** Gets the number of directories in the path. */ UINT32 getNumDirectories() const { return (UINT32)mDirectories.size(); } /** Gets a directory name with the specified index from the path. */ const WString& getWDirectory(UINT32 idx) const; /** Gets a directory name with the specified index from the path. */ String getDirectory(UINT32 idx) const; /** Returns path device (for example drive, volume, etc.) if one exists in the path. */ const WString& getWDevice() const { return mDevice; } /** Returns path device (for example drive, volume, etc.) if one exists in the path. */ String getDevice() const { return bs::toString(mDevice); } /** Returns path node (for example network name) if one exists in the path. */ const WString& getWNode() const { return mNode; } /** Returns path node (for example network name) if one exists in the path. */ String getNode() const { return bs::toString(mNode); } /** * Gets last element in the path, filename if it exists, otherwise the last directory. If no directories exist * returns device or node. * * @param[in] type Determines format of node or device, in case they are returned. When default, format for * the active platform will be used, otherwise the format defined by the parameter will be used. */ WString getWTail(PathType type = PathType::Default) const; /** * Gets last element in the path, filename if it exists, otherwise the last directory. If no directories exist * returns device or node. * * @param[in] type Determines format of node or device, in case they are returned. When default, format for the * active platform will be used, otherwise the format defined by the parameter will be used. */ String getTail(PathType type = PathType::Default) const; /** Clears the path to nothing. */ void clear(); /** Returns true if no path has been set. */ bool isEmpty() const { return mDirectories.empty() && mFilename.empty() && mDevice.empty() && mNode.empty(); } /** Concatenates two paths. */ Path operator+ (const Path& rhs) const; /** Concatenates two paths. */ Path& operator+= (const Path& rhs); /** Compares two path elements (filenames, directory names, etc.). */ static bool comparePathElem(const WString& left, const WString& right); /** Combines two paths and returns the result. Right path should be relative. */ static Path combine(const Path& left, const Path& right); static const Path BLANK; private: /** * Constructs a path by parsing the provided raw string data. Throws exception if provided path is not valid. * * @param[in] pathStr String containing the path. * @param[in] numChars Number of character in the provided path string. * @param[in] type If set to default path will be parsed according to the rules of the platform the * application is being compiled to. Otherwise it will be parsed according to provided * type. */ void assign(const wchar_t* pathStr, UINT32 numChars, PathType type = PathType::Default); /** * Constructs a path by parsing the provided raw string data. Throws exception if provided path is not valid. * * @param[in] pathStr String containing the path. * @param[in] numChars Number of character in the provided path string. * @param[in] type If set to default path will be parsed according to the rules of the platform the * application is being compiled to. Otherwise it will be parsed according to provided * type. */ void assign(const char* pathStr, UINT32 numChars, PathType type = PathType::Default); /** Parses a Windows path and stores the parsed data internally. Throws an exception if parsing fails. */ template void parseWindows(const T* pathStr, UINT32 numChars) { clear(); UINT32 idx = 0; BasicStringStream tempStream; if (idx < numChars) { if (pathStr[idx] == '\\' || pathStr[idx] == '/') { mIsAbsolute = true; idx++; } } if (idx < numChars) { // Path starts with a node, a drive letter or is relative if (mIsAbsolute && (pathStr[idx] == '\\' || pathStr[idx] == '/')) // Node { idx++; tempStream.str(BasicString()); tempStream.clear(); while (idx < numChars && pathStr[idx] != '\\' && pathStr[idx] != '/') tempStream << pathStr[idx++]; setNode(tempStream.str()); if (idx < numChars) idx++; } else // A drive letter or not absolute { T drive = pathStr[idx]; idx++; if (idx < numChars && pathStr[idx] == ':') { if (mIsAbsolute || !((drive >= 'a' && drive <= 'z') || (drive >= 'A' && drive <= 'Z'))) throwInvalidPathException(BasicString(pathStr, numChars)); mIsAbsolute = true; setDevice(bs::toWString(drive)); idx++; if (idx >= numChars || (pathStr[idx] != '\\' && pathStr[idx] != '/')) throwInvalidPathException(BasicString(pathStr, numChars)); idx++; } else idx--; } while (idx < numChars) { tempStream.str(BasicString()); tempStream.clear(); while (idx < numChars && pathStr[idx] != '\\' && pathStr[idx] != '/') { tempStream << pathStr[idx]; idx++; } if (idx < numChars) pushDirectory(tempStream.str()); else setFilename(tempStream.str()); idx++; } } } /** Parses a Unix path and stores the parsed data internally. Throws an exception if parsing fails. */ template void parseUnix(const T* pathStr, UINT32 numChars) { clear(); UINT32 idx = 0; BasicStringStream tempStream; if (idx < numChars) { if (pathStr[idx] == '/') { mIsAbsolute = true; idx++; } else if (pathStr[idx] == '~') { idx++; if (idx >= numChars || pathStr[idx] == '/') { pushDirectory(bs::toWString('~')); mIsAbsolute = true; } else idx--; } while (idx < numChars) { tempStream.str(BasicString()); tempStream.clear(); while (idx < numChars && pathStr[idx] != '/') { tempStream << pathStr[idx]; idx++; } if (idx < numChars) { if (mDirectories.empty()) { BasicString deviceStr = tempStream.str(); if (!deviceStr.empty() && *(deviceStr.rbegin()) == ':') { setDevice(deviceStr.substr(0, deviceStr.length() - 1)); mIsAbsolute = true; } else { pushDirectory(deviceStr); } } else { pushDirectory(tempStream.str()); } } else { setFilename(tempStream.str()); } idx++; } } } void setNode(const WString& node) { mNode = node; } void setNode(const String& node) { mNode = bs::toWString(node); } void setDevice(const WString& device) { mDevice = device; } void setDevice(const String& device) { mDevice = bs::toWString(device); } /** Build a Windows path string from internal path data. */ WString buildWindows() const; /** Build a Unix path string from internal path data. */ WString buildUnix() const; /** Add new directory to the end of the path. */ void pushDirectory(const WString& dir); /** Add new directory to the end of the path. */ void pushDirectory(const String& dir); /** Helper method that throws invalid path exception. */ void throwInvalidPathException(const WString& path) const; /** Helper method that throws invalid path exception. */ void throwInvalidPathException(const String& path) const; private: friend struct RTTIPlainType; // For serialization friend struct ::std::hash; Vector mDirectories; WString mDevice; WString mFilename; WString mNode; bool mIsAbsolute; }; /** @cond SPECIALIZATIONS */ /** * RTTIPlainType specialization for Path that allows paths be serialized as value types. * * @see RTTIPlainType */ template<> struct RTTIPlainType { enum { id = TID_Path }; enum { hasDynamicSize = 1 }; static void toMemory(const Path& data, char* memory) { UINT32 size = getDynamicSize(data); memcpy(memory, &size, sizeof(UINT32)); memory += sizeof(UINT32); memory = rttiWriteElem(data.mDevice, memory); memory = rttiWriteElem(data.mNode, memory); memory = rttiWriteElem(data.mFilename, memory); memory = rttiWriteElem(data.mIsAbsolute, memory); memory = rttiWriteElem(data.mDirectories, memory); } static UINT32 fromMemory(Path& data, char* memory) { UINT32 size; memcpy(&size, memory, sizeof(UINT32)); memory += sizeof(UINT32); memory = rttiReadElem(data.mDevice, memory); memory = rttiReadElem(data.mNode, memory); memory = rttiReadElem(data.mFilename, memory); memory = rttiReadElem(data.mIsAbsolute, memory); memory = rttiReadElem(data.mDirectories, memory); return size; } static UINT32 getDynamicSize(const Path& data) { UINT64 dataSize = rttiGetElemSize(data.mDevice) + rttiGetElemSize(data.mNode) + rttiGetElemSize(data.mFilename) + rttiGetElemSize(data.mIsAbsolute) + rttiGetElemSize(data.mDirectories) + sizeof(UINT32); #if BS_DEBUG_MODE if (dataSize > std::numeric_limits::max()) { __string_throwDataOverflowException(); } #endif return (UINT32)dataSize; } }; /** @endcond */ /** @} */ } /** @cond STDLIB */ namespace std { /** Hash value generator for Path. */ template<> struct hash { size_t operator()(const bs::Path& path) const { size_t hash = 0; bs::hash_combine(hash, path.mFilename); bs::hash_combine(hash, path.mDevice); bs::hash_combine(hash, path.mNode); for (auto& dir : path.mDirectories) bs::hash_combine(hash, dir); return hash; } }; } /** @endcond */