//
// OBJNAME.CPP
//
//  Source file for ArchiveLib 1.0
//
//  Copyright (c) Greenleaf Software, Inc. 1994
//  All Rights Reserved
//
// CONTENTS
//
//  ALName::operator new()
//  ALName::operator+()
//  ALName::ALName( const char *, ALCase )
//  ALName::ALName( const ALName & )
//  ALName::operator=( const char * )
//  ALName::operator = ( const ALName & )
//  ALName::~ALName()
//  ALName::ChangeExtension()
//  ALName::ChangeTrailingChar()
//  ALName::GetSafeName()
//  ALName::GetSafeOldName()
//  ALName::operator char *()
//  ALName::StripFileName()
//  ALName::StripPath()
//  ALName::WildCardMatch()
//  ALName::Strcpy()
//
// DESCRIPTION
//
//  This file contains all the source code to support class ALName. 
//  Class ALName doesn't really do much outside of ALStorage, where
//  it shows up as the mName data member.  It does make cameo appearances
//  elsewhere, such as in the wild card expansion code, but those
//  are pretty limited.
//
// REVISION HISTORY
//
//  May 26, 1994  1.0A  : First release
//
//

#include "arclib.h"
#pragma hdrstop

#include <string.h>
#include <ctype.h>

#include "_match.h"

//
// void * ALName::operator new( size_t size )
//
// ARGUMENTS:
//
//  size  :  The number of bytes needed to create a new ALName object.
//
// RETURNS
//
//  A pointer to the newly allocated storage area, or 0 if no storage
//  was available.
//
// DESCRIPTION
//
//  When using a DLL, it is easy to get into a dangerous situation when 
//  creating objects whose ctor and dtor are both in the DLL.  The problem
//  arises because when you create an object using new, the memory for
//  the object will be allocated from the EXE.  However, when you destroy
//  the object using delete, the memory is freed inside the DLL.  Since
//  the DLL doesn't really own that memory, bad things can happen.
//
//  But, you say, won't the space just go back to the Windows heap regardless
//  of who tries to free it?  Maybe, but maybe not.  If the DLL is using 
//  a subsegment allocation scheme, it might do some sort of local free
//  before returning the space to the windows heap.  That is the point where
//  you could conceivably cook your heap.
//
//  By providing our own version of operator new inside this class, we
//  ensure that all memory allocation for the class will be done from
//  inside the DLL, not the EXE calling the DLL.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

#if defined( AL_BUILDING_DLL )
void AL_DLL_FAR * AL_PROTO ALName::operator new( size_t size )
{
    return ::new char[ size ];
}
#endif

//
// ALName ALName::operator+( const char *rhs )
//
// ARGUMENTS:
//
//  rhs  : The character pointer that is going to be added to the
//         ALName object.  This will often be the string component
//         of another ALName object, cast to type const char *.
//
// RETURNS
//
//  A newly created ALName object.  This disappears quickly, but can
//  be copied into a result object using either the assignment operator
//  or the copy constructor.
//
// DESCRIPTION
//
//  This is one of those C++ functions that makes converts out of C
//  programmers.  It allows me to add two strings together to create
//  a third.  I really like that. 
//
//  The implementation is pretty easy.  I allocate a new character buffer of
//  the correct length, the copy the two strings into it.  I use this
//  result as the initializer for a new ALName object, and return that.
//
//  Note that the new string will have the same case sensitivity as this.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

ALName ALName::operator+( const char AL_DLL_FAR *rhs )
{
    int l1 = ( rhs ) ? strlen( rhs ) : 0;
    int l2 = ( mszName ) ? strlen( mszName ) : 0;
    char *p = new char[ l1 + l2 + 1 ];
    if ( p ) {
        strcpy( p, mszName );
        if ( rhs )
            strcat( p, rhs );
    }
    ALName result( p, mCase );
    if ( p )
        delete p;
    return result;
}


//
// ALName::ALName( const char *s = "", ALCase name_case = AL_MIXED )
//
// ARGUMENTS:
//
//  s        : The initial string value of the new object.  Note that
//             you can pass a null pointer here and all will still be okay.
//
//  name_case: The case sensitivity of the new ALName object.  Will its
//             contents be mixed, or will it always be forced to upper
//             or lower.
//
// RETURNS
//
//  Nothing, a constructor.
//
// DESCRIPTION
//
//  This constructor first initializes the mCase member in an initializer.
//  mCase is a const member, which is nice, because you can make it public.
//  But, it means you can't initialize it *in* the constructor, you have 
//  to do it before the body.
//
//  Things are pretty easy after that.  We allocate enough space to hold
//  the initializer string and copy it in.  The old name gets set to 0, since
//  this name hasn't been around long enough to have been renamed.
//  And that's it.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

