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: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • Live Event Date: August 19, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT You deployed your app with the Bluemix PaaS and it's gaining some serious traction, so it's time to make some tweaks. Did you design your application in a way that it can scale in the cloud? Were you even thinking about the cloud when you built the app? If not, chances are your app is going to break. Check out this upcoming eSeminar to learn various techniques for designing applications that will scale successfully in Bluemix, for the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds