Tool for Globally Changing Class Names

One of the problems you may find while developing a big project is the class
renaming.

For example I begin using an object of a class CConfig. Then a few moments
later I decide to change the class name from ‘CConfig’ to a more specific name
‘CInterfaceConfiguration’. It’s easy but the filenames remain unchanged. You can
change each file Config.h and Config.cpp. But if you have a really big project
the change becames a problem.

But I solve it partially. The code I made solves the problem parsing each
source file in the project, prompting for change or not the header, and the
corresponding source file  (the correct name is builded from the definition
of the clases inside each header). I mean partially beacuse I suppose than a
header file named thing.h has a source named thing.cpp and than changes only
affect one project (the parsed one).

Using the program

It’s very simple to use. You must fill the line of project with the full path
of the *.dsp.

Then click on ‘do changes’ and wait the prompt of the program.

My recomendation is not change the header Sample.h by SampleApp.h where
Sample is ‘theApp’ because ClassWizard will include in new clases the header
‘Sample.h’ and not ‘SampleApp.h’.

Prompting…

When it finishes it shows some statistics and source files that would have
been changed.

Developing the program

The mechanism its easy in concept. You must open the .dsp locate the headers
and inside them locate the classes, make the correct changes and no more. As you
will imagine the solution is not so easy… Let’s take a look to the dsp file.

# Microsoft Developer Studio Project File – Name="ODBC_Query" –
Package Owner=<4>
# Begin Target
# Name "ODBC_Query – Win32 Release"
# Name "ODBC_Query – Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\ODBC_Query.cpp
# End Source File
# Begin Source File
SOURCE=.\ODBC_Query.rc
# End Source File
# Begin Source File
SOURCE=.\StdAfx.cpp
# ADD CPP /Yc"stdafx.h"
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\ODBC_Query.h
# End Source File
# End Target
# End Project

Is the group important? it depends on the developer that manages the project,
but it’s sure that a source file finishes in .cpp and a header file finishes in
.h, and both files are after a # Begin Source File declaration
in a line started with SOURCE=.

Then the code is like that:

// contains all files that not have class declarations
CStringList SourceFiles;

CStringList HeaderFiles;
{
 // Now we need to scan de dsp file
 CString StringFile = _ReadStringFile(m_Project);
 {
  // Next step: Look for source files and header ones
  SourceFiles.RemoveAll(); // can be avoided (it's always empty)

  PD.SetRange(0,StringFile.GetLength());
  PD.SetStatus("Looking for files in the project.");

  int ini_pos = 0;

  do
  {
   ini_pos=StringFile.Find("# Begin Source File",ini_pos);
   if (ini_pos==-1) break;
   PD.SetStep(ini_pos);

   // Ok, there is a source file
   CString	FirstToken = "SOURCE=",
   LastToken = "\n";

   // Get the file
   ini_pos = StringFile.Find(FirstToken,ini_pos)
           + FirstToken.GetLength();

   int SFileSize = StringFile.Find(LastToken,ini_pos)
                 - ini_pos-LastToken.GetLength();

   CString SFile = StringFile.Mid(ini_pos,SFileSize);

   // It's valid?
   SFile.Replace('\\','/');

   if (SFile.Left(2)=="./") SFile=SFile.Mid(2);

   if (!ExistFileAtWorkingDir(SFile))
   {
    ::AfxMessageBox("The file "+SFile+" "
                    "doesn't exist, avoiding it");
   }
   else
   {
    // It's a header?
    CString Extension = SFile.Right(2);

    Extension.MakeUpper();
    if(Extension==".H")
    {
     HeaderFiles.AddTail(SFile);
     m_Headers.AddString(SFile);
    };

    Extension = SFile.Right(4);
    Extension.MakeUpper();
    if(Extension==".CPP")
    {
     SourceFiles.AddTail(SFile);
     m_Sources.AddString(SFile);
    };

    // other kind of files are not included
   }
  } while(true);
 }
}

The next thing to examine is the definition of a class and it may be the most
smart.