AL_PROTO ALName::ALName( const char AL_DLL_FAR *s /* = "" */, 
                         ALCase name_case /* = AL_MIXED */ )
     : mCase( name_case )
{
    mszName = new AL_DLL_FAR char[ strlen( s ) + 1 ];
    if ( mszName )
        Strcpy( s );
     mszOldName = 0;
}

//
// ALName::ALName( const ALName &rhs )
//
// ARGUMENTS:
//
//  rhs   :  A reference to another ALName object.  This is the ALName
//           we are going to copy.
//
// RETURNS
//
//  Nothing, a constructor.
//
// DESCRIPTION
//
//  This is the copy constructor.  It is very nearly the same as the
//  other constructor.
//
//  This constructor first initializes the mCase member in an initializer.
//  mCase is a const member, which is nice, because you can make it public.
//  But, it means you can't initialize it *in* the constructor, you have 
//  to do it before the body.
//
//  Things are pretty easy after that.  We allocate enough space to hold
//  a copy of the string in the rhs, and then copy it.  The old name gets 
//  set to 0, since this name hasn't been around long enough to have been 
//  renamed.  We could have copied the old name from the rhs, but I think
//  this way makes more sense.
//  And that's it.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

AL_PROTO ALName::ALName( const ALName AL_DLL_FAR &rhs )
     : mCase( rhs.mCase )
{
     const char *s = rhs.GetSafeName();
     mszName = new AL_DLL_FAR char[ strlen( s ) + 1 ];
     if ( mszName )
          Strcpy( s );
     mszOldName = 0;
}

//
// ALName & ALName::operator = ( const char * rhs )
//
// ARGUMENTS:
//
//  rhs  :  This is the character string that we are going to assign to this.
//
// RETURNS
//
//  A reference to this.  We need to do it that way so we can do:
//
//    a = b = c = "test";
//
// DESCRIPTION
//
//  This function performs roughly the same function as the first
//  constructor, but there is a twist.  When we assign a new name to
//  an ALName object, we also make a copy of fit and place it in
//  the old name data member.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

ALName AL_DLL_FAR & AL_PROTO ALName::
operator = ( const char AL_DLL_FAR * rhs )
{
    if ( rhs == 0 )
        rhs = "";
    if ( rhs == mszName ) // Pathological?
        return *this;

    char AL_DLL_FAR *new_name = new AL_DLL_FAR char[ strlen( rhs ) + 1 ];
    if ( new_name ) {
        if ( mszOldName )
            delete[] mszOldName;
        mszOldName = mszName;
        mszName = new_name;
        Strcpy( rhs );
    } else {
        if ( mszOldName )
            delete[] mszOldName;
        mszOldName = mszName;
        mszName = 0;
    }
    return *this;
}

//
// ALName & ALName::operator = ( const ALName & rhs )
//
// ARGUMENTS:
//
//  rhs   :  The right hand side of the assignment operator.
//
// RETURNS
//
//  A reference to this.  Like in the above function, we do this so
//  that you can stack assignments:
//
//      a = b = c = "Dummy";
//
// DESCRIPTION
//
//
//  This function is very similar to the copy constructor, but it has
//  one additional twist.  When we copy the rhs string value into our
//  string, we move our old name into the backup copy member, 
//  mszOldName.  That way, if we change our mind, we can easily
//  switch back to the old name.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//
ALName AL_DLL_FAR & AL_PROTO ALName::
operator = ( const ALName AL_DLL_FAR & rhs )
{
    return *this = rhs.GetName();
}

//
// ALName::~ALName()
//
// ARGUMENTS:
//
//  None, this is a destructor.
//
// RETURNS
//
//  None, destructor.
//
// DESCRIPTION
//
//  All this function has to do is free up the two pieces of 
//  dynamic memory.  I just can't get out of that old C habit of
//  checking a pointer for NULL before deleting it.
//
//  In debug mode, I check the state of this before and after freeing
//  the dynamically allocated memory, in hopes of catching any
//  heap errors near their source.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

AL_PROTO ALName::~ALName()
{
    AL_ASSERT( GoodTag(), "~ALName: attempt to delete invalid object" );
    if ( mszName )
        delete[] mszName;
    if ( mszOldName )
        delete[] mszOldName;
    AL_ASSERT( GoodTag(), "~ALName: attempt to delete invalid object" );
}

