1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3 
4     SPDX-FileCopyrightText: 2003 Brian Thomas <thomas@mail630.gsfc.nasa.gov>
5     SPDX-FileCopyrightText: 2003-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
6 */
7 
8 // own header
9 #include "codegenerationpolicy.h"
10 
11 // app includes
12 #include "codegenerationpolicypage.h"
13 #include "debug_utils.h"
14 #include "uml.h"
15 #include "umldoc.h"
16 #include "umbrellosettings.h"
17 
18 // kde includes
19 #include <kconfig.h>
20 #if QT_VERSION < 0x050000
21 #include <kstandarddirs.h>
22 #endif
23 
24 // qt includes
25 #include <QDateTime>
26 #include <QRegExp>
27 #if QT_VERSION >= 0x050000
28 #include <QStandardPaths>
29 #endif
30 #include <QStringList>
31 #include <QTextStream>
32 
33 using namespace std;
34 
35 #define MAXLINES 256
36 
37 /**
38  * Constructor.
39  * @param clone   generation policy to clone
40  */
CodeGenerationPolicy(CodeGenerationPolicy * clone)41 CodeGenerationPolicy::CodeGenerationPolicy(CodeGenerationPolicy * clone)
42 {
43     // first call the function which can give us values from disk, so that we have something to fall back on
44     setDefaults(false);
45     // then set the values from the object passed.
46     setDefaults(clone, false);
47 }
48 
49 /**
50  * Constructor.
51  */
CodeGenerationPolicy()52 CodeGenerationPolicy::CodeGenerationPolicy()
53 {
54     setDefaults(false);
55 }
56 
57 /**
58  * Destructor
59  */
~CodeGenerationPolicy()60 CodeGenerationPolicy::~CodeGenerationPolicy()
61 {
62 }
63 
64 /**
65  * Set the value of m_overwritePolicy
66  * Policy of how to deal with overwriting existing files. Allowed values are "ask",
67  * "yes" and "no".
68  * @param new_var the new value of m_overwritePolicy
69  */
setOverwritePolicy(OverwritePolicy new_var)70 void CodeGenerationPolicy::setOverwritePolicy (OverwritePolicy new_var)
71 {
72     Settings::optionState().codeGenerationState.overwritePolicy = new_var;
73 }
74 
75 /**
76  * Get the value of m_overwritePolicy
77  * Policy of how to deal with overwriting existing files. Allowed values are "ask",
78  * "yes" and "no".
79  * @return the overwrite policy
80  */
getOverwritePolicy() const81 CodeGenerationPolicy::OverwritePolicy CodeGenerationPolicy::getOverwritePolicy () const
82 {
83     return Settings::optionState().codeGenerationState.overwritePolicy;
84 }
85 
86 /**
87  * Set the value of m_commentStyle
88  * @param new_var the new value of m_commentStyle
89  */
setCommentStyle(CommentStyle new_var)90 void CodeGenerationPolicy::setCommentStyle (CommentStyle new_var)
91 {
92     Settings::optionState().codeGenerationState.commentStyle = new_var;
93     emit modifiedCodeContent();
94 }
95 
96 /**
97  * Get the value of m_commentStyle
98  * @return the comment style
99  */
getCommentStyle()100 CodeGenerationPolicy::CommentStyle CodeGenerationPolicy::getCommentStyle()
101 {
102     return Settings::optionState().codeGenerationState.commentStyle;
103 }
104 
105 /**
106  * Set the value of m_codeVerboseSectionComments
107  * Whether or not verbose code commenting for sections is desired. If true, comments
108  * for sections will be written even if the section is empty.
109  * @param new_var the new value of m_codeVerboseSectionComments
110  */
setCodeVerboseSectionComments(bool new_var)111 void CodeGenerationPolicy::setCodeVerboseSectionComments (bool new_var)
112 {
113     Settings::optionState().codeGenerationState.forceSections = new_var;
114     emit modifiedCodeContent();
115 }
116 
117 /**
118  * Get the value of m_codeVerboseSectionComments
119  * Whether or not verbose code commenting for sections is desired. If true, comments
120  * for sections will be written even if the section is empty.
121  * @return the flag whether verbose code commenting for sections is desired
122  */
getCodeVerboseSectionComments() const123 bool CodeGenerationPolicy::getCodeVerboseSectionComments () const
124 {
125     return Settings::optionState().codeGenerationState.forceSections;
126 }
127 
128 /**
129  * Set the value of m_codeVerboseDocumentComments
130  * Whether or not verbose code commenting for documentation is desired. If true,
131  * documentation for various code will be written even if no code would normally be
132  * created at that point in the file.
133  * @param new_var the new value to set verbose code commenting
134  */
setCodeVerboseDocumentComments(bool new_var)135 void CodeGenerationPolicy::setCodeVerboseDocumentComments (bool new_var)
136 {
137     Settings::optionState().codeGenerationState.forceDoc = new_var;
138     emit modifiedCodeContent();
139 }
140 
141 /**
142  * Get the value of m_codeVerboseDocumentComments
143  * Whether or not verbose code commenting for documentation is desired. If true,
144  * documentation for various code will be written even if no code would normally be
145  * created at that point in the file.
146  * @return the value of m_codeVerboseDocumentComments
147  */
getCodeVerboseDocumentComments() const148 bool CodeGenerationPolicy::getCodeVerboseDocumentComments () const
149 {
150     return Settings::optionState().codeGenerationState.forceDoc;
151 }
152 
153 /**
154  * Set the value of m_headingFileDir
155  * location of the header file template.
156  * @param path   the new value of m_headingFileDir
157  */
setHeadingFileDir(const QString & path)158 void CodeGenerationPolicy::setHeadingFileDir (const QString & path)
159 {
160     Settings::optionState().codeGenerationState.headingsDirectory.setPath(path);
161 }
162 
163 /**
164  * Get the value of m_headingFileDir
165  * location of the header file template.
166  * @return the value of m_headingFileDir
167  */
getHeadingFileDir() const168 QString CodeGenerationPolicy::getHeadingFileDir () const
169 {
170     return Settings::optionState().codeGenerationState.headingsDirectory.absolutePath();
171 }
172 
173 /**
174  * Set the value of m_includeHeadings
175  * @param new_var the new value of m_includeHeadings
176  */
setIncludeHeadings(bool new_var)177 void CodeGenerationPolicy::setIncludeHeadings (bool new_var)
178 {
179     Settings::optionState().codeGenerationState.includeHeadings = new_var;
180     emit modifiedCodeContent();
181 }
182 
183 /**
184  * Get the value of m_includeHeadings
185  * @return the value of m_includeHeadings
186  */
getIncludeHeadings() const187 bool CodeGenerationPolicy::getIncludeHeadings () const
188 {
189     return Settings::optionState().codeGenerationState.includeHeadings;
190 }
191 
192 /**
193  * Set the value of m_outputDirectory
194  * location of where output files will go.
195  * @param new_var the new value of m_outputDirectory
196  */
setOutputDirectory(QDir new_var)197 void CodeGenerationPolicy::setOutputDirectory (QDir new_var)
198 {
199     Settings::optionState().codeGenerationState.outputDirectory = new_var;
200 }
201 
202 /**
203  * Get the value of m_outputDirectory
204  * location of where output files will go.
205  * @return the value of m_outputDirectory
206  */
getOutputDirectory()207 QDir CodeGenerationPolicy::getOutputDirectory ()
208 {
209     return Settings::optionState().codeGenerationState.outputDirectory;
210 }
211 
212 /**
213  * Set the value of m_lineEndingType
214  * What line ending characters to use.
215  * @param type   the new value of m_lineEndingType
216  */
setLineEndingType(NewLineType type)217 void CodeGenerationPolicy::setLineEndingType (NewLineType type)
218 {
219     Settings::optionState().codeGenerationState.lineEndingType = type;
220     switch (Settings::optionState().codeGenerationState.lineEndingType) {
221     case DOS:
222         m_lineEndingChars = QString(QLatin1String("\r\n"));
223         break;
224     case MAC:
225         m_lineEndingChars = QString(QLatin1String("\r"));
226         break;
227     case UNIX:
228     default:
229         m_lineEndingChars = QString(QLatin1String("\n"));
230         break;
231     }
232     emit modifiedCodeContent();
233 }
234 
235 /**
236  * Get the value of m_lineEndingType
237  * What line ending characters to use.
238  * @return the value of m_lineEndingType
239  */
getLineEndingType()240 CodeGenerationPolicy::NewLineType CodeGenerationPolicy::getLineEndingType ()
241 {
242     return Settings::optionState().codeGenerationState.lineEndingType;
243 }
244 
245 /**
246  * Utility function to get the actual characters.
247  * @return   the line ending characters
248  */
getNewLineEndingChars() const249 QString CodeGenerationPolicy::getNewLineEndingChars () const
250 {
251     return m_lineEndingChars;
252 }
253 
254 /**
255  * Set the value of m_indentationType
256  * The amount and type of whitespace to indent with.
257  * @param new_var the new value of m_indentationType
258  */
setIndentationType(IndentationType new_var)259 void CodeGenerationPolicy::setIndentationType (IndentationType new_var)
260 {
261     Settings::optionState().codeGenerationState.indentationType = new_var;
262     calculateIndentation();
263     emit modifiedCodeContent();
264 }
265 
266 /**
267  * Get the value of m_indentationType
268  */
getIndentationType()269 CodeGenerationPolicy::IndentationType CodeGenerationPolicy::getIndentationType ()
270 {
271     return Settings::optionState().codeGenerationState.indentationType;
272 }
273 
274 /**
275  * Set how many units to indent for each indentation level.
276  * @param amount   the amount of indentation units
277  */
setIndentationAmount(int amount)278 void CodeGenerationPolicy::setIndentationAmount (int amount)
279 {
280     if (amount > -1)
281     {
282         Settings::optionState().codeGenerationState.indentationAmount = amount;
283         calculateIndentation();
284         emit modifiedCodeContent();
285     }
286 }
287 
288 /**
289  * Get indentation level units.
290  */
getIndentationAmount()291 int CodeGenerationPolicy::getIndentationAmount ()
292 {
293     return Settings::optionState().codeGenerationState.indentationAmount;
294 }
295 
296 /**
297  * Utility method to get the amount (and type of whitespace) to indent with.
298  * @return the value of the indentation
299  */
getIndentation() const300 QString CodeGenerationPolicy::getIndentation () const
301 {
302     return m_indentation;
303 }
304 
305 /**
306  * Calculate the indentation.
307  */
calculateIndentation()308 void CodeGenerationPolicy::calculateIndentation ()
309 {
310     QString indent;
311     m_indentation.clear();
312     switch (Settings::optionState().codeGenerationState.indentationType) {
313     case NONE:
314         break;
315     case TAB:
316         indent = QString(QLatin1String("\t"));
317         break;
318     default:
319     case SPACE:
320         indent = QString(QLatin1String(" "));
321         break;
322     }
323 
324     for (int i = 0; i < Settings::optionState().codeGenerationState.indentationAmount; ++i) {
325         m_indentation += indent;
326     }
327 }
328 
329 /**
330  * Set the value of m_modifyPolicy
331  * @param new_var the new value of m_modifyPolicy
332  */
setModifyPolicy(ModifyNamePolicy new_var)333 void CodeGenerationPolicy::setModifyPolicy (ModifyNamePolicy new_var)
334 {
335     Settings::optionState().codeGenerationState.modnamePolicy = new_var;
336 }
337 
338 /**
339  * Get the value of m_modifyPolicy
340  * @return the value of m_modifyPolicy
341  */
getModifyPolicy() const342 CodeGenerationPolicy::ModifyNamePolicy CodeGenerationPolicy::getModifyPolicy () const
343 {
344     return Settings::optionState().codeGenerationState.modnamePolicy;
345 }
346 
347 /**
348  * Set the value of m_autoGenerateConstructors
349  * @param var   the new value
350  */
setAutoGenerateConstructors(bool var)351 void CodeGenerationPolicy::setAutoGenerateConstructors(bool var)
352 {
353     Settings::optionState().codeGenerationState.autoGenEmptyConstructors = var;
354     emit modifiedCodeContent();
355 }
356 
357 /**
358  * Get the value of m_autoGenerateConstructors
359  * @return the value of m_autoGenerateConstructors
360  */
getAutoGenerateConstructors()361 bool CodeGenerationPolicy::getAutoGenerateConstructors()
362 {
363     return Settings::optionState().codeGenerationState.autoGenEmptyConstructors;
364 }
365 
366 /**
367  * Set the value of m_attributeAccessorScope
368  * @param var the new value
369  */
setAttributeAccessorScope(Uml::Visibility::Enum var)370 void CodeGenerationPolicy::setAttributeAccessorScope(Uml::Visibility::Enum var)
371 {
372     Settings::optionState().codeGenerationState.defaultAttributeAccessorScope = var;
373     emit modifiedCodeContent();
374 }
375 
376 /**
377  * Get the value of m_attributeAccessorScope
378  * @return the Visibility value of m_attributeAccessorScope
379  */
getAttributeAccessorScope()380 Uml::Visibility::Enum CodeGenerationPolicy::getAttributeAccessorScope()
381 {
382     return Settings::optionState().codeGenerationState.defaultAttributeAccessorScope;
383 }
384 
385 /**
386  * Set the value of m_associationFieldScope
387  * @param var the new value
388  */
setAssociationFieldScope(Uml::Visibility::Enum var)389 void CodeGenerationPolicy::setAssociationFieldScope(Uml::Visibility::Enum var)
390 {
391     Settings::optionState().codeGenerationState.defaultAssocFieldScope = var;
392     emit modifiedCodeContent();
393 }
394 
395 /**
396  * Get the value of m_associationFieldScope
397  * @return the Visibility value of m_associationFieldScope
398  */
getAssociationFieldScope()399 Uml::Visibility::Enum CodeGenerationPolicy::getAssociationFieldScope()
400 {
401     return Settings::optionState().codeGenerationState.defaultAssocFieldScope;
402 }
403 
404 /**
405  * Create a new dialog interface for this object.
406  * @return dialog object
407  */
createPage(QWidget * pWidget,const char * name)408 CodeGenerationPolicyPage * CodeGenerationPolicy::createPage (QWidget *pWidget, const char *name)
409 {
410     return new CodeGenerationPolicyPage (pWidget, name, 0);
411 }
412 
413 /**
414  * Emits the signal 'ModifiedCodeContent'.
415  */
emitModifiedCodeContentSig()416 void CodeGenerationPolicy::emitModifiedCodeContentSig()
417 {
418     if (!UMLApp::app()->document()->loading())
419         emit modifiedCodeContent();
420 }
421 
422 /**
423  * set the defaults from a config file
424  */
setDefaults(CodeGenerationPolicy * clone,bool emitUpdateSignal)425 void CodeGenerationPolicy::setDefaults (CodeGenerationPolicy * clone, bool emitUpdateSignal)
426 {
427     if (!clone)
428         return;
429 
430     blockSignals(true); // we need to do this because otherwise most of these
431     // settors below will each send the modifiedCodeContent() signal
432     // needlessly (we can just make one call at the end).
433 
434     setCodeVerboseSectionComments (clone->getCodeVerboseSectionComments());
435     setCodeVerboseDocumentComments (clone->getCodeVerboseDocumentComments());
436     setHeadingFileDir (clone->getHeadingFileDir());
437     setIncludeHeadings (clone->getIncludeHeadings());
438     setOutputDirectory (clone->getOutputDirectory());
439     setLineEndingType (clone->getLineEndingType());
440     setIndentationAmount (clone->getIndentationAmount());
441     setIndentationType (clone->getIndentationType());
442     setModifyPolicy (clone->getModifyPolicy());
443     setOverwritePolicy (clone->getOverwritePolicy());
444 
445     calculateIndentation();
446     blockSignals(false); // "as you were citizen"
447 
448     if (emitUpdateSignal)
449         emit modifiedCodeContent();
450 }
451 
452 /**
453  * set the defaults from a config file
454  */
setDefaults(bool emitUpdateSignal)455 void CodeGenerationPolicy::setDefaults(bool emitUpdateSignal)
456 {
457     blockSignals(true); // we need to do this because otherwise most of these
458     // settors below will each send the modifiedCodeContent() signal
459     // needlessly (we can just make one call at the end).
460 
461     setCodeVerboseSectionComments(UmbrelloSettings::forceSections());
462     setCodeVerboseDocumentComments(UmbrelloSettings::forceDoc());
463     setLineEndingType(UmbrelloSettings::lineEndingType());
464     setIndentationType(UmbrelloSettings::indentationType());
465     setIndentationAmount(UmbrelloSettings::indentationAmount());
466     setAutoGenerateConstructors(UmbrelloSettings::autoGenEmptyConstructors());
467     setAttributeAccessorScope(UmbrelloSettings::defaultAttributeAccessorScope());
468     setAssociationFieldScope(UmbrelloSettings::defaultAssocFieldScope());
469     setCommentStyle(UmbrelloSettings::commentStyle());
470 
471     calculateIndentation();
472 
473     QString path = UmbrelloSettings::outputDirectory();
474     if (path.isEmpty())
475         path = QDir::homePath() + QLatin1String("/uml-generated-code/");
476     setOutputDirectory (QDir (path));
477 
478     path = UmbrelloSettings::headingsDirectory();
479     if (path.isEmpty()) {
480 #if QT_VERSION >= 0x050000
481         path =  QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
482                                           QLatin1String("umbrello5/headings"),
483                                           QStandardPaths::LocateDirectory).first();
484 #else
485         KStandardDirs stddirs;
486         path =  stddirs.findDirs("data", QLatin1String("umbrello/headings")).first();
487 #endif
488     }
489     setHeadingFileDir (path);
490 
491     setIncludeHeadings(UmbrelloSettings::includeHeadings());
492     setOverwritePolicy(UmbrelloSettings::overwritePolicy());
493     setModifyPolicy(UmbrelloSettings::modnamePolicy());
494 
495     blockSignals(false); // "as you were citizen"
496 
497     if (emitUpdateSignal)
498         emit modifiedCodeContent();
499 }
500 
501 /**
502  * Write Default params.
503  */
writeConfig()504 void CodeGenerationPolicy::writeConfig ()
505 {
506     UmbrelloSettings::setDefaultAttributeAccessorScope(getAttributeAccessorScope());
507     UmbrelloSettings::setDefaultAssocFieldScope(getAssociationFieldScope());
508     UmbrelloSettings::setCommentStyle(getCommentStyle());
509     UmbrelloSettings::setAutoGenEmptyConstructors(getAutoGenerateConstructors());
510     //UmbrelloSettings::setNewcodegen(getNewCodegen());
511     UmbrelloSettings::setForceDoc(getCodeVerboseDocumentComments());
512     UmbrelloSettings::setForceSections(getCodeVerboseSectionComments());
513 
514     UmbrelloSettings::setLineEndingType(getLineEndingType());
515     UmbrelloSettings::setIndentationType(getIndentationType());
516     UmbrelloSettings::setIndentationAmount(getIndentationAmount());
517 
518     UmbrelloSettings::setOutputDirectory(getOutputDirectory().absolutePath());
519     UmbrelloSettings::setHeadingsDirectory(getHeadingFileDir());
520     UmbrelloSettings::setIncludeHeadings(getIncludeHeadings());
521     UmbrelloSettings::setOverwritePolicy(getOverwritePolicy());
522     UmbrelloSettings::setModnamePolicy(getModifyPolicy());
523 
524     // this will be written to the disk from the place it was called :)
525 }
526 
527 /**
528  *  Gets the heading file (as a string) to be inserted at the
529  *  beginning of the generated file. you give the file type as
530  *  parameter and get the string. if fileName starts with a
531  *  period (.) then fileName is the extension (.cpp, .h,
532  *  .java) if fileName starts with another character you are
533  *  requesting a specific file (mylicensefile.txt).  The files
534  *  can have parameters which are denoted by %parameter%.
535  *
536  *  current parameters are
537  *  %author%
538  *  %date%
539  *  %time%
540  *  %filepath%
541  */
getHeadingFile(const QString & str)542 QString CodeGenerationPolicy::getHeadingFile(const QString& str)
543 {
544     if (!getIncludeHeadings() || str.isEmpty())
545         return QString();
546     if (str.contains(QLatin1String(" ")) || str.contains(QLatin1String(";"))) {
547         uWarning() << "File folder must not have spaces or semi colons!";
548         return QString();
549     }
550     //if we only get the extension, then we look for the default
551     // heading.[extension]. If there is no such file, we try to
552     // get any file with the same extension
553     QString filename;
554     QDir headingFiles = Settings::optionState().codeGenerationState.headingsDirectory;
555     if (str.startsWith(QLatin1Char('.'))) {
556         if (QFile::exists(headingFiles.absoluteFilePath(QLatin1String("heading") + str)))
557             filename = headingFiles.absoluteFilePath(QLatin1String("heading") + str);
558         else {
559             QStringList filters;
560             filters << QLatin1Char('*') + str;
561             headingFiles.setNameFilters(filters);
562             //if there is more than one match we just take the first one
563             QStringList fileList = headingFiles.entryList();
564             if (!fileList.isEmpty())
565               filename = headingFiles.absoluteFilePath(fileList.first());
566             // uWarning() << "header file name set to " << filename << " because it was *";
567         }
568     } else {   //we got a file name (not only extension)
569         filename = headingFiles.absoluteFilePath(str);
570     }
571 
572     if (filename.isEmpty())
573         return QString();
574     QFile f(filename);
575     if (!f.open(QIODevice::ReadOnly)) {
576         // uWarning() << "Error opening heading file: " << f.name();
577         // uWarning() << "Headings directory was " << headingFiles.absolutePath();
578         return QString();
579     }
580 
581     QTextStream ts(&f);
582     QString retstr;
583     QString endLine = getNewLineEndingChars();
584     for (int l = 0; l < MAXLINES && !ts.atEnd(); ++l) {
585         retstr += ts.readLine()+endLine;
586     }
587 
588     //do variable substitution
589     retstr.replace(QRegExp(QLatin1String("%author%")),
590                    QString::fromLatin1(qgetenv("USER")));  //get the user name from some where else
591     retstr.replace(QRegExp(QLatin1String("%headingpath%")), filename);
592     retstr.replace(QRegExp(QLatin1String("%time%")), QTime::currentTime().toString());
593     retstr.replace(QRegExp(QLatin1String("%date%")), QDate::currentDate().toString());
594     // the replace filepath, time parts are also in the code document updateHeader method
595     // (which is not a virtual function)...
596 
597     return retstr;
598 }
599 
600