If we suppose that a class is defined in one line (As it should be), we have
three patterns:

  • class(1) export_d(0-1)…other_stuff(0-1) THECLASS(1) :(1)
  • class(1) export_d(0-1)…other_stuff(0-1) THECLASS(1) {(1)
  • class(1) export_d(0-1)…other_stuff(0-1) THECLASS(1)

Where the ‘(n)’ means the cardinality ( (0-1) means that it may exists or not
).

// Look for the first class definition
bool ClassFound = false;
while(!ClassFound)
{
 CString Buffer;

 if (!fHead.ReadString(Buffer)) break;

 CString ClassToken = "class ";

 if ((Buffer.Left(ClassToken.GetLength())==ClassToken)
 &&(Buffer.Find(';')==-1)) // No pre-declarations here
 {
  // We found a class definition
  int Pos;
  // First pattern : 
  // 'class(1) export_d(0-1) ... other_stuff(0-1) THECLASS(1) :(1)'
  if ((Pos = Buffer.Find(':'))!=-1) // Ok
  {
   ClassName=Buffer.Left(Pos);
   ClassName.TrimRight();
   Pos = ClassName.ReverseFind(' ');
   ClassName=ClassName.Right(ClassName.GetLength()-(Pos+1));
   ClassFound=true;
  }

  // Second pattern : 
  // 'class(1) export_d(0-1) ... other_stuff(0-1) THECLASS(1) {(1)'
  else if ((Pos = Buffer.Find('{'))!=-1) // Ok
  {
   ClassName=Buffer.Left(Pos);
   ClassName.TrimRight();
   Pos = ClassName.ReverseFind(' ');
   ClassName=ClassName.Right(ClassName.GetLength()-(Pos+1));
   ClassFound=true;
  }
  else
  // Third pattern : 
  // 'class(1) export_d(0-1) ... other_stuff(0-1) THECLASS(1)'
  {
   ClassName=Buffer;
   ClassName.TrimRight();
   Pos = ClassName.ReverseFind(' ');
   ClassName=ClassName.Right(ClassName.GetLength()-(Pos+1));
   ClassFound=true;
  }
   if(ClassFound)
  {
   int hItem=m_HeadersClasses.InsertItem(0,"");
   m_HeadersClasses.SetItemText(hItem,0,TheHeader);
   m_HeadersClasses.SetItemText(hItem,1,ClassName);

   CString CorrectHeader;
   if (ClassName.Left(1)=='C')
    CorrectHeader=ClassName.Mid(1);
   else
    CorrectHeader=ClassName;

   CorrectHeader+=".h";

   if (TheHeader!=CorrectHeader)

   if (IDNO
   ==  ::AfxMessageBox(CString()+"Shall I change old:"
       +TheHeader+" by new:"+CorrectHeader,MB_YESNO))
   {
    CorrectHeader=TheHeader;
    ClassFound=false;
   }

   if(ClassFound)
   {
    if(TheHeader!=CorrectHeader)
    {
     SourceDeps.SetAt(TheHeader,ClassName);
     ClassToCorrectHeader.SetAt(ClassName,CorrectHeader);
     m_HeadersClasses.SetItemText(hItem,2,"Need change");
    }
    else
    {
     m_HeadersClasses.SetItemText(hItem,2,"No need to change");
    }
   }
  }
  m_HeadersClasses.Arrange(LVA_DEFAULT);
 }
}
fHead.Close();

Performing changes

Well the next phase is the change, it will be done by changing the .dsp
project:

// Now the data is acquired, let's do the changes
{
 // Change includes in dsp file
 // Open the file
 CString StringFile = _ReadStringFile(m_Project);
 for(POSITION Pos = SourceDeps.GetStartPosition(); Pos!=NULL;)
 {
  // Get the data
  CString TheHeader,ClassName,CorrectHeader;
  SourceDeps.GetNextAssoc(Pos,TheHeader,ClassName);
  ClassToCorrectHeader.Lookup(ClassName,CorrectHeader);

  {
   // Header
   CString Pattern1 = "SOURCE="+TheHeader;
   CString Pattern2 = "/"+TheHeader;
   CString Pattern3 = "\\"+TheHeader;
   CString Pattern1Change = "SOURCE="+CorrectHeader;
   CString Pattern2Change = "/"+CorrectHeader;
   CString Pattern3Change = "\\"+CorrectHeader;

   StringFile.Replace(Pattern1,Pattern1Change);
   StringFile.Replace(Pattern2,Pattern2Change);
   StringFile.Replace(Pattern3,Pattern3Change);
  }

  {
   // Sources
   TheHeader = TheHeader.Left(TheHeader.GetLength()-1)+"cpp";

   CorrectHeader =
    CorrectHeader.Left(CorrectHeader.GetLength()-1)+"cpp";

   CString Pattern1 = "SOURCE="+TheHeader;
   CString Pattern2 = "/"+TheHeader;
   CString Pattern3 = "\\"+TheHeader;
   CString Pattern1Change = "SOURCE="+CorrectHeader;
   CString Pattern2Change = "/"+CorrectHeader;
   CString Pattern3Change = "\\"+CorrectHeader;

   StringFile.Replace(Pattern1,Pattern1Change);
   StringFile.Replace(Pattern2,Pattern2Change);
   StringFile.Replace(Pattern3,Pattern3Change);
  }
 }

 // Save the file
 _WriteStringFile(m_Project,StringFile);
}

After change the project we must change the include references in source and header files.

{
 // Perform changes in header and source files
 CStringList FilesToPerformReplace;
 FilesToPerformReplace.AddTail(&SourceFiles);
 FilesToPerformReplace.AddTail(&HeaderFiles);

 while(!FilesToPerformReplace.IsEmpty())
 {
  // For each file
  CString TheFile = FilesToPerformReplace.RemoveHead();

  // Open the file
  CString StringFile = _ReadStringFile(TheFile);

  // Perform replaces
  for(POSITION Pos = SourceDeps.GetStartPosition(); Pos!=NULL;)
  {
   // Get the data
   CString TheHeader,ClassName,CorrectHeader;
   SourceDeps.GetNextAssoc(Pos,TheHeader,ClassName);
   ClassToCorrectHeader.Lookup(ClassName,CorrectHeader);

   CString Pattern1 = "\""+TheHeader+"\"";
   CString Pattern2 = "<"+TheHeader+">";
   CString Pattern3 = "\\"+TheHeader+">";
   CString Pattern1Change = "\""+CorrectHeader+"\"";
   CString Pattern2Change = "<"+CorrectHeader+">";
   CString Pattern3Change = "\\"+CorrectHeader+">";

   StringFile.Replace(Pattern1,Pattern1Change);
   StringFile.Replace(Pattern2,Pattern2Change);
   StringFile.Replace(Pattern3,Pattern3Change);
  }

  // Save the file
  _WriteStringFile(TheFile,StringFile);
 }

 // allfiles where replaced
}

And rename the files….

{
 // And now the last phase, renaming the files
 for(POSITION Pos = SourceDeps.GetStartPosition();
     Pos!=NULL;)
 {
  // Get the data
  CString TheHeader,ClassName,CorrectHeader;
  SourceDeps.GetNextAssoc(Pos,TheHeader,ClassName);
  ClassToCorrectHeader.Lookup(ClassName,CorrectHeader);

  // The headers
  if (TheHeader!=CorrectHeader)
  {
   char *file, *filenew;
   file = _fullpath(NULL,TheHeader,0);
   filenew = _fullpath(NULL,CorrectHeader,0);
   CopyFile(file,filenew,false);
   DeleteFile(file);
   free(file);
   free(filenew);
  }

  // The sources
  TheHeader = TheHeader.Left(TheHeader.GetLength()-1)+"cpp";

  CorrectHeader
  = CorrectHeader.Left(CorrectHeader.GetLength()-1)+"cpp";

  if (TheHeader!=CorrectHeader)
  {
   char *file, *filenew;
   file = _fullpath(NULL,TheHeader,0);
   filenew = _fullpath(NULL,CorrectHeader,0);
   CopyFile(file,filenew,false);
   DeleteFile(file);
   free(file);
   free(filenew);
  }
 }
}

And that’s all. Note that I use a file like a string, it’s a serializated
file. This is because it increases overall performance using Replace function of
CString class made in assembler.

// unique string-file functions
CString CAdjustFileClassesDlg::_ReadStringFile(CString file)
{
 CString StringFile;
 {
  // We load the file into a string
  char * str;
  CFile file(file,CFile::modeRead | CFile::shareDenyNone);
  StringFile.GetBufferSetLength(file.GetLength()+1);
  str = StringFile.LockBuffer();
  file.ReadHuge(str,file.GetLength());
  str[file.GetLength()]=0;
  file.Close();
  StringFile.UnlockBuffer();
 }
 return StringFile;
}

void CAdjustFileClassesDlg::_WriteStringFile(CString _file,
                                             CString data)
{
 CFile file(_file,
            CFile::modeCreate
             | CFile::modeWrite
             | CFile::shareDenyNone);

 file.WriteHuge(data.LockBuffer(),data.GetLength()-1);
 data.UnlockBuffer();
 file.Close();
}

But for reading files, that have a relative path from the .dsp we first need
to do this:

// Get the base directory of the project
m_Project.Replace('\\','/');
CString BaseDir;
int pos = m_Project.ReverseFind('/');

if (pos==-1)
{
 BaseDir="";
}
else
{
 BaseDir=m_Project.Left(pos+1);
}

if(-1==_chdir(BaseDir))
{
 ::AfxMessageBox("Something wrong in the path of "
                 +m_Project+", exiting");
 return;
}

Well, I hope that this code will be interesting and useful for you like it
was for me.

Downloads

Download tools (includes debug build and source code) – 90 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read