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


Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds