1 /*  $Id: generate.cpp 591546 2019-08-16 16:59:06Z vasilche $
2 * ===========================================================================
3 *
4 *                            PUBLIC DOMAIN NOTICE
5 *               National Center for Biotechnology Information
6 *
7 *  This software/database is a "United States Government Work" under the
8 *  terms of the United States Copyright Act.  It was written as part of
9 *  the author's official duties as a United States Government employee and
10 *  thus cannot be copyrighted.  This software/database is freely available
11 *  to the public for use. The National Library of Medicine and the U.S.
12 *  Government have not placed any restriction on its use or reproduction.
13 *
14 *  Although all reasonable efforts have been taken to ensure the accuracy
15 *  and reliability of the software and data, the NLM and the U.S.
16 *  Government do not and cannot warrant the performance or results that
17 *  may be obtained by using this software or data. The NLM and the U.S.
18 *  Government disclaim all warranties, express or implied, including
19 *  warranties of performance, merchantability or fitness for any particular
20 *  purpose.
21 *
22 *  Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Eugene Vasilchenko
27 *
28 * File Description:
29 *   Main generator: collects all types, classes and files.
30 */
31 
32 #include <ncbi_pch.hpp>
33 #include <corelib/ncbifile.hpp>
34 #include <algorithm>
35 #include <typeinfo>
36 #include "moduleset.hpp"
37 #include "module.hpp"
38 #include "type.hpp"
39 #include "statictype.hpp"
40 #include "reftype.hpp"
41 #include "unitype.hpp"
42 #include "enumtype.hpp"
43 #include "blocktype.hpp"
44 #include "choicetype.hpp"
45 #include "filecode.hpp"
46 #include "generate.hpp"
47 #include "exceptions.hpp"
48 #include "fileutil.hpp"
49 #include "rpcgen.hpp"
50 #include "code.hpp"
51 #include "classstr.hpp"
52 #include <serial/error_codes.hpp>
53 
54 
55 #define NCBI_USE_ERRCODE_X   Serial_MainGen
56 
57 BEGIN_NCBI_SCOPE
58 
CCodeGenerator(void)59 CCodeGenerator::CCodeGenerator(void)
60     : m_ExcludeRecursion(false), m_FileNamePrefixSource(eFileName_FromNone)
61 {
62     m_MainFiles.SetModuleContainer(this);
63     m_ImportFiles.SetModuleContainer(this);
64     m_UseQuotedForm = false;
65     m_CreateCvsignore = false;
66 }
67 
~CCodeGenerator(void)68 CCodeGenerator::~CCodeGenerator(void)
69 {
70 }
71 
GetConfig(void) const72 const CMemoryRegistry& CCodeGenerator::GetConfig(void) const
73 {
74     return m_Config;
75 }
76 
GetFileNamePrefix(void) const77 string CCodeGenerator::GetFileNamePrefix(void) const
78 {
79     return m_FileNamePrefix;
80 }
81 
UseQuotedForm(bool use)82 void CCodeGenerator::UseQuotedForm(bool use)
83 {
84     m_UseQuotedForm = use;
85 }
86 
CreateCvsignore(bool create)87 void CCodeGenerator::CreateCvsignore(bool create)
88 {
89     m_CreateCvsignore = create;
90 }
91 
SetFileNamePrefix(const string & prefix)92 void CCodeGenerator::SetFileNamePrefix(const string& prefix)
93 {
94     m_FileNamePrefix = prefix;
95 }
96 
GetFileNamePrefixSource(void) const97 EFileNamePrefixSource CCodeGenerator::GetFileNamePrefixSource(void) const
98 {
99     return m_FileNamePrefixSource;
100 }
101 
SetFileNamePrefixSource(EFileNamePrefixSource source)102 void CCodeGenerator::SetFileNamePrefixSource(EFileNamePrefixSource source)
103 {
104     m_FileNamePrefixSource =
105         EFileNamePrefixSource(m_FileNamePrefixSource | source);
106 }
107 
InternalResolve(const string & module,const string & name) const108 CDataType* CCodeGenerator::InternalResolve(const string& module,
109                                            const string& name) const
110 {
111     return ExternalResolve(module, name);
112 }
113 
GetNamespace(void) const114 const CNamespace& CCodeGenerator::GetNamespace(void) const
115 {
116     return m_DefaultNamespace;
117 }
118 
SetDefaultNamespace(const string & ns)119 void CCodeGenerator::SetDefaultNamespace(const string& ns)
120 {
121     m_DefaultNamespace = ns;
122 }
123 
ResetDefaultNamespace(void)124 void CCodeGenerator::ResetDefaultNamespace(void)
125 {
126     m_DefaultNamespace.Reset();
127 }
128 
LoadConfig(CNcbiIstream & in)129 void CCodeGenerator::LoadConfig(CNcbiIstream& in)
130 {
131     m_Config.Read(in);
132 }
133 
LoadConfig(const string & fileName,bool ignoreAbsense,bool warningAbsense)134 void CCodeGenerator::LoadConfig(const string& fileName,
135     bool ignoreAbsense, bool warningAbsense)
136 {
137     m_DefFile.erase();
138     // load descriptions from registry file
139     if ( fileName == "stdin" || fileName == "-" ) {
140         LoadConfig(NcbiCin);
141     }
142     else {
143         CNcbiIfstream in(fileName.c_str());
144         if ( !in ) {
145             if ( ignoreAbsense ) {
146                 return;
147             } else if (warningAbsense) {
148                 ERR_POST_X(1, Warning << "cannot open file " << fileName);
149             } else {
150                 ERR_POST_X(2, Fatal << "cannot open file " << fileName);
151             }
152         }
153         else {
154             m_DefFile = fileName;
155             LoadConfig(in);
156         }
157     }
158 }
159 
AddConfigLine(const string & line)160 void CCodeGenerator::AddConfigLine(const string& line)
161 {
162     SIZE_TYPE bra = line.find('[');
163     SIZE_TYPE ket = line.find(']');
164     SIZE_TYPE eq = line.find('=', ket + 1);
165     if ( bra != 0 || ket == NPOS || eq == NPOS )
166         ERR_POST_X(3, Fatal << "bad config line: " << line);
167 
168     m_Config.Set(line.substr(bra + 1, ket - bra - 1),
169                  line.substr(ket + 1, eq - ket - 1),
170                  line.substr(eq + 1));
171 }
172 
ExternalResolve(const string & module,const string & name,bool exported) const173 CDataType* CCodeGenerator::ExternalResolve(const string& module,
174                                            const string& name,
175                                            bool exported) const
176 {
177     string loc("CCodeGenerator::ExternalResolve: failed");
178     try {
179         return m_MainFiles.ExternalResolve(module, name, exported);
180     }
181     catch ( CAmbiguiousTypes& exc) {
182         _TRACE(exc.what());
183         NCBI_RETHROW_SAME(exc,loc);
184     }
185     catch ( CNotFoundException& _DEBUG_ARG(exc)) {
186         _TRACE(exc.what());
187         return m_ImportFiles.ExternalResolve(module, name, exported);
188     }
189     return NULL;  // Eliminate "return value expected" warning
190 }
191 
ResolveInAnyModule(const string & name,bool exported) const192 CDataType* CCodeGenerator::ResolveInAnyModule(const string& name,
193                                               bool exported) const
194 {
195     string loc("CCodeGenerator::ResolveInAnyModule: failed");
196     try {
197         return m_MainFiles.ResolveInAnyModule(name, exported);
198     }
199     catch ( CAmbiguiousTypes& exc) {
200         _TRACE(exc.what());
201         NCBI_RETHROW_SAME(exc,loc);
202     }
203     catch ( CNotFoundException& _DEBUG_ARG(exc)) {
204         _TRACE(exc.what());
205         return m_ImportFiles.ResolveInAnyModule(name, exported);
206     }
207     return NULL;  // Eliminate "return value expected" warning
208 }
209 
ResolveMain(const string & fullName) const210 CDataType* CCodeGenerator::ResolveMain(const string& fullName) const
211 {
212     SIZE_TYPE dot = fullName.find('.');
213     if ( dot != NPOS ) {
214         // module specified
215         return m_MainFiles.ExternalResolve(fullName.substr(0, dot),
216                                            fullName.substr(dot + 1),
217                                            true);
218     }
219     else {
220         // module not specified - we'll scan all modules for type
221         return m_MainFiles.ResolveInAnyModule(fullName, true);
222     }
223 }
224 
ResolveFileName(const string & name) const225 const string& CCodeGenerator::ResolveFileName(const string& name) const
226 {
227     TOutputFiles::const_iterator i = m_Files.find(name);
228     if (i != m_Files.end()) {
229         return i->second->GetFileBaseName();
230     }
231     return name;
232 }
233 
IncludeAllMainTypes(void)234 void CCodeGenerator::IncludeAllMainTypes(void)
235 {
236     ITERATE ( CFileSet::TModuleSets, msi, m_MainFiles.GetModuleSets() ) {
237         ITERATE ( CFileModules::TModules, mi, (*msi)->GetModules() ) {
238             const CDataTypeModule* module = mi->get();
239             ITERATE ( CDataTypeModule::TDefinitions, ti,
240                       module->GetDefinitions() ) {
241                 const string& name = ti->first;
242                 const CDataType* type = ti->second.get();
243                 if ( !name.empty() && !type->Skipped() ) {
244                     m_GenerateTypes.insert(module->GetName() + '.' + name);
245                 }
246             }
247         }
248     }
249 }
250 
GetTypes(TTypeNames & typeSet,const string & types)251 void CCodeGenerator::GetTypes(TTypeNames& typeSet, const string& types)
252 {
253     SIZE_TYPE pos = 0;
254     SIZE_TYPE next = types.find(',');
255     while ( next != NPOS ) {
256         typeSet.insert(types.substr(pos, next - pos));
257         pos = next + 1;
258         next = types.find(',', pos);
259     }
260     typeSet.insert(types.substr(pos));
261 }
262 
Check(void) const263 bool CCodeGenerator::Check(void) const
264 {
265     return m_MainFiles.CheckNames() && m_ImportFiles.CheckNames() &&
266         m_MainFiles.Check();
267 }
268 
ExcludeTypes(const string & typeList)269 void CCodeGenerator::ExcludeTypes(const string& typeList)
270 {
271     TTypeNames typeNames;
272     GetTypes(typeNames, typeList);
273     ITERATE ( TTypeNames, i, typeNames ) {
274         m_Config.Set(*i, "_class", "-");
275         m_GenerateTypes.erase(*i);
276     }
277 }
278 
IncludeTypes(const string & typeList)279 void CCodeGenerator::IncludeTypes(const string& typeList)
280 {
281     GetTypes(m_GenerateTypes, typeList);
282 }
283 
284 struct string_nocase_less
285 {
operator ()string_nocase_less286     bool operator()(const string& s1, const string& s2) const
287     {
288         return (NStr::CompareNocase(s1, s2) < 0);
289     }
290 };
291 
UndoGenerateCode(void)292 void CCodeGenerator::UndoGenerateCode(void)
293 {
294     if ( m_FileListFileName.empty() ) {
295         return;
296     }
297     string fileName( MakeAbsolutePath(
298         Path(m_CPPDir,Path(m_FileNamePrefix,m_FileListFileName))));
299     CNcbiIfstream fileList(fileName.c_str());
300     if ( fileList.is_open() ) {
301         string strline;
302         string fileroot;
303         while ( NcbiGetlineEOL(fileList, strline) ) {
304             if ( !NStr::StartsWith(strline, "GENFILES =") ) {
305                 continue;
306             }
307             NStr::ReplaceInPlace(strline,"GENFILES =","");
308             list<string> files;
309             NStr::Split( strline, " ", files, NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
310             ITERATE(list<string>, f, files) {
311                 CFile fo;
312                 fo.Reset( MakeAbsolutePath( Path(m_HPPDir, *f)) + "_.hpp");
313                 if (fo.Exists()) {
314                     fo.Remove();
315                 }
316                 fo.Reset( MakeAbsolutePath( Path(m_CPPDir, *f)) + "_.cpp");
317                 if (fo.Exists()) {
318                     fo.Remove();
319                 }
320             }
321         }
322         fileList.close();
323         CFile fo(fileName);
324         if (fo.Exists()) {
325             fo.Remove();
326         }
327     }
328 }
329 
CheckFileNames(void)330 void CCodeGenerator::CheckFileNames(void)
331 {
332     set<string,string_nocase_less> names;
333     ITERATE ( TOutputFiles, filei, m_Files ) {
334         CFileCode* code = filei->second.get();
335         string fname;
336         for ( fname = code->GetFileBaseName();
337                 names.find(fname) != names.end();) {
338             fname = code->ChangeFileBaseName();
339         }
340         names.insert(fname);
341     }
342 }
343 
GenerateCode(void)344 void CCodeGenerator::GenerateCode(void)
345 {
346     // collect types
347     ITERATE ( TTypeNames, ti, m_GenerateTypes ) {
348         CollectTypes(ResolveMain(*ti), eRoot);
349     }
350     CheckFileNames();
351 
352     // generate output files
353     string outdir_cpp, outdir_hpp;
354     list<string> listGenerated, listUntouched;
355     list<string> allGeneratedHpp, allGeneratedCpp, allSkippedHpp, allSkippedCpp;
356     map<string, pair<string,string> > module_names;
357     ITERATE ( TOutputFiles, filei, m_Files ) {
358         CFileCode* code = filei->second.get();
359         code->GetModuleNames( module_names);
360         code->UseQuotedForm(m_UseQuotedForm);
361         code->GenerateCode();
362         string fileName;
363         code->GenerateHPP(m_HPPDir, fileName);
364         allGeneratedHpp.push_back(fileName);
365         if (outdir_hpp.empty()) {
366             CDirEntry entry(fileName);
367             outdir_hpp = entry.GetDir();
368         }
369         code->GenerateCPP(m_CPPDir, fileName);
370         allGeneratedCpp.push_back(fileName);
371         if (outdir_cpp.empty()) {
372             CDirEntry entry(fileName);
373             outdir_cpp = entry.GetDir();
374         }
375         if (code->GenerateUserHPP(m_HPPDir, fileName)) {
376             listGenerated.push_back( fileName);
377             allGeneratedHpp.push_back(fileName);
378         } else {
379             listUntouched.push_back( fileName);
380             allSkippedHpp.push_back(fileName);
381         }
382         if (code->GenerateUserCPP(m_CPPDir, fileName)) {
383             listGenerated.push_back( fileName);
384             allGeneratedCpp.push_back(fileName);
385         } else {
386             listUntouched.push_back( fileName);
387             allSkippedCpp.push_back(fileName);
388         }
389     }
390     list<string> module_inc, module_src;
391     GenerateModuleHPP(Path(m_HPPDir,m_FileNamePrefix), module_inc);
392     GenerateModuleCPP(Path(m_CPPDir,m_FileNamePrefix), module_src);
393 
394     GenerateDoxygenGroupDescription(module_names);
395     GenerateCombiningFile(module_inc, module_src, allGeneratedHpp, allGeneratedCpp);
396 	listGenerated.insert(listGenerated.end(), module_inc.begin(), module_inc.end());
397 	allGeneratedHpp.insert(allGeneratedHpp.end(), module_inc.begin(), module_inc.end());
398 	listGenerated.insert(listGenerated.end(), module_src.begin(), module_src.end());
399 	allGeneratedCpp.insert(allGeneratedCpp.end(), module_src.begin(), module_src.end());
400     GenerateFileList(listGenerated, listUntouched,
401         allGeneratedHpp, allGeneratedCpp, allSkippedHpp, allSkippedCpp);
402     GenerateCvsignore(outdir_cpp, outdir_hpp, listGenerated, module_names);
403     GenerateClientCode();
404 }
405 
406 
GenerateDoxygenGroupDescription(map<string,pair<string,string>> & module_names)407 void CCodeGenerator::GenerateDoxygenGroupDescription(
408     map<string, pair<string,string> >& module_names)
409 {
410     if (!CClassCode::GetDoxygenComments() || module_names.empty()) {
411         return;
412     }
413     string ingroup_name =
414         m_DoxygenIngroup.empty() ? "DatatoolGeneratedClasses" : m_DoxygenIngroup;
415     CDirEntry entry(GetMainModules().GetModuleSets().front()->GetSourceFileName());
416     string fileName = MakeAbsolutePath(
417         Path(m_HPPDir, Path(m_FileNamePrefix, entry.GetBase() + "_doxygen.h")));
418     CNcbiOfstream doxyfile(fileName.c_str());
419     if ( doxyfile.is_open() ) {
420         CFileCode::WriteCopyrightHeader( doxyfile);
421         doxyfile <<
422             " *  File Description:\n"
423             " *    This file was generated by application DATATOOL\n"
424             " *    It contains comment blocks for DOXYGEN metamodules\n"
425             " *\n"
426             " * ===========================================================================\n"
427             " */\n";
428         if (CClassCode::GetDoxygenGroup().empty()) {
429             map<string, pair<string,string> >::iterator i;
430             for (i = module_names.begin(); i != module_names.end(); ++i) {
431                 doxyfile << "\n\n/** @defgroup dataspec_" << i->second.second << " ";
432                 if (m_DoxygenGroupDescription.empty()) {
433                     doxyfile << "Code generated by DATATOOL from "
434                         << i->second.first << " (module \'" << i->first << "\')";
435                 } else {
436                     doxyfile << m_DoxygenGroupDescription;
437                 }
438                 doxyfile << "\n *  @ingroup " << ingroup_name << "\n */\n\n";
439             }
440         } else {
441             doxyfile << "\n\n/** @defgroup ";
442             doxyfile << CClassCode::GetDoxygenGroup() << " ";
443             if (m_DoxygenGroupDescription.empty()) {
444                 doxyfile << "Code generated by DATATOOL";
445             } else {
446                 doxyfile << m_DoxygenGroupDescription;
447             }
448             doxyfile << "\n *  @ingroup " << ingroup_name << "\n */\n\n";
449         }
450     }
451 }
452 
453 
GenerateFileList(const list<string> & generated,const list<string> & untouched,list<string> & allGeneratedHpp,list<string> & allGeneratedCpp,list<string> & allSkippedHpp,list<string> & allSkippedCpp)454 void CCodeGenerator::GenerateFileList(
455     const list<string>& generated, const list<string>& untouched,
456     list<string>& allGeneratedHpp, list<string>& allGeneratedCpp,
457     list<string>& allSkippedHpp, list<string>& allSkippedCpp)
458 {
459     if ( m_FileListFileName.empty() ) {
460         return;
461     }
462     string fileName( MakeAbsolutePath(
463         Path(m_CPPDir,Path(m_FileNamePrefix,m_FileListFileName))));
464     CNcbiOfstream fileList(fileName.c_str());
465     if ( !fileList ) {
466         ERR_POST_X(4, Fatal <<
467                     "cannot create file list file: " << m_FileListFileName);
468     }
469 
470     fileList << "GENFILES =";
471     {
472         ITERATE ( TOutputFiles, filei, m_Files ) {
473             string tmp(filei->second->GetFileBaseName());
474 #if defined(NCBI_OS_MSWIN)
475             tmp = NStr::Replace(tmp,"\\","/");
476 #endif
477             fileList << ' ' << tmp;
478         }
479     }
480     fileList << "\n";
481     fileList << "GENFILES_LOCAL =";
482     {
483         ITERATE ( TOutputFiles, filei, m_Files ) {
484             fileList << ' ' << BaseName(
485                 filei->second->GetFileBaseName());
486         }
487     }
488     fileList << "\n";
489 
490     // generation report
491     for (int  user=0;  user<2; ++user)  {
492     for (int local=0; local<2; ++local) {
493     for (int   cpp=0;   cpp<2; ++cpp)   {
494         fileList << (user ? "SKIPPED" : "GENERATED") << "_"
495             << (cpp ? "CPP" : "HPP") << (local ? "_LOCAL" : "") << " =";
496         const list<string> *lst = (user ? &untouched : &generated);
497         for (list<string>::const_iterator i=lst->begin();
498             i != lst->end(); ++i) {
499             CDirEntry entry(*i);
500             bool is_cpp = (NStr::CompareNocase(entry.GetExt(),".cpp")==0);
501             if ((is_cpp && cpp) || (!is_cpp && !cpp)) {
502                 fileList << ' ';
503                 if (local) {
504                     fileList << entry.GetBase();
505                 } else {
506                     string pp = entry.GetPath();
507                     size_t found;
508                     if (is_cpp) {
509                         if (!m_CPPDir.empty() &&
510                             (found = pp.find(m_CPPDir)) == 0) {
511                             pp.erase(0,m_CPPDir.length()+1);
512                         }
513                     } else {
514                         if (!m_HPPDir.empty() &&
515                             (found = pp.find(m_HPPDir)) == 0) {
516                             pp.erase(0,m_HPPDir.length()+1);
517                         }
518                     }
519                     CDirEntry ent(CDirEntry::ConvertToOSPath(pp));
520                     string tmp(ent.GetDir(CDirEntry::eIfEmptyPath_Empty));
521 #if defined(NCBI_OS_MSWIN)
522                     tmp = NStr::Replace(tmp,"\\","/");
523 #endif
524                     fileList << tmp << ent.GetBase();
525                 }
526             }
527 
528         }
529         fileList << endl;
530     }
531     }
532     }
533 
534     string flist, flist_local;
535     ITERATE( list<string>, p, allGeneratedHpp) {
536         string tmp(*p);
537         flist_local += ' ' + CDirEntry(tmp).GetName();
538         if (!m_HPPDir.empty() && tmp.find(m_HPPDir) == 0) {
539             tmp.erase(0,m_HPPDir.length()+1);
540         }
541         tmp = CDirEntry::ConvertToOSPath(tmp);
542 #if defined(NCBI_OS_MSWIN)
543         tmp = NStr::Replace(tmp,"\\","/");
544 #endif
545         flist += ' ' +tmp;
546     }
547     fileList << "ALLGENERATED_HPP =" << flist << '\n';
548     fileList << "ALLGENERATED_HPP_LOCAL =" << flist_local << '\n';
549 
550     flist.erase(); flist_local.erase();
551     ITERATE( list<string>, p, allSkippedHpp) {
552         string tmp(*p);
553         flist_local += ' ' + CDirEntry(tmp).GetName();
554         if (!m_HPPDir.empty() && tmp.find(m_HPPDir) == 0) {
555             tmp.erase(0,m_HPPDir.length()+1);
556         }
557         tmp = CDirEntry::ConvertToOSPath(tmp);
558 #if defined(NCBI_OS_MSWIN)
559         tmp = NStr::Replace(tmp,"\\","/");
560 #endif
561         flist += ' ' +tmp;
562     }
563     fileList << "ALLSKIPPED_HPP =" << flist << '\n';
564     fileList << "ALLSKIPPED_HPP_LOCAL =" << flist_local << '\n';
565 
566     flist.erase(); flist_local.erase();
567     ITERATE( list<string>, p, allGeneratedCpp) {
568         string tmp(*p);
569         flist_local += ' ' + CDirEntry(tmp).GetName();
570         if (!m_CPPDir.empty() && tmp.find(m_CPPDir) == 0) {
571             tmp.erase(0,m_CPPDir.length()+1);
572         }
573         tmp = CDirEntry::ConvertToOSPath(tmp);
574 #if defined(NCBI_OS_MSWIN)
575         tmp = NStr::Replace(tmp,"\\","/");
576 #endif
577         flist += ' ' +tmp;
578     }
579     fileList << "ALLGENERATED_CPP =" << flist << '\n';
580     fileList << "ALLGENERATED_CPP_LOCAL =" << flist_local << '\n';
581 
582     flist.erase(); flist_local.erase();
583     ITERATE( list<string>, p, allSkippedCpp) {
584         string tmp(*p);
585         flist_local += ' ' + CDirEntry(tmp).GetName();
586         if (!m_CPPDir.empty() && tmp.find(m_CPPDir) == 0) {
587             tmp.erase(0,m_CPPDir.length()+1);
588         }
589         tmp = CDirEntry::ConvertToOSPath(tmp);
590 #if defined(NCBI_OS_MSWIN)
591         tmp = NStr::Replace(tmp,"\\","/");
592 #endif
593         flist += ' ' +tmp;
594     }
595     fileList << "ALLSKIPPED_CPP =" << flist << '\n';
596     fileList << "ALLSKIPPED_CPP_LOCAL =" << flist_local << '\n';
597 
598 }
599 
GenerateCombiningFile(const list<string> & module_inc,const list<string> & module_src,list<string> & allHpp,list<string> & allCpp)600 void CCodeGenerator::GenerateCombiningFile(
601     const list<string>& module_inc, const list<string>& module_src,
602     list<string>& allHpp, list<string>& allCpp)
603 {
604     if ( m_CombiningFileName.empty() ) {
605         return;
606     }
607     // write combined files *__.cpp and *___.cpp
608     for ( int i = 0; i < 2; ++i ) {
609         const char* suffix = i? "_.cpp": ".cpp";
610         string fileName = m_CombiningFileName + "__" + suffix;
611         fileName = Path(m_CPPDir,Path(m_FileNamePrefix,fileName));
612         allCpp.push_back(fileName);
613         fileName = MakeAbsolutePath(fileName);
614         CNcbiOfstream out(fileName.c_str());
615         if ( !out )
616             ERR_POST_X(5, Fatal << "Cannot create file: "<<fileName);
617 
618         if (!CFileCode::GetPchHeader().empty()) {
619             out <<
620                 "#include <" << CFileCode::GetPchHeader() << ">\n";
621         }
622 
623         ITERATE ( TOutputFiles, filei, m_Files ) {
624             out << "#include \""<<BaseName(
625                 filei->second->GetFileBaseName())<<
626                 suffix<<"\"\n";
627         }
628         if (i) {
629             ITERATE( list<string>, m, module_src) {
630                 out << "#include \"" << CDirEntry(*m).GetBase() << ".cpp\"\n";
631             }
632         }
633 
634         out.close();
635         if ( !out )
636             ERR_POST_X(6, Fatal << "Error writing file "<<fileName);
637     }
638     // write combined *__.hpp file
639     const char* suffix = ".hpp";
640     // save to the includes directory
641     string fileName = Path(m_HPPDir, Path(m_FileNamePrefix,
642                            m_CombiningFileName + "__" + suffix));
643     allHpp.push_back(fileName);
644     fileName = MakeAbsolutePath(fileName);
645 
646     CNcbiOfstream out(fileName.c_str());
647     if ( !out )
648         ERR_POST_X(7, Fatal << "Cannot create file: " << fileName);
649 
650     ITERATE ( TOutputFiles, filei, m_Files ) {
651         out << "#include " << (m_UseQuotedForm ? '\"' : '<') << GetStdPath(
652             Path(m_FileNamePrefix, BaseName(
653                 filei->second->GetFileBaseName())) + suffix) <<
654             (m_UseQuotedForm ? '\"' : '>') << "\n";
655     }
656     ITERATE( list<string>, m, module_inc) {
657         out << "#include " << (m_UseQuotedForm ? '\"' : '<')
658         << GetStdPath(Path(m_FileNamePrefix, CDirEntry(*m).GetBase())) << ".hpp"
659         << (m_UseQuotedForm ? '\"' : '>') << "\n";
660     }
661 
662     out.close();
663     if ( !out )
664         ERR_POST_X(8, Fatal << "Error writing file " << fileName);
665 }
666 
GenerateCvsignore(const string & outdir_cpp,const string & outdir_hpp,const list<string> & generated,map<string,pair<string,string>> & module_names)667 void CCodeGenerator::GenerateCvsignore(
668     const string& outdir_cpp, const string& outdir_hpp,
669     const list<string>& generated, map<string, pair<string,string> >& module_names)
670 {
671     if (!m_CreateCvsignore) {
672         return;
673     }
674     string ignoreName(".cvsignore");
675     string extraName(".cvsignore.extra");
676 
677     for (int i=0; i<2; ++i) {
678         bool is_cpp = (i==0);
679         bool different_dirs = (outdir_cpp != outdir_hpp);
680         string out_dir(is_cpp ? outdir_cpp : outdir_hpp);
681 
682         string ignorePath(MakeAbsolutePath(Path(out_dir,ignoreName)));
683         // ios::out should be redundant, but some compilers
684         // (GCC 2.9x, for one) seem to need it. :-/
685         CNcbiOfstream ignoreFile(ignorePath.c_str(),
686             ios::out | ((different_dirs || is_cpp) ? ios::trunc : ios::app));
687 
688         if (ignoreFile.is_open()) {
689 
690             if (different_dirs || is_cpp) {
691                 ignoreFile << ignoreName << endl;
692             }
693 
694 // .cvsignore.extra
695             if (different_dirs || is_cpp) {
696                 string extraPath(Path(out_dir,extraName));
697                 CNcbiIfstream extraFile(extraPath.c_str());
698                 if (extraFile.is_open()) {
699                     char buf[256];
700                     while (extraFile.good()) {
701                         extraFile.getline(buf, sizeof(buf));
702                         CTempString sbuf(NStr::TruncateSpaces_Unsafe(buf));
703                         if (!sbuf.empty()) {
704                             ignoreFile << sbuf << endl;
705                         }
706                     }
707                 }
708             }
709 
710 // base classes (always generated)
711             ITERATE ( TOutputFiles, filei, m_Files ) {
712                 ignoreFile
713                     << BaseName(filei->second->GetFileBaseName())
714                     << "_." << (is_cpp ? "cpp" : "hpp") << endl;
715             }
716 
717 // user classes
718             for (list<string>::const_iterator it = generated.begin();
719                 it != generated.end(); ++it) {
720                 CDirEntry entry(*it);
721                 if (is_cpp == (NStr::CompareNocase(entry.GetExt(),".cpp")==0)) {
722                     ignoreFile << entry.GetName() << endl;
723                 }
724             }
725 
726 // combining files
727             if ( !m_CombiningFileName.empty() ) {
728                 if (is_cpp) {
729                     ignoreFile << m_CombiningFileName << "__" << "_.cpp" << endl;
730                     ignoreFile << m_CombiningFileName << "__" << ".cpp" << endl;
731                 } else {
732                     ignoreFile << m_CombiningFileName << "__" << ".hpp" << endl;
733                 }
734             }
735 
736 // doxygen header
737             if ( !is_cpp  &&  CClassCode::GetDoxygenComments()
738                     &&  !module_names.empty() ) {
739                 CDirEntry entry(GetMainModules().GetModuleSets().front()
740                                 ->GetSourceFileName());
741                 ignoreFile << entry.GetBase() << "_doxygen.h" << endl;
742             }
743 
744 // file list
745             if ( is_cpp && !m_FileListFileName.empty() ) {
746                 CDirEntry entry(Path(m_FileNamePrefix,m_FileListFileName));
747                 ignoreFile << entry.GetName() << endl;
748             }
749 
750 // specification dump (somewhat hackishly)
751             if ( const CArgValue& f
752                  = CNcbiApplication::Instance()->GetArgs()["fd"] ) {
753                 ignoreFile << f.AsString() << endl;
754             }
755         }
756     }
757 }
758 
GenerateModuleHPP(const string & path,list<string> & generated) const759 void CCodeGenerator::GenerateModuleHPP(const string& path, list<string>& generated) const
760 {
761     set<string> modules;
762     string module_name, current_module, filename, hppDefine;
763     unique_ptr<CDelayedOfstream> out;
764     CNamespace ns;
765 
766     bool isfound = false;
767     do {
768         isfound = false;
769         bool types_found = false;
770         ITERATE ( TOutputFiles, filei, m_Files ) {
771             CFileCode* code = filei->second.get();
772             list<CTypeStrings*> filetypes;
773             code->GetClasses( filetypes );
774             module_name.clear();
775             ITERATE(list<CTypeStrings*>, t, filetypes) {
776                 string module = (*t)->GetDoxygenModuleName();
777                 if (module_name.empty() || module.size() < module_name.size()) {
778                     module_name = module;
779                 }
780             }
781             if (current_module.empty()) {
782                 if (modules.find(module_name) != modules.end()) {
783                     continue;
784                 }
785                 modules.insert(module_name);
786                 current_module = module_name;
787             } else if (current_module != module_name) {
788                 continue;
789             }
790             isfound = true;
791             if ( !out.get()  ||  !out->is_open() ) {
792                 if (isdigit((unsigned char)current_module[0])) {
793                     current_module.insert(current_module.begin(),'x');
794                 }
795                 filename = Path(path, current_module + "_module.hpp");
796                 out.reset(new CDelayedOfstream(filename.c_str()));
797                 if (!out->is_open()) {
798                     ERR_POST_X(9, Fatal << "Cannot create file: " << filename);
799                     return;
800                 }
801                 generated.push_back(filename);
802                 hppDefine = current_module + "_REGISTERMODULECLASSES_HPP";
803                 code->WriteCopyright(*out, false) <<
804                     "\n"
805                     "#ifndef " << hppDefine << "\n"
806                     "#define " << hppDefine << "\n"
807                     "\n#include <serial/serialbase.hpp>\n\n";
808                 ns.Set(code->GetNamespace(), *out, true);
809                 *out << '\n';
810                 if (!CClassCode::GetExportSpecifier().empty()) {
811                     *out << CClassCode::GetExportSpecifier() << '\n';
812                 }
813                 *out <<
814                     "void " << current_module << "_RegisterModuleClasses(void);\n\n";
815             }
816             ITERATE(list<CTypeStrings*>, t, filetypes) {
817                 if ((*t)->GetKind() == CTypeStrings::eKindObject) {
818                     CClassTypeStrings* classtype = dynamic_cast<CClassTypeStrings*>(*t);
819                     if (classtype && classtype->HaveTypeInfo()) {
820                         types_found = true;
821                     }
822                 }
823             }
824         }
825         if (isfound && !types_found) {
826             generated.pop_back();
827             out->Discard();
828         }
829         if (out->is_open()) {
830             ns.Reset(*out);
831             *out <<
832                 "\n"
833                 "#endif // " << hppDefine << "\n";
834             out->close();
835             if ( !*out )
836                 ERR_POST_X(10, Fatal << "Error writing file " << filename);
837         }
838         current_module.erase();
839     } while (isfound);
840 }
841 
GenerateModuleCPP(const string & path,list<string> & generated) const842 void CCodeGenerator::GenerateModuleCPP(const string& path, list<string>& generated) const
843 {
844     set<string> modules;
845     string module_name, current_module, filename, hppDefine;
846     unique_ptr<CDelayedOfstream> out;
847     CNcbiOstrstream out_inc;
848     CNcbiOstrstream out_code;
849     CNamespace ns;
850 
851     bool isfound = false;
852     do {
853         isfound = false;
854         bool types_found = false;
855         ITERATE ( TOutputFiles, filei, m_Files ) {
856             CFileCode* code = filei->second.get();
857             list<CTypeStrings*> filetypes;
858             code->GetClasses( filetypes );
859             module_name.clear();
860             ITERATE(list<CTypeStrings*>, t, filetypes) {
861                 string module = (*t)->GetDoxygenModuleName();
862                 if (module_name.empty() || module.size() < module_name.size()) {
863                     module_name = module;
864                 }
865             }
866             if (current_module.empty()) {
867                 if (modules.find(module_name) != modules.end()) {
868                     continue;
869                 }
870                 modules.insert(module_name);
871                 current_module = module_name;
872             } else if (current_module != module_name) {
873                 continue;
874             }
875             isfound = true;
876             if ( !out.get()  ||  !out->is_open()) {
877                 if (isdigit((unsigned char)current_module[0])) {
878                     current_module.insert(current_module.begin(),'x');
879                 }
880                 filename = Path(path, current_module + "_module.cpp");
881                 string module_inc =
882                     CDirEntry::ConcatPath( CDirEntry( code->GetUserHPPName() ).GetDir(),
883                                            current_module + "_module.hpp");
884                 out_inc <<
885                     "#include " << code->Include(module_inc) << "\n";
886                 out.reset(new CDelayedOfstream(filename.c_str()));
887                 if (!out->is_open()) {
888                     ERR_POST_X(11, Fatal << "Cannot create file: " << filename);
889                     return;
890                 }
891                 generated.push_back(filename);
892                 code->WriteCopyright(*out, false);
893                 *out << "\n";
894                 if (!CFileCode::GetPchHeader().empty()) {
895                     *out <<
896                         "#include <" << CFileCode::GetPchHeader() << ">\n";
897                 }
898                 ns.Set(code->GetNamespace(), out_code, false);
899                 out_code <<
900                     "void " << current_module << "_RegisterModuleClasses(void)\n{\n";
901             }
902             set<string> user_includes;
903             ITERATE(list<CTypeStrings*>, t, filetypes) {
904                 if ((*t)->GetKind() == CTypeStrings::eKindObject) {
905                     CClassTypeStrings* classtype = dynamic_cast<CClassTypeStrings*>(*t);
906                     if (classtype && classtype->HaveTypeInfo()) {
907                         types_found = true;
908                         string userhpp(code->Include(code->GetUserHPPName()));
909                         if (user_includes.find(userhpp) == user_includes.end()) {
910                             user_includes.insert(userhpp);
911                             out_inc <<
912                                 "#include " << code->Include(code->GetUserHPPName()) << "\n";
913                         }
914                         out_code << "    "
915                                  << code->GetClassNamespace(*t).ToString()
916                                  << classtype->GetClassNameDT() << "::GetTypeInfo();\n";
917                     }
918                 }
919             }
920         }
921         if (isfound && !types_found) {
922             generated.pop_back();
923             out->Discard();
924         }
925         if (out->is_open()) {
926             out_code << "}\n\n";
927             ns.Reset(out_code);
928             *out << string(CNcbiOstrstreamToString(out_inc))
929                  << "\n\n"
930                  << string(CNcbiOstrstreamToString(out_code));
931             out_inc.seekp(0);
932             out_code.seekp(0);
933 #ifdef NCBI_SHUN_OSTRSTREAM
934             out_inc.str(kEmptyStr);
935             out_code.str(kEmptyStr);
936 #endif
937             out->close();
938             if ( !*out )
939                 ERR_POST_X(12, Fatal << "Error writing file " << filename);
940         }
941         current_module.erase();
942     } while (isfound);
943 }
944 
GenerateClientCode(void)945 void CCodeGenerator::GenerateClientCode(void)
946 {
947     string clients = m_Config.Get("-", "clients");
948     if (clients.empty()) {
949         // // for compatibility with older specifications
950         // GenerateClientCode("client", false);
951     } else {
952         // explicit name; must be enabled
953         list<string> l;
954         // if multiple items, may have whitespace, commas, or both...
955         NStr::Split(clients, ", \t", l, NStr::fSplit_MergeDelimiters);
956         ITERATE (list<string>, it, l) {
957             if ( !it->empty() ) {
958                 GenerateClientCode(*it, true);
959             }
960         }
961     }
962 }
963 
GenerateClientCode(const string & name,bool mandatory)964 void CCodeGenerator::GenerateClientCode(const string& name, bool mandatory)
965 {
966     string class_name = m_Config.Get(name, "class");
967     if (class_name.empty()) {
968         if (mandatory) {
969             ERR_POST_X(13, Fatal << "No configuration for mandatory client " + name);
970         }
971         return; // not configured
972     }
973     CFileCode code(this,Path(m_FileNamePrefix, name));
974     CClientPseudoDataType type(*this, name, class_name);
975     code.UseQuotedForm(m_UseQuotedForm);
976     code.AddType(&type);
977     code.GenerateCode();
978     string filename;
979     code.GenerateHPP(m_HPPDir, filename);
980     code.GenerateCPP(m_CPPDir, filename);
981     code.GenerateUserHPP(m_HPPDir, filename);
982     code.GenerateUserCPP(m_CPPDir, filename);
983 }
984 
AddType(const CDataType * type)985 bool CCodeGenerator::AddType(const CDataType* type)
986 {
987     string fileName = type->FileName();
988     AutoPtr<CFileCode>& file = m_Files[fileName];
989     if ( !file )
990         file = new CFileCode(this,fileName);
991     return file->AddType(type);
992 }
993 
Imported(const CDataType * type) const994 bool CCodeGenerator::Imported(const CDataType* type) const
995 {
996     try {
997         m_MainFiles.ExternalResolve(type->GetModule()->GetName(),
998                                     type->IdName(),
999                                     true);
1000         return false;
1001     }
1002     catch ( CNotFoundException& /* ignored */) {
1003     }
1004     return true;
1005 }
1006 
CollectTypes(const CDataType * type,EContext)1007 void CCodeGenerator::CollectTypes(const CDataType* type, EContext /*context*/)
1008 {
1009     if ( type->GetParentType() == 0 ) {
1010         const CWsdlDataType* w = dynamic_cast<const CWsdlDataType*>(type);
1011         if (!w || w->GetWsdlType() == CWsdlDataType::eWsdlEndpoint) {
1012             if ( !AddType(type) ) {
1013                 return;
1014             }
1015         }
1016     }
1017 
1018     if ( m_ExcludeRecursion )
1019         return;
1020 
1021     const CUniSequenceDataType* array =
1022         dynamic_cast<const CUniSequenceDataType*>(type);
1023     if ( array != 0 ) {
1024         // we should add element type
1025         CollectTypes(array->GetElementType(), eElement);
1026         return;
1027     }
1028 
1029     const CReferenceDataType* user =
1030         dynamic_cast<const CReferenceDataType*>(type);
1031     if ( user != 0 ) {
1032         // reference to another type
1033         const CDataType* resolved;
1034         try {
1035             resolved = user->Resolve();
1036         }
1037         catch ( CNotFoundException& exc) {
1038             ERR_POST_X(14, Warning <<
1039                        "Skipping type: " << user->GetUserTypeName() <<
1040                        ": " << exc.what());
1041             return;
1042         }
1043         if ( resolved->Skipped() ) {
1044             ERR_POST_X(15, Warning << "Skipping type: " << user->GetUserTypeName());
1045             return;
1046         }
1047         if ( !Imported(resolved) ) {
1048             CollectTypes(resolved, eReference);
1049         }
1050         return;
1051     }
1052 
1053     const CDataMemberContainerType* cont =
1054         dynamic_cast<const CDataMemberContainerType*>(type);
1055     if ( cont != 0 ) {
1056         // collect member's types
1057         ITERATE ( CDataMemberContainerType::TMembers, mi,
1058                   cont->GetMembers() ) {
1059             const CDataType* memberType = mi->get()->GetType();
1060             CollectTypes(memberType, eMember);
1061         }
1062         return;
1063     }
1064 }
1065 
1066 #if 0
1067 void CCodeGenerator::CollectTypes(const CDataType* type, EContext context)
1068 {
1069     const CUniSequenceDataType* array =
1070         dynamic_cast<const CUniSequenceDataType*>(type);
1071     if ( array != 0 ) {
1072         // SET OF or SEQUENCE OF
1073         if ( type->GetParentType() == 0 || context == eChoice ) {
1074             if ( !AddType(type) )
1075                 return;
1076         }
1077         if ( m_ExcludeRecursion )
1078             return;
1079         // we should add element type
1080         CollectTypes(array->GetElementType(), eElement);
1081         return;
1082     }
1083 
1084     const CReferenceDataType* user =
1085         dynamic_cast<const CReferenceDataType*>(type);
1086     if ( user != 0 ) {
1087         // reference to another type
1088         const CDataType* resolved;
1089         try {
1090             resolved = user->Resolve();
1091         }
1092         catch ( CNotFoundException& exc) {
1093             ERR_POST_X(16, Warning <<
1094                        "Skipping type: " << user->GetUserTypeName() <<
1095                        ": " << exc.what());
1096             return;
1097         }
1098         if ( resolved->Skipped() ) {
1099             ERR_POST_X(17, Warning << "Skipping type: " << user->GetUserTypeName());
1100             return;
1101         }
1102         if ( context == eChoice ) {
1103             // in choice
1104             if ( resolved->InheritFromType() != user->GetParentType() ||
1105                  dynamic_cast<const CEnumDataType*>(resolved) != 0 ) {
1106                 // add intermediate class
1107                 AddType(user);
1108             }
1109         }
1110         else if ( type->GetParentType() == 0 ) {
1111             // alias declaration
1112             // generate empty class
1113             AddType(user);
1114         }
1115         if ( !Imported(resolved) ) {
1116             CollectTypes(resolved, eReference);
1117         }
1118         return;
1119     }
1120 
1121     if ( dynamic_cast<const CStaticDataType*>(type) != 0 ) {
1122         // STD type
1123         if ( type->GetParentType() == 0 || context == eChoice ) {
1124             AddType(type);
1125         }
1126         return;
1127     }
1128 
1129     if ( dynamic_cast<const CEnumDataType*>(type) != 0 ) {
1130         // ENUMERATED type
1131         if ( type->GetParentType() == 0 || context == eChoice ) {
1132             AddType(type);
1133         }
1134         return;
1135     }
1136 
1137     if ( type->GetParentType() == 0 || context == eChoice ) {
1138         if ( type->Skipped() ) {
1139             ERR_POST_X(18, Warning << "Skipping type: " << type->IdName());
1140             return;
1141         }
1142     }
1143 
1144     const CChoiceDataType* choice =
1145         dynamic_cast<const CChoiceDataType*>(type);
1146     if ( choice != 0 ) {
1147         if ( !AddType(type) )
1148             return;
1149 
1150         if ( m_ExcludeRecursion )
1151             return;
1152 
1153         // collect member's types
1154         ITERATE ( CDataMemberContainerType::TMembers, mi,
1155                   choice->GetMembers() ) {
1156             const CDataType* memberType = mi->get()->GetType();
1157             CollectTypes(memberType, eMember); // eChoice
1158         }
1159     }
1160 
1161     const CDataMemberContainerType* cont =
1162         dynamic_cast<const CDataMemberContainerType*>(type);
1163     if ( cont != 0 ) {
1164         if ( !AddType(type) )
1165             return;
1166 
1167         if ( m_ExcludeRecursion )
1168             return;
1169 
1170         // collect member's types
1171         ITERATE ( CDataMemberContainerType::TMembers, mi,
1172                   cont->GetMembers() ) {
1173             const CDataType* memberType = mi->get()->GetType();
1174             CollectTypes(memberType, eMember);
1175         }
1176         return;
1177     }
1178     if ( !AddType(type) )
1179         return;
1180 }
1181 #endif
1182 
ResolveImportRefs(void)1183 void CCodeGenerator::ResolveImportRefs(void)
1184 {
1185     ITERATE( CFileSet::TModuleSets, fs, GetMainModules().GetModuleSets()) {
1186         ITERATE(CFileModules::TModules, fm, (*fs)->GetModules()) {
1187             CDataTypeModule* head = const_cast<CDataTypeModule*>(fm->get());
1188             head->AddImportRef( head->GetName());
1189             ResolveImportRefs(*head,head);
1190         }
1191     }
1192 }
1193 
ResolveImportRefs(CDataTypeModule & head,const CDataTypeModule * ref)1194 void CCodeGenerator::ResolveImportRefs(CDataTypeModule& head, const CDataTypeModule* ref)
1195 {
1196     if (ref) {
1197         ITERATE ( CDataTypeModule::TImports, i, ref->GetImports() ) {
1198             const string& s = (*i)->moduleName;
1199             if (head.AddImportRef(s)) {
1200                 ResolveImportRefs(head,FindModuleByName(s));
1201             }
1202         }
1203     }
1204 }
1205 
FindModuleByName(const string & name) const1206 const CDataTypeModule* CCodeGenerator::FindModuleByName(const string& name) const
1207 {
1208     ITERATE( CFileSet::TModuleSets, mm, GetMainModules().GetModuleSets()) {
1209         ITERATE(CFileModules::TModules, fm, (*mm)->GetModules()) {
1210             if ((*fm)->GetName() == name) {
1211                 return fm->get();
1212             }
1213         }
1214     }
1215     ITERATE( CFileSet::TModuleSets, mi, GetImportModules().GetModuleSets()) {
1216         ITERATE(CFileModules::TModules, fm, (*mi)->GetModules()) {
1217             if ((*fm)->GetName() == name) {
1218                 return fm->get();
1219             }
1220         }
1221     }
1222     ERR_POST_X(19, Error << "cannot find module " << name);
1223     return 0;
1224 }
1225 
GetOpt(const string & opt,string * value)1226 bool CCodeGenerator::GetOpt(const string& opt, string* value)
1227 {
1228     string result;
1229     string key("-");
1230     string xopt = key + opt;
1231     if (m_Config.HasEntry(key, xopt)) {
1232         result = m_Config.Get(key, xopt);
1233         if (value) {
1234             *value = result;
1235         }
1236         return (result != key);
1237     }
1238     const CArgs& args = CNcbiApplication::Instance()->GetArgs();
1239     const CArgValue& argv = args[opt];
1240     if (argv.HasValue()) {
1241         if (value) {
1242             *value = argv.AsString();
1243         }
1244         return true;
1245     }
1246     return false;
1247 }
1248 
1249 
1250 END_NCBI_SCOPE
1251