//
// ALName & ALName::ChangeExtension( const char *new_extension = ".bak" )
//
// ARGUMENTS:
//
//  new_extension  :  The new extension you want to apply to the name.
//
// RETURNS
//
//  A reference to this.  This is nice, because it lets you do things
//  like this:
//
//     fopen( name.ChangeExtension( ".OBJ" ), "rb" );
//
// DESCRIPTION
//
//  This function is used to change the extension of a filename stored
//  in an ALName object.  A lot of times you will want to do this
//  in order to create a backup.  For example, you could change
//  TEMP.DAT to TEMP.BAK.
//
//  This function makes a copy of the current name in mszOldName, so we
//  can keep track of it later.  It then searches for the '.' character
//  in the new file name, and sticks the new extension there.
//
//  If you apply this function to a filename, you can then rename the file
//  by calling ALStorage::Rename() with no argument.  When you do this, 
//  the rename function uses the saved OldName and current name as its
//  arguments for the rename() function.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

//
// I need to fix this code up to handle extensions with and without a
// leading '.' character.  Maybe you can use a leading character
// other than '.' to indicate a different type of extension?
//

ALName AL_DLL_FAR & AL_PROTO ALName::
ChangeExtension( const char AL_DLL_FAR *new_extension /* = ".bak" */ )
{
    AL_ASSERT( new_extension != 0, "ChangeExtension: new extension is null" );
    AL_ASSERT( mszName, "ChangeExtension: current name is null" );

    char *file_name = new char[ strlen( mszName ) + strlen( new_extension ) + 1 ];
    if ( mszOldName )
        delete[] mszOldName;
    mszOldName = mszName;
    mszName = file_name;

    if ( !file_name )
        return *this;
    strcpy( mszName, mszOldName );
    char *p = strrchr( file_name, '.' );
    if ( p )
        strcpy( p, new_extension );
    else
        strcat( mszName, new_extension );
    switch ( mCase ) {
        case AL_UPPER : strupr( mszName ); break;
        case AL_LOWER : strlwr( mszName ); break;
    }
    return *this;
}

//
// ALName & ALName::ChangeTrailingChar( char new_char = '@' )
//
// ARGUMENTS:
//
//  new_char  :  The new character to use as the last name of the file
//               name.
//
// RETURNS
//
//  A reference to this.  This is nice, because it lets you do things
//  like this:
//
//     fopen( name.ChangeTrailingChar(), "rb" );
//
// DESCRIPTION
//
//  This function is used to change the the last character of the extension
//  in filename stored in an ALName object.  A lot of times you will want to 
//  do this in order to create a backup.  For example, you could change
//  TEMP.DAT to TEMP.DA@.
//
//  This function makes a copy of the current name in mszOldName, so we
//  can keep track of it later.  It then searches for the end of the
//  current file name, and changes it.  
//
//  Note that if the filename doesn't have an extension, we do something
//  funny.  Instead of just changing the last character, we create a 
//  new extension, and append that instead.  So if the filename is
//  "TEST", the new name will be "TEST.@".
//
//  If you apply this function to a filename, you can then rename the file
//  by calling ALStorage::Rename() with no argument.  When you do this, 
//  the rename function uses the saved OldName and current name as its
//  arguments for the rename() function.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

ALName AL_DLL_FAR & AL_PROTO ALName::
ChangeTrailingChar( char new_char /* = '@' */ )
{
    AL_ASSERT( mszName != 0, "ChangeTrailingChar: current name is null" );

    char *file_name = new char[ strlen( mszName ) + 2 ];
    if ( mszOldName )
        delete[] mszOldName;
    mszOldName = mszName;
    mszName = file_name;
    switch ( mCase ) {
        case AL_UPPER : new_char = (char) toupper( new_char ); break;
        case AL_LOWER : new_char = (char) tolower( new_char ); break;
    }
    if ( !file_name )
        return *this;
    strcpy( mszName, mszOldName );
    char *p;
    if ( ( p = strrchr( mszName, '.' ) ) != 0 ) {
        if ( p[ 1 ] == '0' ) {
            p[ 1 ] = new_char;
            p[ 2 ] = '\0';
        } else
            mszName[ strlen( mszName ) - 1 ] = new_char;
    } else {
        char new_extension[ 3 ];
        new_extension[ 0 ] ='.';
        new_extension[ 1 ] = new_char;
        new_extension[ 2 ] = 0;
        strcat( mszName, new_extension );
    }
    return *this;
}

//
// const char * ALName::GetSafeName() const
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  This function normally returns the value of mszName.  However, if
//  mszName is currently a null pointer, we return a pointer to an
//  empty string instead.  This means you can use the return value from
//  this function anywhere you want without checking for its NULLity.
//
// DESCRIPTION
//
//  See above.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

const char AL_DLL_FAR * AL_PROTO ALName::GetSafeName() const
{
    if ( mszName )
        return mszName;
    else
        return "";
}

//
// const char * ALName::GetSafeOldName() const
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  This function normally returns the value of mszOldName.  However, if
//  mszOldName is currently a null pointer, we return a pointer to an
//  empty string instead.  This means you can use the return value from
//  this function anywhere you want without checking for its NULLity.
//
// DESCRIPTION
//
//  See above.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

