1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2005-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 // own header
7 #include "idlimport.h"
8 
9 // app includes
10 #include "attribute.h"
11 #include "classifier.h"
12 #include "codeimpthread.h"
13 #include "debug_utils.h"
14 #include "enum.h"
15 #include "import_utils.h"
16 #include "object_factory.h"
17 #include "operation.h"
18 #include "package.h"
19 #include "stereotype.h"
20 #include "uml.h"
21 #include "umldoc.h"
22 #include "umlpackagelist.h"
23 
24 // kde includes
25 #if QT_VERSION < 0x050000
26 #include <KStandardDirs>
27 #endif
28 
29 // qt includes
30 #include <QProcess>
31 #include <QRegExp>
32 #if QT_VERSION >= 0x050000
33 #include <QStandardPaths>
34 #endif
35 #include <QStringList>
36 
37 #include <stdio.h>
38 
39 QString IDLImport::m_preProcessor;
40 QStringList IDLImport::m_preProcessorArguments;
41 bool IDLImport::m_preProcessorChecked = false;
42 
IDLImport(CodeImpThread * thread)43 IDLImport::IDLImport(CodeImpThread* thread) : NativeImportBase(QLatin1String("//"), thread)
44 {
45     m_doc = UMLApp::app()->document();
46     m_isOneway = m_isReadonly = m_isAttribute = m_isUnionDefault = false;
47     setMultiLineComment(QLatin1String("/*"), QLatin1String("*/"));
48 
49     // we do not want to find the executable on each imported file
50     if (m_preProcessorChecked) {
51         m_enabled = !m_preProcessor.isEmpty();
52         return;
53     }
54 
55     QStringList arguments;
56 #if QT_VERSION >= 0x050000
57     QString executable = QStandardPaths::findExecutable(QLatin1String("cpp"));
58 #else
59     QString executable = KStandardDirs::findExe(QLatin1String("cpp"));
60 #endif
61     if (!executable.isEmpty()) {
62         arguments << QLatin1String("-C");   // -C means "preserve comments"
63     }
64 #ifdef Q_OS_WIN
65     else {
66 #if QT_VERSION >= 0x050000
67         executable = QStandardPaths::findExecutable(QLatin1String("cl"));
68 #else
69         executable = KStandardDirs::findExe(QLatin1String("cl"));
70 #endif
71         if (executable.isEmpty()) {
72             QString path = QLatin1String(qgetenv("VS100COMNTOOLS").constData());
73             if (!path.isEmpty())
74 #if QT_VERSION >= 0x050000
75                 executable = QStandardPaths::findExecutable(QLatin1String("cl"), QStringList() << path + QLatin1String("/../../VC/bin"));
76 #else
77                 executable = KStandardDirs::findExe(QLatin1String("cl"), path + QLatin1String("/../../VC/bin"));
78 #endif
79         }
80         if (!executable.isEmpty()) {
81             arguments << QLatin1String("-E");   // -E means "preprocess to stdout"
82         }
83     }
84 #endif
85     if (!executable.isEmpty()) {
86         m_preProcessor = executable;
87         m_preProcessorArguments = arguments;
88     }
89     else {
90         log("Error: Cannot find any of the supported preprocessors (gcc, Microsoft Visual Studio 2010)");
91         m_enabled = false;
92     }
93     m_preProcessorChecked = true;
94 }
95 
~IDLImport()96 IDLImport::~IDLImport()
97 {
98 }
99 
100 /// Check for split type names (e.g. unsigned long long)
joinTypename()101 QString IDLImport::joinTypename()
102 {
103     QString typeName = m_source[m_srcIndex];
104     if (m_source[m_srcIndex] == QLatin1String("unsigned"))
105         typeName += QLatin1Char(' ') + advance();
106     if (m_source[m_srcIndex] == QLatin1String("long") &&
107             (m_source[m_srcIndex + 1] == QLatin1String("long") ||
108              m_source[m_srcIndex + 1] == QLatin1String("double")))
109         typeName += QLatin1Char(' ') + advance();
110     return typeName;
111 }
112 
113 /**
114  * Override operation from NativeImportBase.
115  */
preprocess(QString & line)116 bool IDLImport::preprocess(QString& line)
117 {
118     // Ignore C preprocessor generated lines.
119     if (line.startsWith(QLatin1Char('#')))
120         return true;  // done
121     return NativeImportBase::preprocess(line);
122 }
123 
124 /**
125  * Implement abstract operation from NativeImportBase.
126  */
fillSource(const QString & word)127 void IDLImport::fillSource(const QString& word)
128 {
129     QString lexeme;
130     const uint len = word.length();
131     for (uint i = 0; i < len; ++i) {
132         QChar c = word[i];
133         if (c.isLetterOrNumber() || c == QLatin1Char('_')) {
134             lexeme += c;
135         } else if (c == QLatin1Char(':') && i < len-1 && word[i + 1] == QLatin1Char(':')) {
136             // compress scoped name into lexeme
137             lexeme += QLatin1String("::");
138             i++;
139         } else {
140             if (!lexeme.isEmpty()) {
141                 m_source.append(lexeme);
142                 lexeme.clear();
143             }
144             m_source.append(QString(c));
145         }
146     }
147     if (!lexeme.isEmpty())
148         m_source.append(lexeme);
149 }
150 
151 /**
152  * Reimplement operation from NativeImportBase.
153  * Need to do this because we use the external C preprocessor.
154  */
parseFile(const QString & filename)155 bool IDLImport::parseFile(const QString& filename)
156 {
157     if (filename.contains(QLatin1Char('/'))) {
158         QString path = filename;
159         path.remove(QRegExp(QLatin1String("/[^/]+$")));
160         uDebug() << "adding path " << path;
161         Import_Utils::addIncludePath(path);
162     }
163     const QStringList includePaths = Import_Utils::includePathList();
164 
165     if (m_preProcessor.isEmpty()) {
166         log("Error: no preprocessor installed, could not import file");
167         return false;
168     }
169     QStringList arguments(m_preProcessorArguments);
170 
171     QProcess p(UMLApp::app());
172     for (QStringList::ConstIterator pathIt = includePaths.begin();
173             pathIt != includePaths.end(); ++pathIt) {
174         QString path = (*pathIt);
175         arguments << QLatin1String("-I") + path;
176     }
177     arguments << filename;
178     uDebug() << "importIDL: " << m_preProcessor << arguments;
179     p.start(m_preProcessor, arguments);
180     if (!p.waitForStarted()) {
181         log("Error: could not run preprocessor");
182         return false;
183     }
184 
185     if (!p.waitForFinished()) {
186         log("Error: could not run preprocessor");
187         return false;
188     }
189     int exitCode = p.exitCode();
190     if (exitCode  != 0) {
191         QString errMsg = QLatin1String("Error: preprocessor returned error");
192         log(errMsg + QString::number(exitCode));
193         return false;
194     }
195 
196     QByteArray out = p.readAllStandardOutput();
197     QTextStream data(out);
198 
199     // Scan the input file into the QStringList m_source.
200     m_source.clear();
201     while (!data.atEnd()) {
202         NativeImportBase::scan(data.readLine());
203     }
204 
205     // Parse the QStringList m_source.
206     m_scope.clear();
207     pushScope(0); // global scope
208     const int srcLength = m_source.count();
209     for (m_srcIndex = 0; m_srcIndex < srcLength; ++m_srcIndex) {
210         const QString& keyword = m_source[m_srcIndex];
211         //uDebug() << QLatin1Char('"') << keyword << QLatin1Char('"');
212         if (keyword.startsWith(m_singleLineCommentIntro)) {
213             m_comment = keyword.mid(m_singleLineCommentIntro.length());
214             continue;
215         }
216         if (! parseStmt())
217             skipStmt();
218         m_currentAccess = Uml::Visibility::Public;
219         m_comment.clear();
220     }
221     return true;
222 }
223 
224 /**
225  * Skip to the end of struct/union/valuetype/interface declaration.
226  *
227  * @return True for success, false for non recoverable syntax error
228  *         related to brace matching (missing opening or closing brace).
229  */
skipStructure()230 bool IDLImport::skipStructure()
231 {
232     bool status = skipToClosing(QLatin1Char('{'));  // skip to '}'
233     // Skipping of ';' after '}' is done by NativeImportBase::parseFile
234     return status;
235 }
236 
237 /**
238  * Returns true if the given text is a valid IDL scoped name.
239  */
isValidScopedName(QString text)240 bool IDLImport::isValidScopedName(QString text) {
241     QRegExp validScopedName(QLatin1String("^[A-Za-z_:][A-Za-z0-9_:]*$"));
242     return text.contains(validScopedName);
243 }
244 
245 /**
246  * Implement abstract operation from NativeImportBase.
247  * The function only returns false if an error is encountered from which
248  * no recovery is possible.
249  * On recoverable syntax errors, the function issues an error message,
250  * skips to the end of the offending declaration, and returns true.
251  * Returning true in spite of a local syntax error is done in the interest
252  * of best effort (returning false would abort the entire code import).
253  * A syntax error is typically unrecoverable when it involves imbalance of
254  * braces, in particular when the closing "}" of an opening "{" is missing.
255  */
parseStmt()256 bool IDLImport::parseStmt()
257 {
258     QString keyword = m_source[m_srcIndex];
259     const int srcLength = m_source.count();
260     uDebug() << "keyword is " << keyword;
261     if (keyword == QLatin1String("module")) {
262         const QString& name = advance();
263         UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Package,
264                                                       name, currentScope(), m_comment);
265         pushScope(ns->asUMLPackage());
266         currentScope()->setStereotype(QLatin1String("idlModule"));
267         if (advance() != QLatin1String("{")) {
268             log(QLatin1String("Error: importIDL: unexpected: ") + m_source[m_srcIndex]);
269             skipStmt(QLatin1String("{"));
270         }
271         return true;
272     }
273     if (keyword == QLatin1String("interface")) {
274         const QString& name = advance();
275         UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class,
276                                                       name, currentScope(), m_comment);
277         m_klass = ns->asUMLClassifier();
278         m_klass->setStereotype(QLatin1String("idlInterface"));
279         m_klass->setAbstract(m_isAbstract);
280         m_isAbstract = false;
281         m_comment.clear();
282         if (advance() == QLatin1String(";"))   // forward declaration
283             return true;
284         pushScope(m_klass);
285         if (m_source[m_srcIndex] == QLatin1String(":")) {
286             while (++m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String("{")) {
287                 const QString& baseName = m_source[m_srcIndex];
288                 Import_Utils::createGeneralization(m_klass, baseName);
289                 if (advance() != QLatin1String(","))
290                     break;
291             }
292         }
293         if (m_source[m_srcIndex] != QLatin1String("{")) {
294             log(QLatin1String("Error: importIDL: ignoring excess chars at ") + name);
295             skipStmt(QLatin1String("{"));
296         }
297         return true;
298     }
299     if (keyword == QLatin1String("struct") || keyword == QLatin1String("exception")) {
300         const QString& name = advance();
301         UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class,
302                                                       name, currentScope(), m_comment);
303         m_klass = ns->asUMLClassifier();
304         pushScope(m_klass);
305         if (keyword == QLatin1String("struct"))
306             m_klass->setStereotype(QLatin1String("idlStruct"));
307         else
308             m_klass->setStereotype(QLatin1String("idlException"));
309         if (advance() != QLatin1String("{")) {
310             log(QLatin1String("Error: importIDL: expecting '{' at ") + name);
311             skipStmt(QLatin1String("{"));
312         }
313         return true;
314     }
315     if (keyword == QLatin1String("union")) {
316         const QString& name = advance();
317         // create union type
318         UMLObject *u = Import_Utils::createUMLObject(UMLObject::ot_Class,
319                                                      name, currentScope(), m_comment,
320                                                      QLatin1String("idlUnion"));
321         if (!u) {
322             log(QLatin1String("Error: importIDL: Could not create union ") + name);
323             return skipStructure();
324         }
325         UMLClassifier *uc = u->asUMLClassifier();
326         if (!uc) {
327             log(QLatin1String("Error: importIDL(") + name +
328                 QLatin1String("): Expecting type ot_Class, actual type is ") +
329                 UMLObject::toString(u->baseType()));
330             return skipStructure();
331         }
332         if (advance() != QLatin1String("switch")) {
333             log(QLatin1String("Error: importIDL: expecting 'switch' after ") + name);
334             return skipStructure();
335         }
336         if (advance() != QLatin1String("(")) {
337             log(QLatin1String("Error: importIDL: expecting '(' after 'switch'"));
338             return skipStructure();
339         }
340         const QString& switchType = advance();
341         if (!isValidScopedName(switchType)) {
342             log(QLatin1String("Error: importIDL: expecting typename after 'switch'"));
343             return skipStructure();
344         }
345         // find or create discriminator type
346         UMLObject *dt = Import_Utils::createUMLObject(UMLObject::ot_Class,
347                          switchType, currentScope(), m_comment);
348         if (!dt) {
349             log(QLatin1String("Error: importIDL(") + name +
350                                               QLatin1String("): Could not create switchtype ") + switchType);
351             return skipStructure();
352         }
353         UMLClassifier *discrType = dt->asUMLClassifier();
354         if (!discrType) {
355             log(QLatin1String("Error: importIDL(") + name +
356                 QLatin1String(") swichtype: Expecting classifier type, found ") +
357                 UMLObject::toString(dt->baseType()));
358             return skipStructure();
359         }
360         m_currentAccess = Uml::Visibility::Public;
361         UMLAttribute *discr = Import_Utils::insertAttribute(uc, m_currentAccess,
362                                                             QLatin1String("discriminator"),
363                                                             discrType, m_comment);
364         if (!discr) {
365             log(QLatin1String("Error: importIDL(") + name +
366                 QLatin1String("): Could not create switch attribute"));
367             return skipStructure();
368         }
369         discr->setStereotype(QLatin1String("idlSwitch"));
370         if (advance() != QLatin1String(")")) {
371             log(QLatin1String("Error: importIDL: expecting ')' after switch type"));
372             return skipStructure();
373         }
374         if (advance() != QLatin1String("{")) {
375             log(QLatin1String("Error: importIDL(") + name +
376                 QLatin1String("): expecting '{' after switch type"));
377             return skipStructure();
378         }
379         m_klass = uc;
380         pushScope(m_klass);
381         m_unionCases.clear();
382         m_isUnionDefault = false;
383         return true;
384     }
385     if (keyword == QLatin1String("case")) {
386         QString caseValue = advance();
387         if (!isValidScopedName(caseValue)) {
388             log(QLatin1String("Error: importIDL: expecting symbolic identifier after 'case'"));
389             skipStmt(QLatin1String(":"));
390         }
391         m_unionCases.append(caseValue);
392         if (advance() != QLatin1String(":")) {
393             log(QLatin1String("Error: importIDL: expecting ':' after 'case'"));
394         }
395         return true;
396     }
397     if (keyword == QLatin1String("default")) {
398         m_isUnionDefault = true;
399         if (advance() != QLatin1String(":")) {
400             log(QLatin1String("Error: importIDL: expecting ':' after 'default'"));
401         }
402         return true;
403     }
404     if (keyword == QLatin1String("enum")) {
405         const QString& name = advance();
406         UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Enum,
407                         name, currentScope(), m_comment);
408         UMLEnum *enumType = ns->asUMLEnum();
409         if (enumType == 0)
410             enumType = Import_Utils::remapUMLEnum(ns, currentScope());
411         m_srcIndex++;  // skip name
412         while (++m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String("}")) {
413             if (enumType != 0)
414                 Import_Utils::addEnumLiteral(enumType, m_source[m_srcIndex]);
415             if (advance() != QLatin1String(","))
416                 break;
417         }
418         skipStmt();
419         return true;
420     }
421     if (keyword == QLatin1String("typedef")) {
422         if (m_source[m_srcIndex + 2] == QLatin1String("<")) {
423             keyword = advance();
424             m_srcIndex += 2;
425             QString oldType;
426             QString bound;
427             if (keyword == QLatin1String("sequence")) {
428                 oldType = joinTypename();
429                 if (advance() == QLatin1String(",")) {
430                     bound = advance();
431                     m_srcIndex++;  // position on ">"
432                 }
433             } else if (keyword ==  QLatin1String("string")) {
434                 bound = m_source[m_srcIndex];
435                 m_srcIndex++;  // position on ">"
436             } else {
437                 log(QLatin1String("Error: parseStmt: Expecting 'sequence' or 'string', found ") +
438                     keyword);
439                 skipStmt();
440                 return true;
441             }
442             const QString& newType = advance();
443             skipStmt();
444             UMLObject::ObjectType ot = (oldType.length() ? UMLObject::ot_Class : UMLObject::ot_Datatype);
445             UMLObject *pOld = m_doc->findUMLObject(oldType, UMLObject::ot_UMLObject, currentScope());
446             if (pOld == 0) {
447                 pOld = Import_Utils::createUMLObject(ot, oldType, currentScope());
448             }
449             UMLObject *dt = Import_Utils::createUMLObject(ot, newType, currentScope(), m_comment);
450             if (!dt) {
451                 log(QLatin1String("Error: importIDL(typedef ") + keyword +
452                     QLatin1String("): Could not create datatype ") + newType);
453                 return true;
454             }
455             QString stereoName = (oldType.length() ? QLatin1String("idlSequence") : QLatin1String("idlString"));
456             UMLStereotype *pStereo = m_doc->findStereotype(stereoName);
457             if (pStereo == 0) {
458                 pStereo = m_doc->createStereotype(stereoName);
459                 UMLStereotype::AttributeDef tagDef(QLatin1String("bound"), Uml::PrimitiveTypes::UnlimitedNatural);
460                 // Empty bound stands for "unbounded".
461                 pStereo->getAttributeDefs().append(tagDef);
462             }
463             if (oldType.length()) {
464                 UMLAttribute *typeAttr = Object_Factory::createAttribute(dt, QLatin1String("members"), pOld);
465                 Q_UNUSED(typeAttr);
466                 /*
467                 UMLClassifier *cl = dt->asUMLClassifier();
468                 if (cl == 0) {
469                     uError() << "parseStmt(typedef " << newType
470                              << ") internal error: object returned by Import_Utils::createUMLObject is not a Class";
471                     return false;
472                 } */
473             }
474             dt->setUMLStereotype(pStereo);
475             dt->tags().append(bound);
476             return true;
477         }
478         m_srcIndex++;
479         const QString& oldType = joinTypename();
480         const QString& newType = advance();
481         uDebug() << "parseStmt(typedef) : oldType is " << oldType
482                  << ", newType is " << newType
483                  << ", scopeIndex is " << scopeIndex();
484         UMLObject::ObjectType ot = UMLObject::ot_Class;
485         UMLObject *pOld = m_doc->findUMLObject(oldType, UMLObject::ot_UMLObject, currentScope());
486         if (pOld) {
487             ot = pOld->baseType();
488         } else {
489             pOld = Import_Utils::createUMLObject(ot, oldType, currentScope());
490         }
491         UMLClassifier *oldClassifier = pOld->asUMLClassifier();
492         UMLObject *pNew = Import_Utils::createUMLObject(ot, newType, currentScope(), m_comment,
493                                                         QLatin1String("idlTypedef")); /* stereotype */
494         UMLClassifier *newClassifier = pNew->asUMLClassifier();
495         if (oldClassifier == 0) {
496             log(QLatin1String("Error: importIDL(typedef ") + newType +
497                 QLatin1String("): Origin type ") + oldType + QLatin1String(" is not a classifier"));
498         } else if (newClassifier == 0) {
499             log(QLatin1String("Error: importIDL(typedef ") + newType +
500                 QLatin1String(") internal error: Import_Utils::createUMLObject did not return a classifier"));
501         } else {
502             Import_Utils::createGeneralization(newClassifier, oldClassifier);
503         }
504         skipStmt();
505         return true;
506     }
507     if (keyword == QLatin1String("const")) {
508         skipStmt();
509         return true;
510     }
511     if (keyword == QLatin1String("custom")) {
512         return true;
513     }
514     if (keyword == QLatin1String("abstract")) {
515         m_isAbstract = true;
516         return true;
517     }
518     if (keyword == QLatin1String("valuetype")) {
519         const QString& name = advance();
520         UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class,
521                         name, currentScope(), m_comment);
522         m_klass = ns->asUMLClassifier();
523         m_klass->setStereotype(QLatin1String("idlValue"));
524         m_klass->setAbstract(m_isAbstract);
525         m_isAbstract = false;
526         if (advance() == QLatin1String(";"))   // forward declaration
527             return true;
528         pushScope(m_klass);
529         if (m_source[m_srcIndex] == QLatin1String(":")) {
530             if (advance() == QLatin1String("truncatable"))
531                 m_srcIndex++;
532             while (m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String("{")) {
533                 const QString& baseName = m_source[m_srcIndex];
534                 Import_Utils::createGeneralization(m_klass, baseName);
535                 if (advance() != QLatin1String(","))
536                     break;
537                 m_srcIndex++;
538             }
539         }
540         if (m_source[m_srcIndex] != QLatin1String("{")) {
541             log(QLatin1String("Error: importIDL: ignoring excess chars at ") + name);
542             skipStmt(QLatin1String("{"));
543         }
544         return true;
545     }
546     if (keyword == QLatin1String("public")) {
547         return true;
548     }
549     if (keyword == QLatin1String("private")) {
550         m_currentAccess = Uml::Visibility::Private;
551         return true;
552     }
553     if (keyword == QLatin1String("readonly")) {
554         m_isReadonly = true;
555         return true;
556     }
557     if (keyword == QLatin1String("attribute")) {
558         m_isAttribute = true;
559         return true;
560     }
561     if (keyword == QLatin1String("oneway")) {
562         m_isOneway = true;
563         return true;
564     }
565     if (keyword == QLatin1String("}")) {
566         if (scopeIndex())
567             m_klass = popScope()->asUMLClassifier();
568         else
569             log(QLatin1String("Error: importIDL: too many }"));
570         m_srcIndex++;  // skip ';'
571         return true;
572     }
573     if (keyword == QLatin1String(";"))
574         return true;
575     // At this point, we expect `keyword' to be a type name
576     // (of a member of struct or valuetype, or return type
577     // of an operation.) Up next is the name of the attribute
578     // or operation.
579     if (! keyword.contains(QRegExp(QLatin1String("^\\w")))) {
580         log(QLatin1String("Error: importIDL: ignoring ") + keyword);
581         return false;
582     }
583     QString typeName = joinTypename();
584     QString name = advance();
585     if (name.contains(QRegExp(QLatin1String("\\W")))) {
586         log(QLatin1String("Error: importIDL: expecting name in ") + name);
587         return false;
588     }
589     // At this point we most definitely need a class.
590     if (m_klass == 0) {
591         log(QLatin1String("Error: importIDL: no class set for ") + name);
592         return false;
593     }
594     QString nextToken = advance();
595     if (nextToken == QLatin1String("(")) {
596         // operation
597         UMLOperation *op = Import_Utils::makeOperation(m_klass, name);
598         m_srcIndex++;
599         while (m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String(")")) {
600             const QString &direction = m_source[m_srcIndex++];
601             QString typeName = joinTypename();
602             const QString &parName = advance();
603             UMLAttribute *att = Import_Utils::addMethodParameter(op, typeName, parName);
604             Uml::ParameterDirection::Enum dir;
605             if (Model_Utils::stringToDirection(direction, dir))
606                 att->setParmKind(dir);
607             else
608                 log(QLatin1String("Error: importIDL: expecting parameter direction at ") +
609                     direction);
610             if (advance() != QLatin1String(","))
611                 break;
612             m_srcIndex++;
613         }
614         Import_Utils::insertMethod(m_klass, op, Uml::Visibility::Public, typeName,
615                                   false, false, false, false, false, m_comment);
616         if (m_isOneway) {
617             op->setStereotype(QLatin1String("oneway"));
618             m_isOneway = false;
619         }
620         skipStmt();  // skip possible "raises" clause
621         return true;
622     }
623     // At this point we know it's some kind of attribute declaration.
624     while (1) {
625         while (nextToken != QLatin1String(",") && nextToken != QLatin1String(";")) {
626             name += nextToken;  // add possible array dimensions to `name'
627             nextToken = advance();
628         }
629         UMLAttribute *attr = Import_Utils::insertAttribute(m_klass, m_currentAccess, name, typeName, m_comment);
630         if (m_isReadonly) {
631             attr->setStereotype(QLatin1String("readonly"));
632             m_isReadonly = false;
633         }
634         if (m_unionCases.count()) {
635             const QString stereoName = QLatin1String("idlCase");
636             UMLStereotype *pStereo = m_doc->findStereotype(stereoName);
637             if (pStereo == 0) {
638                 pStereo = m_doc->createStereotype(stereoName);
639                 UMLStereotype::AttributeDef tagDef(QLatin1String("label"), Uml::PrimitiveTypes::String);
640                 pStereo->getAttributeDefs().append(tagDef);
641             }
642             attr->setUMLStereotype(pStereo);
643             const QString caseLabels = m_unionCases.join(QChar(' '));
644             attr->tags().append(caseLabels);
645             m_unionCases.clear();
646         } else if (m_isUnionDefault) {
647             attr->setStereotype(QLatin1String("idlDefault"));
648             m_isUnionDefault = false;
649         }
650         if (nextToken != QLatin1String(","))
651             break;
652         name = advance();
653         nextToken = advance();
654     }
655     return true;
656 }
657 
658