passage/minorGems/io/file/Path.h
2025-10-03 02:19:59 -04:00

615 lines
15 KiB
C++

/*
* Modification History
*
* 2001-February-12 Jason Rohrer
* Created.
*
* 2001-May-11 Jason Rohrer
* Added a version of getPathString that
* returns a '\0' terminated string.
*
* 2001-September-21 Jason Rohrer
* Added a missing include.
*
* 2001-September-23 Jason Rohrer
* Added a copy function.
* Made some comments more explicit.
* Changed the constructor to allow for const path step strings.
*
* 2001-November-3 Jason Rohrer
* Added a function for appending a string to a path.
* Changed the interface to the main constructor.
*
* 2002-March-29 Jason Rohrer
* Added Fortify inclusion.
*
* 2002-April-11 Jason Rohrer
* Fixed a variable scoping bug.
*
* 2002-July-2 Jason Rohrer
* Fixed a major memory leak in copy().
*
* 2002-August-1 Jason Rohrer
* Added support for path truncation.
* Added support for parsing platform-dependent path strings.
*
* 2003-May-29 Jason Rohrer
* Fixed a bug when an extra delimeters are at the end of the path.
* Fixed a bug when string path consists only of root.
*
* 2003-June-2 Jason Rohrer
* Fixed a bug in absolute path detection.
* Added platform-specific functions for root and absolute path detection.
* Fixed a memory bug when string path contains root only.
* Fixed a path step bug when path is root.
* Fixed bugs in truncate and append when non-default root string is used.
*
* 2005-August-29 Jason Rohrer
* Fixed an uninitialized variable warning.
*
* 2010-May-14 Jason Rohrer
* String parameters as const to fix warnings.
*/
#include "minorGems/common.h"
#ifndef PATH_CLASS_INCLUDED
#define PATH_CLASS_INCLUDED
#include <string.h>
#include "minorGems/util/stringUtils.h"
#ifdef FORTIFY
#include "minorGems/util/development/fortify/fortify.h"
#endif
/**
* Platform-independent file path interface. Contains
* all of path except for file name. Thus, appending
* a file name to the path will produce a complete file path.
*
* E.g., on Linux, file path:
* temp/files/
* file name:
* test.txt
* full path:
* temp/files/test.txt
*
* @author Jason Rohrer
*/
class Path {
public:
/**
* Constructs a path.
*
* @param inPathSteps an array of c-strings representing
* each step in the path, with no delimeters.
* For example, { "temp", "files" } to represent
* the linux path temp/files.
* Must be destroyed by caller since copied internally.
* @param inNumSteps the number of strings in the path.
* @param inAbsolute set to true to make this an absolute
* path. For example, in Linux, an absolute path
* is one that starts with '/', as in /usr/include/.
* The effects of inAbsolute vary by platform.
* @param inRootString the root string for this path if it
* is absolute, or NULL to specify a default root.
* Defaults to NULL.
* Must be destroyed by caller if non-NULL.
*/
Path( char **inPathSteps, int inNumSteps, char inAbsolute,
char *inRootString = NULL );
/**
* Constructs a path by parsing a platform-dependent path string.
*
* @param inPathSteps a \0-terminated string representing the path.
* Must be destroyed by caller.
*/
Path( const char *inPathString );
~Path();
/**
* Returns a complete, platform-dependent string path.
*
* @param outLength pointer to where the path length, in
* characters, will be returned.
*
* @return a new char array containing the path. Note
* that the string is not terminated by '\0'. Must
* be destroyed by the caller.
*/
char *getPathString( int *outLength );
/**
* Returns a complete, platform-dependent string path, terminated
* bye '\0'.
*
* @return a new char array containing the path. Note
* that the string IS terminated by '\0'. Must
* be destroyed by the caller.
*/
char *getPathStringTerminated();
/**
* Gets the platform-specific path delimeter.
*
* Note that this function is implemented separately for
* each supported platform.
*
* @return the path delimeter.
*/
static char getDelimeter();
/**
* Gets start characters for an absolute path.
*
* Note that this function is implemented separately for
* each supported platform.
*
* @param outLength pointer to where the string length, in
* characters, will be returned.
*
* @return the absolute path start string characters. For
* example, on Linux, this would be the string "/".
* Must be destroyed by the caller.
*/
static char *getAbsoluteRoot( int *outLength );
/**
* Gets whether a path string is absolute.
*
* Note that this function is implemented separately for
* each supported platform.
*
* @param inPathString the string to check.
* Must be destroyed by caller if non-const.
*
* @return true if the string is absolute, or false otherwise.
*/
static char isAbsolute( const char *inPathString );
/**
* Extracts the root string from a path string.
*
*
* @param inPathString the string to check.
* Must be destroyed by caller if non-const.
*
* @return the root string, or NULL if inPathString is not
* absolute. Must be destroyed by caller if non-NULL.
*/
static char *extractRoot( const char *inPathString );
/**
* Gets whether a path string is a root path.
*
* Note that this function is implemented separately for
* each supported platform. For example, on Unix, only "/"
* is the root path, while on Windows, both "c:\" and "d:\" might
* be root paths.
*
* @param inPathString the string to check.
* Must be destroyed by caller if non-const.
*
* @return true if the string is a root string, or false otherwise.
*/
static char isRoot( const char *inPathString );
/**
* Gets start string for an absolute path.
*
* @return the absolute path start string in \0-terminated form.
* Must be destroyed by the caller.
*/
static char *getAbsoluteRootString();
/**
* Copies this path.
*
* @return a new path that is a deep copy of this path.
*/
Path *copy();
/**
* Constructs a new path by appending an additional
* step onto this path.
*
* @param inStepString the step to add to this path.
* Must be destroyed by caller if non-const.
*
* @return a new path with the extra step.
* Must be destroyed by caller.
*/
Path *append( const char *inStepString );
/**
* Constructs a new path by removing the last step from this path.
*
* @return a new path, or NULL if there is only one step in this path.
* Must be destroyed by caller.
*/
Path *truncate();
/**
* Gets the last step in this path.
*
* @return the last step. Must be destroyed by caller.
*/
char *getLastStep();
private:
char **mPathSteps;
int mNumSteps;
int *mStepLength;
char mAbsolute;
// the root string of this path, if it is absolute
char *mRootString;
};
inline Path::Path( char **inPathSteps, int inNumSteps,
char inAbsolute, char *inRootString )
: mNumSteps( inNumSteps ), mAbsolute( inAbsolute ),
mRootString( NULL ) {
if( inRootString != NULL ) {
mRootString = stringDuplicate( inRootString );
}
// copy the path steps
mPathSteps = new char*[ mNumSteps ];
mStepLength = new int[ mNumSteps ];
for( int i=0; i<mNumSteps; i++ ) {
int stepLength = strlen( inPathSteps[i] );
mPathSteps[i] = new char[ stepLength + 1 ];
memcpy( mPathSteps[i], inPathSteps[i], stepLength + 1 );
mStepLength[i] = stepLength;
}
}
inline Path::Path( const char *inPathString ) {
mAbsolute = isAbsolute( inPathString );
char *pathStringCopy = stringDuplicate( inPathString );
char delimeter = getDelimeter();
char *delimString = new char[ 2 ];
delimString[0] = delimeter;
delimString[1] = '\0';
char *pathRootSkipped;
if( !mAbsolute ) {
mRootString = NULL;
pathRootSkipped = pathStringCopy;
}
else {
// root occurs at start of path string
mRootString = extractRoot( inPathString );
pathRootSkipped = &( pathStringCopy[ strlen( mRootString ) ] );
}
// remove any trailing delimeters, if they exist
while( pathRootSkipped[ strlen( pathRootSkipped ) - 1 ] == delimeter ) {
pathRootSkipped[ strlen( pathRootSkipped ) - 1 ] = '\0';
}
char *currentDelimPointer = strstr( pathRootSkipped, delimString );
if( currentDelimPointer != NULL ) {
// first, count the delimeters
int delimCount = 0;
while( currentDelimPointer != NULL ) {
if( strlen( currentDelimPointer ) > 1 ) {
// don't count tail end delimeters
delimCount++;
}
currentDelimPointer = strstr( &( currentDelimPointer[1] ),
delimString );
}
// no delimeter at end of path
mNumSteps = delimCount + 1;
mPathSteps = new char*[ mNumSteps ];
mStepLength = new int[ mNumSteps ];
// now extract the chars between delimeters as path steps
currentDelimPointer = strstr( pathRootSkipped, delimString );
int stepIndex = 0;
currentDelimPointer[0] = '\0';
mPathSteps[ stepIndex ] = stringDuplicate( pathRootSkipped );
mStepLength[ stepIndex ] = strlen( mPathSteps[ stepIndex ] );
stepIndex++;
while( currentDelimPointer != NULL ) {
char *nextDelimPointer = strstr( &( currentDelimPointer[1] ),
delimString );
if( nextDelimPointer != NULL ) {
nextDelimPointer[0] = '\0';
}
mPathSteps[ stepIndex ] =
stringDuplicate( &( currentDelimPointer[1] ) );
mStepLength[ stepIndex ] = strlen( mPathSteps[ stepIndex ] );
stepIndex++;
currentDelimPointer = nextDelimPointer;
}
}
else {
// no delimeters
if( strlen( pathRootSkipped ) > 0 ) {
mNumSteps = 1;
mPathSteps = new char*[1];
mPathSteps[0] = stringDuplicate( pathRootSkipped );
mStepLength = new int[1];
mStepLength[0] = strlen( mPathSteps[0] );
}
else {
// path with root only
mNumSteps = 0;
mPathSteps = new char*[0];
mStepLength = new int[0];
}
}
delete [] delimString;
delete [] pathStringCopy;
}
inline Path::~Path() {
// delete each step
for( int i=0; i<mNumSteps; i++ ) {
delete [] mPathSteps[i];
}
delete [] mPathSteps;
delete [] mStepLength;
if( mRootString != NULL ) {
delete [] mRootString;
}
}
inline char *Path::getPathString( int *outLength ) {
int length = 0;
// length = sum( length each step string + 1 )
// ( + 1 is for the delimeter that occurs after each step string )
int i;
for( i=0; i<mNumSteps; i++ ) {
length += mStepLength[i] + 1;
}
// if absolute, we need to add in the length of the root
char *rootString = NULL;
int rootLength = 0;
if( mAbsolute ) {
if( mRootString != NULL ) {
rootString = stringDuplicate( mRootString );
rootLength = strlen( mRootString );
}
else {
rootString = getAbsoluteRoot( &rootLength );
}
length += rootLength;
}
char *returnString = new char[ length ];
int index = 0;
if( rootString != NULL ) {
// write root into string
memcpy( &( returnString[index] ), rootString, rootLength );
index += rootLength;
delete [] rootString;
}
char delimeter = getDelimeter();
// write each step into the string
for( i=0; i<mNumSteps; i++ ) {
memcpy( &( returnString[index] ), mPathSteps[i], mStepLength[i] );
index += mStepLength[i];
returnString[ index ] = delimeter;
index++;
}
*outLength = length;
return returnString;
}
inline char *Path::getPathStringTerminated() {
int length;
char *pathString = getPathString( &length );
char *delimitedPathString = new char[ length + 1 ];
memcpy( delimitedPathString, pathString, length );
delimitedPathString[ length ] = '\0';
delete [] pathString;
return delimitedPathString;
}
inline Path *Path::copy() {
// the steps will be copied internally
return new Path( mPathSteps, mNumSteps, mAbsolute, mRootString );
}
inline Path *Path::append( const char *inStepString ) {
char **newPathSteps = new char*[ mNumSteps + 1 ];
// shallow copy, since the strings themselves
// are copied in the Path constructor below
for( int i=0; i<mNumSteps; i++ ) {
newPathSteps[i] = mPathSteps[i];
}
// append final step
newPathSteps[ mNumSteps ] = (char*)inStepString;
Path *newPath = new Path( newPathSteps, mNumSteps + 1, mAbsolute,
mRootString );
// shallow delete, because of shallow copy above
delete [] newPathSteps;
return newPath;
}
inline Path *Path::truncate() {
if( mNumSteps < 2 && !mAbsolute ) {
return NULL;
}
else if( mNumSteps < 1 ) {
return NULL;
}
char **newPathSteps = new char*[ mNumSteps ];
// shallow copy, since the strings themselves
// are copied in the Path constructor below
for( int i=0; i<mNumSteps-1; i++ ) {
newPathSteps[i] = mPathSteps[i];
}
Path *newPath = new Path( newPathSteps, mNumSteps - 1, mAbsolute,
mRootString );
// shallow delete, because of shallow copy above
delete [] newPathSteps;
return newPath;
}
inline char *Path::getLastStep() {
if( mNumSteps >= 1 ) {
return stringDuplicate( mPathSteps[ mNumSteps - 1 ] );
}
else {
if( mAbsolute ) {
if( mRootString != NULL ) {
return stringDuplicate( mRootString );
}
else {
return getAbsoluteRootString();
}
}
else {
// no path steps and not absolute...
return stringDuplicate( "" );
}
}
}
inline char *Path::getAbsoluteRootString() {
int rootLength;
char *root = getAbsoluteRoot( &rootLength );
char *rootString = new char[ rootLength + 1 ];
strncpy( rootString, root, rootLength );
// strncopy won't add termination if length limit reached
rootString[ rootLength ] = '\0';
delete [] root;
return rootString;
}
#endif