const char AL_DLL_FAR * AL_PROTO ALName::GetSafeOldName() const
{
    if ( mszOldName )
        return mszOldName;
    else
        return "";
}

//
// ALName::operator const char *() const
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  A pointer to the string inside the object.  If the string is
//  presently a null pointer, we return a pointer to an empty string
//  instead.  See ALName::GetSafeName() for an explanation of why
//  this is.
//
// DESCRIPTION
//
//  This is the casting operator that pretty much lets me use
//  an ALName object anywhere I use a char *.  There are some strange
//  Microsoft compiler problems that make me use this goofy STRINGF
//  typedef instead of char *, but it all adds up the same in
//  the wash.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

#if defined( AL_MICROSOFT ) && ( AL_MICROSOFT < 800 ) && defined( AL_BUILDING_DLL ) //??????
AL_PROTO ALName::operator char *()  const
#else
AL_PROTO ALName::operator const STRINGF() const
#endif
{
    if ( mszName )
        return mszName;
    else
        return "";
}

//
// ALName & ALName::StripFileName()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  A reference to this.  This is nice, because you can strip the
//  file name from an object and use it in the same operation.
//
// DESCRIPTION
//
//  In the wildcard parsing code, sometimes I need to get the path
//  of a file, which means stripping off the filename and extension.
//  This is pretty easy to do, I just find the right spot and stick
//  a string termination character in that position.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

ALName AL_DLL_FAR & AL_PROTO ALName::StripFileName()
{
    if ( mszName ) {
        char *p = strrchr( mszName, '\\' );
        if ( p == 0 )
            p = strrchr( mszName, ':' );
        if ( p )
            p[ 1 ] = '\0';
        else
            mszName[ 0 ] = '\0';
    }
    return *this;
}

//
// ALName & ALName::StripPath()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  A reference to this.  This is nice, because you can strip the
//  file name from an object and use it in the same operation.
//
// DESCRIPTION
//
//  In the wildcard parsing code, sometimes I need to get just the
//  filename and extension of a file, which means stripping off the 
//  drive and path information.  This is pretty easy to do, I just find 
//  the start of the filename, and move it up to the start of the string.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

ALName AL_DLL_FAR & AL_PROTO ALName::StripPath()
{
    if ( mszName ) {
        char *p = strrchr( mszName, '\\' );
        if ( p == 0 )
            p = strrchr( mszName, ':' );
        if ( p ) {
            p++;
            char *s = mszName;
            while( ( *s++ = *p++ ) != 0 )
                ;
            *s = '\0';
        } //If not p, path is already stripped
    }
    return *this;
}

//
// int ALName::WildCardMatch( const char *pattern )
//
// ARGUMENTS:
//
//  pattern : A pointer to a regular expression, including wildcards
//            and sets.  It can get pretty complicated.
//
// RETURNS
//
//  0 if the pattern doesn't match the object name, 1 if it does.
//
// DESCRIPTION
//
//  This is a super-duper powerful function.  It is used to compare
//  a regular expression to the contents of an ALName.  The real meat
//  here is in the public domain string matching code found in _MATCH.CPP.
//
//  The tricky bit here is that we have to make a comparison based
//  on the case sensitivity of this.  Rather than trying to modify
//  the code in _MATCH.CPP, I just make a new copy of the pattern, and
//  mangle the case according to what this expects.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

int AL_PROTO ALName::WildCardMatch( const char AL_DLL_FAR *pattern )
{
    int error;
    int result;
    char *p = new char[ strlen( pattern ) + 1 ];

    if ( !p )
        return 0;
    strcpy( p, pattern );
    switch ( mCase ) {
        case AL_UPPER : strupr( p ); break;
        case AL_LOWER : strlwr( p ); break;
    }
    if ( !is_valid_pattern( p, &error ) )
        result = 0;
    else if ( matche( p, mszName ) == MATCH_VALID )
        result = 1;
    else
        result = 0;
    delete p;
    return result;
}

// PROTECTED MEMBER FUNCTION.
//
// void ALName::Strcpy( const char *s )
//
// ARGUMENTS:
//
//  s  :  A character string to be copied into mszName.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  Whenever I am going to copy a string into mszName, I need to convert
//  it to the case that this object expects.  If it is AL_UPPER or
//  AL_LOWER, that means copying and then converting to either all upper
//  case or all lower case.  I do that here.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

void AL_PROTO ALName::Strcpy( const char *s )
{
    strcpy( mszName, s );
     switch ( mCase ) {
        case AL_UPPER : strupr( mszName ); break;
        case AL_LOWER : strlwr( mszName ); break;
     }
}