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: 2004-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
6 */
7 
8 // own header
9 #include "simplecodegenerator.h"
10 
11 // app includes
12 #include "overwritedialog.h"
13 #include "model_utils.h"
14 #include "attribute.h"
15 #include "umloperationlist.h"
16 #include "umlattributelist.h"
17 #include "classifier.h"
18 #include "codedocument.h"
19 #include "codegenerationpolicy.h"
20 #include "operation.h"
21 #include "umldoc.h"
22 #include "uml.h"
23 
24 // kde includes
25 #if QT_VERSION < 0x050000
26 #include <kdialog.h>
27 #endif
28 #include <KLocalizedString>
29 #include <KMessageBox>
30 
31 // qt includes
32 #include <QApplication>
33 #include <QDateTime>
34 #include <QDir>
35 #include <QPointer>
36 #include <QRegExp>
37 
38 // system includes
39 #include <cstdlib> //to get the user name
40 
41 /**
42  * Constructor.
43  */
SimpleCodeGenerator(bool createDirHierarchyForPackages)44 SimpleCodeGenerator::SimpleCodeGenerator(bool createDirHierarchyForPackages)
45   : CodeGenerator(),
46     m_createDirHierarchyForPackages(createDirHierarchyForPackages),
47     m_indentLevel(0)
48 {
49     m_document->disconnect(this); // disconnect from UMLDoc.. we arent planning to be synced at all
50 
51     // load Classifier documents from parent document
52     // initFromParentDocument();
53 
54     m_fileMap.clear();
55 
56     // this really is just being used to sync the internal params
57     // to the codegenpolicy as there are no code documents to really sync.
58     syncCodeToDocument();
59 }
60 
61 /**
62  * Destructor.
63  */
~SimpleCodeGenerator()64 SimpleCodeGenerator::~SimpleCodeGenerator()
65 {
66 }
67 
68 /**
69  * Returns the current indent string based on m_indentLevel and m_indentation.
70  * @return indentation string
71  */
indent()72 QString SimpleCodeGenerator::indent()
73 {
74     QString myIndent;
75     for (int i = 0 ; i < m_indentLevel; ++i) {
76         myIndent.append(m_indentation);
77     }
78     return myIndent;
79 }
80 
81 /**
82  * Determine the file name.
83  * @param concept   the package
84  * @param ext       the file extension
85  * @return the valid file name
86  */
findFileName(UMLPackage * concept,const QString & ext)87 QString SimpleCodeGenerator::findFileName(UMLPackage* concept, const QString &ext)
88 {
89     //if we already know to which file this class was written/should be written, just return it.
90     if (m_fileMap.contains(concept))
91         return m_fileMap[concept];
92 
93     //else, determine the "natural" file name
94     QString name;
95     // Get the package name
96     QString package = concept->package(QLatin1String("."));
97 
98     // Replace all white spaces with blanks
99     package = package.simplified();
100 
101     // Replace all blanks with underscore
102     package.replace(QRegExp(QLatin1String(" ")), QLatin1String("_"));
103 
104     // Convert all "::" to "/" : Platform-specific path separator
105     // package.replace(QRegExp(QLatin1String("::")), QLatin1String("/"));
106 
107     // if package is given add this as a directory to the file name
108     if (!package.isEmpty() && m_createDirHierarchyForPackages) {
109         name = package + QLatin1Char('.') + concept->name();
110         name.replace(QRegExp(QLatin1String("\\.")), QLatin1String("/"));
111         package.replace(QRegExp(QLatin1String("\\.")), QLatin1String("/"));
112         package = QLatin1Char('/') + package;
113     } else {
114         name = concept->fullyQualifiedName(QLatin1String("-"));
115     }
116 
117     if (! UMLApp::app()->activeLanguageIsCaseSensitive()) {
118         package = package.toLower();
119         name = name.toLower();
120     }
121 
122     // if a package name exists check the existence of the package directory
123     if (!package.isEmpty() && m_createDirHierarchyForPackages) {
124         QDir pathDir(UMLApp::app()->commonPolicy()->getOutputDirectory().absolutePath() + package);
125         // does our complete output directory exist yet? if not, try to create it
126         if (!pathDir.exists())
127         {
128             const QStringList dirs = pathDir.absolutePath().split(QLatin1Char('/'));
129             QString currentDir;
130 
131             QStringList::const_iterator end(dirs.end());
132             for (QStringList::const_iterator dir(dirs.begin()); dir != end; ++dir)
133             {
134                 currentDir += QLatin1Char('/') + *dir;
135                 if (! (pathDir.exists(currentDir)
136                         || pathDir.mkdir(currentDir)))
137                 {
138                     KMessageBox::error(0, i18n("Cannot create the folder:\n") +
139                                        pathDir.absolutePath() + i18n("\nPlease check the access rights"),
140                                        i18n("Cannot Create Folder"));
141                     return QString();
142                 }
143             }
144         }
145     }
146 
147     name = name.simplified();
148     name.replace(QRegExp(QLatin1String(" ")), QLatin1String("_"));
149 
150     QString extension = ext.simplified();
151     extension.replace(QLatin1Char(' '), QLatin1Char('_'));
152 
153     return overwritableName(concept, name, extension);
154 }
155 
156 /**
157  * Check if a file named "name" with extension "ext" already exists.
158  * @param concept   the package
159  * @param name      the name of the file
160  * @param ext       the extension of the file
161  * @return the valid filename or null
162  */
overwritableName(UMLPackage * concept,const QString & name,const QString & ext)163 QString SimpleCodeGenerator::overwritableName(UMLPackage* concept, const QString &name, const QString &ext)
164 {
165     CodeGenerationPolicy *commonPolicy = UMLApp::app()->commonPolicy();
166     QDir outputDir = commonPolicy->getOutputDirectory();
167     QString filename = name + ext;
168     if (!outputDir.exists(filename)) {
169         m_fileMap.insert(concept, filename);
170         return filename; //if not, "name" is OK and we have not much to to
171     }
172 
173     int suffix;
174     QPointer<OverwriteDialog> overwriteDialog =
175         new OverwriteDialog(filename, outputDir.absolutePath(),
176                               m_applyToAllRemaining, qApp->activeWindow());
177     switch(commonPolicy->getOverwritePolicy()) {  //if it exists, check the OverwritePolicy we should use
178     case CodeGenerationPolicy::Ok:                //ok to overwrite file
179         break;
180     case CodeGenerationPolicy::Ask:               //ask if we can overwrite
181         switch(overwriteDialog->exec()) {
182 #if QT_VERSION >= 0x050000
183         case OverwriteDialog::Ok:  //overwrite file
184 #else
185         case QDialog::Accepted:
186 #endif
187             if (overwriteDialog->applyToAllRemaining()) {
188                 commonPolicy->setOverwritePolicy(CodeGenerationPolicy::Ok);
189             } else {
190                 m_applyToAllRemaining = false;
191             }
192             break;
193 #if QT_VERSION >= 0x050000
194         case OverwriteDialog::No: //generate similar name
195 #else
196         case KDialog::No:
197 #endif
198             suffix = 1;
199             while (1) {
200                 filename = name + QLatin1String("__") + QString::number(suffix) + ext;
201                 if (!outputDir.exists(filename))
202                     break;
203                 suffix++;
204             }
205             if (overwriteDialog->applyToAllRemaining()) {
206                 commonPolicy->setOverwritePolicy(CodeGenerationPolicy::Never);
207             } else {
208                 m_applyToAllRemaining = false;
209             }
210             break;
211 #if QT_VERSION >= 0x050000
212         case OverwriteDialog::Cancel: //don't output anything
213 #else
214         case QDialog::Rejected:
215 #endif
216             if (overwriteDialog->applyToAllRemaining()) {
217                 commonPolicy->setOverwritePolicy(CodeGenerationPolicy::Cancel);
218             } else {
219                 m_applyToAllRemaining = false;
220             }
221             delete overwriteDialog;
222             return QString();
223             break;
224         }
225         break;
226     case CodeGenerationPolicy::Never: //generate similar name
227         suffix = 1;
228         while (1) {
229             filename = name + QLatin1String("__") + QString::number(suffix) + ext;
230             if (!outputDir.exists(filename))
231                 break;
232             suffix++;
233         }
234         break;
235     case CodeGenerationPolicy::Cancel: //don't output anything
236         delete overwriteDialog;
237         return QString();
238         break;
239     }
240 
241     m_fileMap.insert(concept, filename);
242     delete overwriteDialog;
243     return filename;
244 }
245 
246 /**
247  * Check whether classifier has default values for attributes.
248  * @param c   the classifier to check
249  * @return true when classifier attributes has default values
250  */
hasDefaultValueAttr(UMLClassifier * c)251 bool SimpleCodeGenerator::hasDefaultValueAttr(UMLClassifier *c)
252 {
253     UMLAttributeList atl = c->getAttributeList();
254     foreach (UMLAttribute* at, atl) {
255         if (!at->getInitialValue().isEmpty())
256             return true;
257     }
258     return false;
259 }
260 
261 /**
262  * Check whether classifier has abstract operations.
263  * @param c   the classifier to check
264  * @return true when classifier has abstract operations
265  */
hasAbstractOps(UMLClassifier * c)266 bool SimpleCodeGenerator::hasAbstractOps(UMLClassifier *c)
267 {
268     UMLOperationList opl(c->getOpList());
269     foreach (UMLOperation* op, opl) {
270         if (op->isAbstract())
271             return true;
272     }
273     return false;
274 }
275 
276 /**
277  * Write all concepts in project to file.
278  */
writeCodeToFile()279 void SimpleCodeGenerator::writeCodeToFile()
280 {
281     UMLClassifierList concepts = m_document->classesAndInterfaces();
282     writeCodeToFile(concepts);
283 }
284 
285 /**
286  * Write only selected concepts to file.
287  * @param concepts   the selected concepts
288  */
writeCodeToFile(UMLClassifierList & concepts)289 void SimpleCodeGenerator::writeCodeToFile(UMLClassifierList & concepts)
290 {
291     m_fileMap.clear(); // ??
292     foreach (UMLClassifier* c, concepts) {
293         if (! Model_Utils::isCommonDataType(c->name()))
294             this->writeClass(c); // call the writer for each class.
295     }
296     finalizeRun();
297 }
298 
299 /**
300  * A little method to provide some compatibility between
301  * the newer codegenpolicy object and the older class fields.
302  */
syncCodeToDocument()303 void SimpleCodeGenerator::syncCodeToDocument()
304 {
305     CodeGenerationPolicy *policy = UMLApp::app()->commonPolicy();
306 
307     m_indentation = policy->getIndentation();
308     m_endl = policy->getNewLineEndingChars();
309 }
310 
311 /**
312  * Override parent method.
313  */
initFromParentDocument()314 void SimpleCodeGenerator::initFromParentDocument()
315 {
316     // do nothing
317 }
318 
319