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