// MOPathString.m
//
// by Mike Ferris
// Part of MOKit
// Copyright 1993, all rights reserved.

// ABOUT MOKit
// by Mike Ferris (mike@lorax.com)
//
// MOKit is a collection of useful and general objects.  Permission is 
// granted by the author to use MOKit in your own programs in any way 
// you see fit.  All other rights pertaining to the kit are reserved by the 
// author including the right to sell these objects as objects,  as part 
// of a LIBRARY, or as SOURCE CODE.  In plain English, I wish to retain 
// rights to these objects as objects, but allow the use of the objects 
// as pieces in a fully functional program.  Permission is also granted to 
// redistribute the source code of MOKit for FREE as long as this copyright 
// notice is left intact and unchanged.  NO WARRANTY is expressed or implied.  
// The author will under no circumstances be held responsible for ANY 
// consequences from the use of these objects.  Since you don't have to pay 
// for them, and full source is provided, I think this is perfectly fair.

#import "MOPathString.h"
#import <objc/objc-runtime.h>

#define CLASS_VERSION				0
#define CLASS_NAME					"MOPathString"

#define DEFAULT_PATH_SEPARATOR		'/'
#define DEFAULT_EXT_SEPARATOR		'.'

@implementation MOPathString

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=- Initializing  the class -=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

+ initialize
// Set our version.
{
	if (self == objc_lookUpClass(CLASS_NAME))  {
		[self setVersion:CLASS_VERSION];
	}
	return self;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=- Initializing  paths -=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- initPath:(const char *)path
// Identical to -initStringValue:path.
{
	return [self initStringValue:path];
}

- initDirectory:(const char *)dir file:(const char *)file
// Identical to -initFromFormat:"%s/%s, dir, file.  Use the 
// DEFAULT_PATH_SEPARATOR because the instance variable won't be set 
// up yet, and when it is it will be set to the default value anyway.
{
	return [self initFromFormat:"%s%c%s", dir, DEFAULT_PATH_SEPARATOR, file];
}

- initStringValueNoCopy:(char *)s shouldFree:(BOOL)flag
// This is the designated initializer for this class.
{
	[super initStringValueNoCopy:s shouldFree:flag];
	pathSeparator = DEFAULT_PATH_SEPARATOR;
	extSeparator = DEFAULT_EXT_SEPARATOR;
	return self;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-= Setting paths =-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- setPath:(const char *)path
// Identical to -setStringValue:path
{
	return [self setStringValue:path];
}

- setDirectory:(const char *)dir file:(const char *)file
// Identical to -setFromFormat:"%s/%s, dir, file.
{
	return [self setFromFormat:"%s%c%s", dir, pathSeparator, file];
}

- setPathSeparator:(char)c
// Set the character used as the path component separator.
{
	pathSeparator = c;
	return self;
}

- (char)pathSeparator
// Return the character used as the path component separator.
{
	return pathSeparator;
}

- setExtensionSeparator:(char)c
// Set the character used as the file extension separator.
{
	extSeparator = c;
	return self;
}

- (char)extensionSeparator
// Return the character used as the file extension separator.
{
	return extSeparator;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=- Accessing important parts of a path -=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- (const char *)path
// Identical to -stringValue.
{
	return [self stringValue];
}

- directory
// If there is an occurrence of pathSeparator in the string, this 
// returns the substring to the left of the last occurrence of 
// pathSeparator (not including the final pathSeparator).
// Otherwise it returns an empty string.  Returned substring objects are
// the receiving object's class and must be freed by the caller.
{
	int index = [self positionOf:pathSeparator nthOccurrence:-1];
	
	if (index == -1)  {
		return [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}
	
	if (index == 0)  {
		// it's a single pathSeparator, so we return an empty string.
		return [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}
	return [self substringFrom:0 to:index-1];
}

- file
// If there is an occurrence of pathSeparator in the string, this 
// returns the substring to the right of the last occurrence of 
// pathSeparator (not including the pathSeparator).
// Otherwise it returns a copy of the whole string.  If the pathSeparator 
// is the last character in the string, this returns an empty string.
// Returned substring objects are the receiving object's class and must be 
// freed by the caller.
{
	int index = [self positionOf:pathSeparator nthOccurrence:-1];
	
	if (index == -1)  {
		return [[[self class] allocFromZone:[self zone]] 
					initStringValue:[self stringValue]];
	}
	
	if (index == [self length] - 1)  {
		// string ends in pathSeparator
		return [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}
	return [self substringFrom:index+1 to:[self length]-1];
}

- fileExtension
// If the file (as returned by -file) has an occurrence of extSeparator, 
// this returns the substring to the right of the last occurrence of 
// extSeparator (not including the separator).  Otherwise it returns an
// empty string.
// Returned substring objects are the receiving object's class and must be 
// freed by the caller.
{
	id file = [self file];
	id ret = nil;
	int index = [file positionOf:extSeparator nthOccurrence:-1];
	
	if (index == -1)  {
		ret = [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}  else if (index == [self length]-1)  {
		// file ends in extSeparator.
		ret = [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}  else  {
		ret = [file substringFrom:index+1 to:[file length]-1];
	}
	[file free];
	return ret;
}

- fileBasename
// If the file (as returned by -file) has an occurrence of extSeparator, 
// this returns the substring to the left of the last occurrence of 
// extSeparator (not including the separator).  Otherwise it returns a 
// copy of the file.
// Returned substring objects are the receiving object's class and must be 
// freed by the caller.
{
	id file = [self file];
	id ret = nil;
	int index = [file positionOf:extSeparator nthOccurrence:-1];
	
	if (index == -1)  {
		ret = [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}  else if (index == 0)  {
		// file begins with extSeparator.
		ret = [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}  else  {
		ret = [file substringFrom:0 to:index-1];
	}
	[file free];
	return ret;
}

- (int)numberOfComponents
// Returns the number of components (that is the number of substrings 
// defined by breaking the string at pathSeparators).
{
	return [self countOccurrencesOf:pathSeparator] + 1;
}

- componentAt:(int)index
// Components are numbered from 0 to [self numberOfComponents]-1.
// Returned objects are the receiving object's class and must be freed 
// by the caller.
{
	int start, end;
	id ret;
	
	if ((index < 0) || (index > [self numberOfComponents]-1))  {
		return nil;
	}
	start = [self positionOf:pathSeparator nthOccurrence:index];
	if (start == -1)  {
		start = 0;
	}  else  {
		start++;
	}
	end = [self positionOf:pathSeparator nthOccurrence:index+1];
	if (end == -1)  {
		end = [self length]-1;
	}  else  {
		end--;
	}
	
	ret = [self substringFrom:start to:end];
	if (ret == nil)  {
		ret = [[[self class] allocFromZone:[self zone]] initStringValue:""];
	}
	
	return ret;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-= Testing  the file =-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- (BOOL)isRelative
// Returns YES if the path is non-empty and does NOT begin with pathSeparator.
{
	const char *strVal = [self stringValue];
	
	if ((!strVal) || (!*strVal))  {
		return NO;
	}
	return (*strVal != pathSeparator);
}

- (BOOL)isAbsolute
// Returns YES if the path is non-empty and DOES begin with pathSeparator.
{
	const char *strVal = [self stringValue];
	
	if ((!strVal) || (!*strVal))  {
		return NO;
	}
	return (*strVal == pathSeparator);
}

- (BOOL)doesExistInFileSystem
// Returns YES if the a file exists at our path, and is visible to the user 
// id of the process.  A NO can mean any number of things, but a YES 
// definitely indicates that the file is there and visible.
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return YES;
}

- (BOOL)isDirectory
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a directory.  A NO can mean any number of things, 
// but a YES definitely indicates that the file is there and visible and 
// a directory.  
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return ((statBuff.st_mode & S_IFDIR) == S_IFDIR);
}

- (BOOL)isPlainFile
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a plain file.  A NO can mean any number of things, 
// but a YES definitely indicates that the file is there and visible and 
// a plain file.  
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return ((statBuff.st_mode & S_IFREG) == S_IFREG);
}

- (BOOL)isSymbolicLink
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a symlink.  A NO can mean any number of things, 
// but a YES definitely indicates that the file is there and visible and 
// a symlink.  
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return ((statBuff.st_mode & S_IFLNK) == S_IFLNK);
}

- (BOOL)isCharacterSpecial
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a character special file.  A NO can mean any 
// number of things, but a YES definitely indicates that the file is 
// there and visible and a character special file.  
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return ((statBuff.st_mode & S_IFCHR) == S_IFCHR);
}

- (BOOL)isBlockSpecial
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a block special file.  A NO can mean any 
// number of things, but a YES definitely indicates that the file is 
// there and visible and a block special file.  
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return ((statBuff.st_mode & S_IFBLK) == S_IFBLK);
}

- (BOOL)isSocket
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a socket file.  A NO can mean any 
// number of things, but a YES definitely indicates that the file is 
// there and visible and a socketspecial file.  
{
	struct stat statBuff;
	
	if (stat([self stringValue], &statBuff) == -1)  {
		return NO;
	}
	
	return ((statBuff.st_mode & S_IFSOCK) == S_IFSOCK);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-= Archiving methods =-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- write:(NXTypedStream *)typedStream
// Archive the path to a typed stream.
{
	[super write:typedStream];
	NXWriteTypes(typedStream, "cc", &pathSeparator, &extSeparator);
	return self;
}

- read:(NXTypedStream *)typedStream
// int NXTypedStreamClassVersion(NXTypedStream *typedStream, 
// 				const char *className) 
// can be used to handle multi version reading and writing.
{
	int classVersion;

	[super read:typedStream];
	
	classVersion = NXTypedStreamClassVersion(typedStream, CLASS_NAME);
	
	switch (classVersion)  {
		case 0:		// First version.
			NXReadTypes(typedStream, "cc", &pathSeparator, &extSeparator);
			break;
		default:
			NXLogError("[%s read:] class version %d cannot read "
						"instances archived with version %d", 
						CLASS_NAME, CLASS_VERSION, classVersion);
			pathSeparator = DEFAULT_PATH_SEPARATOR;
			extSeparator = DEFAULT_EXT_SEPARATOR;
			break;
	}
	return self;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-= NXTransport  protocol =-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

- encodeUsing:(id <NXEncoding>)portal
// Encode the string for transmission across a distributed object connection.
{
	if ([super respondsTo:@selector(encodeUsing:)])  {
		[super encodeUsing:portal];
	}
	[portal encodeData:&pathSeparator ofType:"c"];
	[portal encodeData:&extSeparator ofType:"c"];
	return self;
	
}

- decodeUsing:(id <NXDecoding>)portal
// Decode the data sent over the distributed object connection into 
// the receiving MOString.
{
	if ([super respondsTo:@selector(decodeUsing:)])  {
		[super decodeUsing:portal];
	}  else  {
		[self init];
	}
	[portal decodeData:&pathSeparator ofType:"c"];
	[portal decodeData:&extSeparator ofType:"c"];
	return self;
}

@end
