diff --git a/umbrello/codeimport/import_utils.cpp b/umbrello/codeimport/import_utils.cpp index 71217a484..1e176d847 100644 --- a/umbrello/codeimport/import_utils.cpp +++ b/umbrello/codeimport/import_utils.cpp @@ -1,776 +1,785 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2005-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "import_utils.h" // app includes #include "association.h" #include "artifact.h" #include "classifier.h" #include "datatype.h" #include "debug_utils.h" #include "folder.h" #include "enum.h" #include "object_factory.h" #include "operation.h" #include "package.h" #include "template.h" #include "uml.h" #include "umldoc.h" #include "umllistview.h" #include "umlobject.h" // kde includes #include #include #ifdef Q_OS_WIN #define PATH_SEPARATOR QLatin1Char(';') #else #define PATH_SEPARATOR QLatin1Char(':') #endif DEBUG_REGISTER_DISABLED(Import_Utils) #undef DBG_SRC #define DBG_SRC QLatin1String("Import_Utils") namespace Import_Utils { /** * Flag manipulated by createUMLObject(). * Global state is generally bad, I know. * It would be cleaner to make this into a return value from * createUMLObject(). */ bool bNewUMLObjectWasCreated = false; /** * Related classifier for creation of dependencies on template * parameters in createUMLObject(). */ UMLClassifier * gRelatedClassifier = 0; /** * On encountering a scoped typename string where the scopes * have not yet been seen, we synthesize UML objects for the * unknown scopes (using a question dialog to the user to decide * whether to treat a scope as a class or as a package.) * However, such an unknown scope is put at the global level. * I.e. before calling createUMLObject() we set this flag to true. */ bool bPutAtGlobalScope = false; /** * The include path list (see addIncludePath() and includePathList()) */ QStringList incPathList; /** * Control whether an object which is newly created by createUMLObject() * is put at the global scope. * * @param yesno When set to false, the object is created at the scope * given by the parentPkg argument of createUMLObject(). */ void putAtGlobalScope(bool yesno) { bPutAtGlobalScope = yesno; } /** * Set a related classifier for creation of dependencies on template * parameters in createUMLObject(). */ void setRelatedClassifier(UMLClassifier *c) { gRelatedClassifier = c; } /** * Control whether the creation methods solicit a new unique ID for the * created object. * By default, unique ID generation is turned on. * * @param yesno False turns UID generation off, true turns it on. */ void assignUniqueIdOnCreation(bool yesno) { Object_Factory::assignUniqueIdOnCreation(yesno); } /** * Returns whether the last createUMLObject() actually created * a new object or just returned an existing one. */ bool newUMLObjectWasCreated() { return bNewUMLObjectWasCreated; } /** * Strip comment lines of leading whitespace and stars. */ QString formatComment(const QString &comment) { if (comment.isEmpty()) return comment; QStringList lines = comment.split(QLatin1Char('\n')); QString& first = lines.first(); QRegExp wordex(QLatin1String("\\w")); if (first.startsWith(QLatin1String(QLatin1String("/*")))) { int wordpos = wordex.indexIn(first); if (wordpos != -1) first = first.mid(wordpos); // remove comment start else lines.pop_front(); // nothing interesting on this line } if (! lines.count()) return QString(); QString& last = lines.last(); int endpos = last.indexOf(QLatin1String("*/")); if (endpos != -1) { if (last.contains(wordex)) last = last.mid(0, endpos - 1); // remove comment end else lines.pop_back(); // nothing interesting on this line } if (! lines.count()) return QString(); QStringList::Iterator end(lines.end()); for (QStringList::Iterator lit(lines.begin()); lit != end; ++lit) { (*lit).remove(QRegExp(QLatin1String("^\\s+"))); (*lit).remove(QRegExp(QLatin1String("^\\*+\\s?"))); } return lines.join(QLatin1String("\n")); } /* UMLObject* findUMLObject(QString name, UMLObject::ObjectType type) { // Why an extra wrapper? See comment at addMethodParameter() UMLObject * o = umldoc->findUMLObject(name, type); return o; } */ /** * Find or create a document object. * @param type object type * @param inName name of uml object * @param parentPkg parent package * @param comment comment for uml object * @param stereotype stereotype for uml object * @param searchInParentPackageOnly flags to search only in parent package * @param remapParent flag to control remapping of parents if an uml object has been found * @return new object or zero */ UMLObject *createUMLObject(UMLObject::ObjectType type, const QString& inName, UMLPackage *parentPkg, const QString& comment, const QString& stereotype, bool searchInParentPackageOnly, bool remapParent) { QString name = inName; UMLDoc *umldoc = UMLApp::app()->document(); UMLFolder *logicalView = umldoc->rootFolder(Uml::ModelType::Logical); if (parentPkg == 0) { // DEBUG(DBG_SRC) << "Import_Utils::createUMLObject(" << name // << "): parentPkg is 0, assuming Logical View"; parentPkg = logicalView; } else if (parentPkg->baseType() == UMLObject::ot_Artifact) { DEBUG(DBG_SRC) << "Import_Utils::createUMLObject(" << name << "): Artifact as parent package is not supported yet, using Logical View"; parentPkg = logicalView; } else if (parentPkg->baseType() == UMLObject::ot_Association) { DEBUG(DBG_SRC) << "Import_Utils::createUMLObject(" << name << "): Association as parent package is not supported yet, using Logical View"; parentPkg = logicalView; } else if (name.startsWith(UMLApp::app()->activeLanguageScopeSeparator())) { name = name.mid(2); parentPkg = logicalView; } bNewUMLObjectWasCreated = false; UMLObject *o = 0; if (searchInParentPackageOnly) { o = Model_Utils::findUMLObject(parentPkg->containedObjects(), name, type); if (!o) { o = Object_Factory::createNewUMLObject(type, name, parentPkg); bNewUMLObjectWasCreated = true; bPutAtGlobalScope = false; } } else { o = umldoc->findUMLObject(name, type, parentPkg); } if (o == 0) { // Strip possible adornments and look again. const bool isConst = name.contains(QRegExp(QLatin1String("^const "))); name.remove(QRegExp(QLatin1String("^const\\s+"))); const bool isVolatile = name.contains(QRegExp(QLatin1String("^volatile "))); name.remove(QRegExp(QLatin1String("^volatile\\s+"))); const bool isMutable = name.contains(QRegExp(QLatin1String("^mutable "))); name.remove(QRegExp(QLatin1String("^mutable\\s+"))); QString typeName(name); bool isAdorned = typeName.contains(QRegExp(QLatin1String("[^\\w:\\. ]"))); const bool isPointer = typeName.contains(QLatin1Char('*')); const bool isRef = typeName.contains(QLatin1Char('&')); typeName.remove(QRegExp(QLatin1String("[^\\w:\\. ].*$"))); typeName = typeName.simplified(); UMLObject *origType = umldoc->findUMLObject(typeName, UMLObject::ot_UMLObject, parentPkg); if (origType == 0) { // Still not found. Create the stripped down type. if (bPutAtGlobalScope) parentPkg = logicalView; // Find, or create, the scopes. QStringList components; QString scopeSeparator = UMLApp::app()->activeLanguageScopeSeparator(); if (typeName.contains(scopeSeparator)) { components = typeName.split(scopeSeparator, QString::SkipEmptyParts); } else if (typeName.contains(QLatin1String("..."))) { // Java variable length arguments type = UMLObject::ot_Datatype; parentPkg = umldoc->datatypeFolder(); isAdorned = false; } if (components.count() > 1) { typeName = components.back(); components.pop_back(); while (components.count()) { QString scopeName = components.front(); components.pop_front(); o = umldoc->findUMLObject(scopeName, UMLObject::ot_UMLObject, parentPkg); if (o) { parentPkg = o->asUMLPackage(); continue; } o = Object_Factory::createUMLObject(UMLObject::ot_Class, scopeName, parentPkg); o->setStereotypeCmd(QLatin1String("class-or-package")); // setStereotypeCmd() triggers tree view item update if not loading by default if (umldoc->loading()) { UMLListViewItem *item = UMLApp::app()->listView()->findUMLObject(o); if (item) item->updateObject(); } parentPkg = o->asUMLPackage(); Model_Utils::treeViewSetCurrentItem(o); } // All scope qualified datatypes live in the global scope. bPutAtGlobalScope = true; } UMLObject::ObjectType t = type; if (type == UMLObject::ot_UMLObject || isAdorned) t = UMLObject::ot_Class; origType = Object_Factory::createUMLObject(t, typeName, parentPkg, false); bNewUMLObjectWasCreated = true; bPutAtGlobalScope = false; } if (isConst || isAdorned || isMutable || isVolatile) { // Create the full given type (including adornments.) if (isVolatile) name.prepend(QLatin1String("volatile ")); if (isMutable) name.prepend(QLatin1String("mutable ")); if (isConst) name.prepend(QLatin1String("const ")); o = Object_Factory::createUMLObject(UMLObject::ot_Datatype, name, umldoc->datatypeFolder(), false); //solicitNewName UMLDatatype *dt = o ? o->asUMLDatatype() : 0; UMLClassifier *c = origType->asUMLClassifier(); if (dt && c) dt->setOriginType(c); else uError() << "createUMLObject(" << name << "): " << "origType " << typeName << " is not a UMLClassifier"; if (dt && (isRef || isPointer)) dt->setIsReference(); /* if (isPointer) { UMLObject *pointerDecl = Object_Factory::createUMLObject(UMLObject::ot_Datatype, type); UMLClassifier *dt = pointerDecl->asUMLClassifier(); dt->setOriginType(classifier); dt->setIsReference(); classifier = dt; } */ } else { o = origType; } } else if (parentPkg && !bPutAtGlobalScope && remapParent) { UMLPackage *existingPkg = o->umlPackage(); if (existingPkg != parentPkg && existingPkg != umldoc->datatypeFolder()) { if (existingPkg) existingPkg->removeObject(o); else uError() << "createUMLObject(" << name << "): " << "o->getUMLPackage() was NULL"; parentPkg->addObject(o); o->setUMLPackage(parentPkg); // setUMLPackage() triggers tree view item update if not loading by default if (umldoc->loading()) { UMLListViewItem *item = UMLApp::app()->listView()->findUMLObject(o); if (item) item->updateObject(); } } } QString strippedComment = formatComment(comment); if (! strippedComment.isEmpty()) { o->setDoc(strippedComment); } if (o && !stereotype.isEmpty()) { o->setStereotype(stereotype); } if (gRelatedClassifier == 0 || gRelatedClassifier == o) return o; QRegExp templateInstantiation(QLatin1String("^[\\w:\\.]+\\s*<(.*)>")); int pos = templateInstantiation.indexIn(name); if (pos == -1) return o; // Create dependencies on template parameters. QString caption = templateInstantiation.cap(1); const QStringList params = caption.split(QRegExp(QLatin1String("[^\\w:\\.]+"))); if (!params.count()) return o; QStringList::ConstIterator end(params.end()); for (QStringList::ConstIterator it(params.begin()); it != end; ++it) { UMLObject *p = umldoc->findUMLObject(*it, UMLObject::ot_UMLObject, parentPkg); if (p == 0 || p->isUMLDatatype()) continue; const Uml::AssociationType::Enum at = Uml::AssociationType::Dependency; UMLAssociation *assoc = umldoc->findAssociation(at, gRelatedClassifier, p); if (assoc) continue; assoc = new UMLAssociation(at, gRelatedClassifier, p); assoc->setUMLPackage(umldoc->rootFolder(Uml::ModelType::Logical)); umldoc->addAssociation(assoc); } if (o == 0) { uError() << "is NULL!"; } return o; } /** * Create hierachical tree of UML objects * * This methods creates the UML object specified by #type and #name including an optional namespace hierachy * if included in the #name e.g. NamespaceA::ClassA in C++. * * @param type type of UML object to create * @param name name of UML object * @param topLevelParent UML package to add the hierachie of UML objects * @return pointer to created or found UML object */ UMLObject* createUMLObjectHierachy(UMLObject::ObjectType type, const QString &name, UMLPackage *topLevelParent) { UMLPackage *parent = topLevelParent; QString objectName; QString scopeSeparator = UMLApp::app()->activeLanguageScopeSeparator(); UMLObject *o = nullptr; if (name.contains(scopeSeparator)) { QStringList components = name.split(scopeSeparator); objectName = components.takeLast(); foreach(const QString scopeName, components) { o = parent->findObject(scopeName); if (o && (o->isUMLPackage() || o->isUMLClassifier())) { parent = o->asUMLPackage(); continue; } o = Object_Factory::createNewUMLObject(UMLObject::ot_Class, scopeName, parent, false); parent->addObject(o); o->setStereotypeCmd(QLatin1String("class-or-package")); parent = o->asUMLPackage(); } } else { objectName = name; } o = parent->findObject(objectName); if (o && (o->isUMLPackage() || o->isUMLClassifier())) return o; o = Object_Factory::createNewUMLObject(type, objectName, parent); parent->addObject(o); return o; } /** * Create a UMLOperation. * The reason for this method is to not generate any Qt signals. * Instead, these are generated by insertMethod(). * (If we generated a creation signal prematurely, i.e. without * the method parameters being known yet, then that would lead to * a conflict with a pre-existing parameterless method of the same * name.) */ UMLOperation* makeOperation(UMLClassifier *parent, const QString &name) { UMLOperation *op = Object_Factory::createOperation(parent, name); return op; } /** * Create a UMLAttribute and insert it into the document. * Use the specified existing attrType. */ UMLAttribute* insertAttribute(UMLClassifier *owner, Uml::Visibility::Enum scope, const QString& name, UMLClassifier *attrType, const QString& comment /* =QString() */, bool isStatic /* =false */) { UMLObject::ObjectType ot = owner->baseType(); Uml::ProgrammingLanguage::Enum pl = UMLApp::app()->activeLanguage(); if (! (ot == UMLObject::ot_Class || (ot == UMLObject::ot_Interface && pl == Uml::ProgrammingLanguage::Java))) { DEBUG(DBG_SRC) << "insertAttribute: Don not know what to do with " << owner->name() << " (object type " << UMLObject::toString(ot) << ")"; return 0; } UMLObject *o = owner->findChildObject(name, UMLObject::ot_Attribute); if (o) { return o->asUMLAttribute(); } UMLAttribute *attr = owner->addAttribute(name, attrType, scope); attr->setStatic(isStatic); QString strippedComment = formatComment(comment); if (! strippedComment.isEmpty()) { attr->setDoc(strippedComment); } UMLApp::app()->document()->setModified(true); return attr; } /** * Create a UMLAttribute and insert it into the document. */ UMLAttribute *insertAttribute(UMLClassifier *owner, Uml::Visibility::Enum scope, const QString& name, const QString& type, const QString& comment /* =QString() */, bool isStatic /* =false */) { UMLObject *attrType = owner->findTemplate(type); if (attrType == 0) { bPutAtGlobalScope = true; gRelatedClassifier = owner; attrType = createUMLObject(UMLObject::ot_UMLObject, type, owner); gRelatedClassifier = 0; bPutAtGlobalScope = false; } return insertAttribute (owner, scope, name, attrType->asUMLClassifier(), comment, isStatic); } /** * Insert the UMLOperation into the given classifier. * * @param klass The classifier into which the operation shall be added. * @param op Reference to pointer to the temporary UMLOperation * for insertion. The caller relinquishes ownership of the * object pointed to. If an UMLOperation of same signature * already exists at the classifier then the incoming * UMLOperation is deleted and the pointer is set to the * existing UMLOperation. * @param scope The Uml::Visibility of the method * @param type The return type * @param isStatic boolean switch to decide if method is static * @param isAbstract boolean switch to decide if method is abstract * @param isFriend true boolean switch to decide if methods is a friend function * @param isConstructor boolean switch to decide if methods is a constructor * @param isDestructor boolean switch to decide if methods is a destructor * @param comment The Documentation for this method */ void insertMethod(UMLClassifier *klass, UMLOperation* &op, Uml::Visibility::Enum scope, const QString& type, bool isStatic, bool isAbstract, bool isFriend, bool isConstructor, bool isDestructor, const QString& comment) { op->setVisibilityCmd(scope); if (!type.isEmpty() // return type may be missing (constructor/destructor) && type != QLatin1String("void")) { if (type == klass->name()) { op->setType(klass); } else { UMLObject *typeObj = klass->findTemplate(type); if (typeObj == 0) { bPutAtGlobalScope = true; gRelatedClassifier = klass; typeObj = createUMLObject(UMLObject::ot_UMLObject, type, klass); gRelatedClassifier = 0; bPutAtGlobalScope = false; op->setType(typeObj); } } } op->setStatic(isStatic); op->setAbstract(isAbstract); // if the operation is friend, add it as a stereotype if (isFriend) op->setStereotype(QLatin1String("friend")); // if the operation is a constructor, add it as a stereotype if (isConstructor) op->setStereotype(QLatin1String("constructor")); if (isDestructor) op->setStereotype(QLatin1String("destructor")); QString strippedComment = formatComment(comment); if (! strippedComment.isEmpty()) { op->setDoc(strippedComment); } UMLAttributeList params = op->getParmList(); UMLOperation *exist = klass->checkOperationSignature(op->name(), params); if (exist) { // copy contents to existing operation exist->setVisibilityCmd(scope); exist->setStatic(isStatic); exist->setAbstract(isAbstract); if (! strippedComment.isEmpty()) exist->setDoc(strippedComment); UMLAttributeList exParams = exist->getParmList(); for (UMLAttributeListIt it(params), exIt(exParams) ; it.hasNext() ;) { UMLAttribute *param = it.next(), *exParam = exIt.next(); exParam->setName(param->name()); exParam->setVisibilityCmd(param->visibility()); exParam->setStatic(param->isStatic()); exParam->setAbstract(param->isAbstract()); exParam->setDoc(param->doc()); exParam->setInitialValue(param->getInitialValue()); exParam->setParmKind(param->getParmKind()); } // delete incoming UMLOperation and pass out the existing one delete op; op = exist; } else { klass->addOperation(op); } } /** * Add an argument to a UMLOperation. * The parentPkg arg is only used for resolving possible scope * prefixes in the `type'. */ UMLAttribute* addMethodParameter(UMLOperation *method, const QString& type, const QString& name) { UMLClassifier *owner = method->umlParent()->asUMLClassifier(); UMLObject *typeObj = owner ? owner->findTemplate(type) : 0; if (typeObj == 0) { bPutAtGlobalScope = true; gRelatedClassifier = owner; typeObj = createUMLObject(UMLObject::ot_UMLObject, type, owner); gRelatedClassifier = 0; bPutAtGlobalScope = false; } UMLAttribute *attr = Object_Factory::createAttribute(method, name, typeObj); method->addParm(attr); return attr; } /** * Add an enum literal to an UMLEnum. */ void addEnumLiteral(UMLEnum *enumType, const QString &literal, const QString &comment, const QString &value) { UMLObject *el = enumType->addEnumLiteral(literal, Uml::ID::None, value); el->setDoc(comment); } /** * Create a generalization from the given child classifier to the given * parent classifier. */ UMLAssociation *createGeneralization(UMLClassifier *child, UMLClassifier *parent) { // if the child is an interface, so is the parent. if (child->isInterface()) parent->setBaseType(UMLObject::ot_Interface); Uml::AssociationType::Enum association = Uml::AssociationType::Generalization; if (parent->isInterface() && !child->isInterface()) { // if the parent is an interface, but the child is not, then // this is really realization. // association = Uml::AssociationType::Realization; } UMLAssociation *assoc = new UMLAssociation(association, child, parent); UMLDoc *umldoc = UMLApp::app()->document(); assoc->setUMLPackage(umldoc->rootFolder(Uml::ModelType::Logical)); umldoc->addAssociation(assoc); return assoc; } /** * Create a subdir with the given name. */ UMLFolder *createSubDir(const QString& name, UMLFolder *parentPkg, const QString &comment) { UMLDoc *umldoc = UMLApp::app()->document(); if (!parentPkg) { parentPkg = umldoc->rootFolder(Uml::ModelType::Component); } UMLObject::ObjectType type = UMLObject::ot_Folder; UMLFolder *o = umldoc->findUMLObjectRaw(parentPkg, name, type)->asUMLFolder(); if (o) return o; o = Object_Factory::createUMLObject(type, name, parentPkg, false)->asUMLFolder(); if (o) o->setDoc(comment); return o; } /** * Create a folder for artifacts */ UMLObject *createArtifactFolder(const QString& name, UMLPackage *parentPkg, const QString &comment) { Q_UNUSED(parentPkg); UMLObject::ObjectType type = UMLObject::ot_Folder; UMLDoc *umldoc = UMLApp::app()->document(); UMLFolder *componentView = umldoc->rootFolder(Uml::ModelType::Component); UMLObject *o = umldoc->findUMLObjectRaw(componentView, name, type); if (o) return o; o = Object_Factory::createUMLObject(type, name, componentView, false); UMLFolder *a = o->asUMLFolder(); a->setDoc(comment); DEBUG(DBG_SRC) << name << comment; return o; } /** * Create an artifact with the given name. */ UMLObject *createArtifact(const QString& name, UMLFolder *parentPkg, const QString &comment) { UMLDoc *umldoc = UMLApp::app()->document(); if (!parentPkg) { parentPkg = umldoc->rootFolder(Uml::ModelType::Component); } UMLObject::ObjectType type = UMLObject::ot_Artifact; UMLObject *o = umldoc->findUMLObjectRaw(parentPkg, name, type); if (o) return o; o = Object_Factory::createUMLObject(type, name, parentPkg, false); UMLArtifact *a = o->asUMLArtifact(); a->setDrawAsType(UMLArtifact::file); a->setDoc(comment); DEBUG(DBG_SRC) << name << comment; return o; } /** * Create a generalization from the existing child UMLObject to the given * parent class name. * This method does not handle scopes well and is only a last resort. * The method * createGeneralization(UMLClassifier *child, UMLClassifier *parent) * should be used instead. */ void createGeneralization(UMLClassifier *child, const QString &parentName) { UMLObject *parentObj = createUMLObject(UMLObject::ot_Class, parentName); UMLClassifier *parent = parentObj->asUMLClassifier(); createGeneralization(child, parent); } /** * Remap UMLObject instance in case it does not have the correct type. * * @param ns uml object instance with incorrect class * @param currentScope parent uml object * @return newly created UMLEnum instance or zero in case of error */ UMLEnum *remapUMLEnum(UMLObject *ns, UMLPackage *currentScope) { if (ns) { QString comment = ns->doc(); QString name = ns->name(); QString stereotype = ns->stereotype(); Uml::Visibility::Enum visibility = ns->visibility(); UMLApp::app()->document()->removeUMLObject(ns, true); if (currentScope == 0) currentScope = UMLApp::app()->document()->rootFolder(Uml::ModelType::Logical); UMLObject *o = Object_Factory::createNewUMLObject(UMLObject::ot_Enum, name, currentScope, false); if (!o) return 0; UMLEnum *e = o->asUMLEnum(); if (!e) return 0; e->setDoc(comment); e->setStereotypeCmd(stereotype.isEmpty() ? QLatin1String("enum") : stereotype); e->setVisibilityCmd(visibility); // add to parents child list if (!currentScope->containedObjects().contains(e)) currentScope->containedObjects().append(e); return e; } return 0; } /** * Return the list of paths set by previous calls to addIncludePath() * and the environment variable UMBRELLO_INCPATH. * This list can be used for finding included (or Ada with'ed or...) * files. */ QStringList includePathList() { QStringList includePathList(incPathList); QString umbrello_incpath = QString::fromLatin1(qgetenv("UMBRELLO_INCPATH")); if (!umbrello_incpath.isEmpty()) { includePathList += umbrello_incpath.split(PATH_SEPARATOR); } return includePathList; } /** * Add a path to the include path list. */ void addIncludePath(const QString& path) { if (! incPathList.contains(path)) incPathList.append(path); } /** * Returns true if a type is an actual Datatype */ bool isDatatype(const QString& name, UMLPackage *parentPkg) { UMLDoc *umldoc = UMLApp::app()->document(); UMLObject * o = umldoc->findUMLObject(name, UMLObject::ot_Datatype, parentPkg); return (o != 0); } +/** + * Returns the UML package of the global scope. + */ +UMLPackage *globalScope() +{ + UMLFolder *logicalView = UMLApp::app()->document()->rootFolder(Uml::ModelType::Logical); + return logicalView; +} + } // end namespace Import_Utils diff --git a/umbrello/codeimport/import_utils.h b/umbrello/codeimport/import_utils.h index 3ff20c76c..f6ff5d869 100644 --- a/umbrello/codeimport/import_utils.h +++ b/umbrello/codeimport/import_utils.h @@ -1,108 +1,110 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2005-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef IMPORT_UTILS_H #define IMPORT_UTILS_H #include "basictypes.h" #include "folder.h" #include "umlattributelist.h" #include class UMLObject; class UMLClassifier; class UMLPackage; class UMLOperation; class UMLEnum; class UMLScene; class QMimeData; /** * Utilities for code import * @author Oliver Kellogg * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ namespace Import_Utils { UMLFolder *createSubDir(const QString& name, UMLFolder *parentPkg, const QString &comment = QString()); UMLObject *createArtifactFolder(const QString& name, UMLPackage *parentPkg, const QString &comment); UMLObject *createArtifact(const QString& name, UMLFolder *parentPkg = NULL, const QString &comment = QString()); UMLObject* createUMLObject(UMLObject::ObjectType type, const QString& name, UMLPackage *parentPkg = 0, const QString& comment = QString(), const QString& stereotype = QString(), bool searchInParentPackageOnly = false, bool remapParent = true); UMLObject* createUMLObjectHierachy(UMLObject::ObjectType type, const QString &name, UMLPackage *parentPkg); void putAtGlobalScope(bool yesno); void setRelatedClassifier(UMLClassifier *c); void assignUniqueIdOnCreation(bool yesno); UMLAttribute* insertAttribute(UMLClassifier *klass, Uml::Visibility::Enum scope, const QString& name, const QString& type, const QString& comment = QString(), bool isStatic = false); UMLAttribute *insertAttribute(UMLClassifier *klass, Uml::Visibility::Enum scope, const QString& name, UMLClassifier *attrType, const QString& comment /* =QString() */, bool isStatic /* =false */); UMLOperation* makeOperation(UMLClassifier *parent, const QString &name); void insertMethod(UMLClassifier *klass, UMLOperation* &op, Uml::Visibility::Enum scope, const QString& type, bool isStatic, bool isAbstract, bool isFriend = false, bool isConstructor = false, bool isDestructor = false, const QString& comment = QString()); UMLAttribute* addMethodParameter(UMLOperation *method, const QString& type, const QString& name); void addEnumLiteral(UMLEnum *enumType, const QString &literal, const QString &comment = QString(), const QString &value = QString()); UMLAssociation *createGeneralization(UMLClassifier *child, UMLClassifier *parent); void createGeneralization(UMLClassifier *child, const QString &parentName); UMLEnum *remapUMLEnum(UMLObject *ns, UMLPackage *currentScope); QString formatComment(const QString &comment); QStringList includePathList(); void addIncludePath(const QString& path); bool newUMLObjectWasCreated(); bool isDatatype(const QString& name, UMLPackage *parentPkg = 0); + UMLPackage *globalScope(); + } // end namespace Import_Utils #endif diff --git a/umbrello/codeimport/nativeimportbase.cpp b/umbrello/codeimport/nativeimportbase.cpp index b9b9f2135..2359d8711 100644 --- a/umbrello/codeimport/nativeimportbase.cpp +++ b/umbrello/codeimport/nativeimportbase.cpp @@ -1,504 +1,504 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2005-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "nativeimportbase.h" // app includes #include "codeimpthread.h" #include "debug_utils.h" #include "import_utils.h" // kde includes #include // qt includes #include #include #include QStringList NativeImportBase::m_parsedFiles; // static, see nativeimportbase.h /** * Constructor * @param singleLineCommentIntro "//" for IDL and Java, "--" for Ada * @param thread thread in which the code import runs */ NativeImportBase::NativeImportBase(const QString &singleLineCommentIntro, CodeImpThread* thread) : ClassImport(thread), m_singleLineCommentIntro(singleLineCommentIntro), m_srcIndex(0), m_klass(0), m_currentAccess(Uml::Visibility::Public), m_inComment(false), m_isAbstract(false) { } /** * Destructor. */ NativeImportBase::~NativeImportBase() { } /** * Set the delimiter strings for a multi line comment. * @param intro In languages with a C style multiline comment * this is slash-star. * @param end In languages with a C style multiline comment * this is star-slash. */ void NativeImportBase::setMultiLineComment(const QString &intro, const QString &end) { m_multiLineCommentIntro = intro; m_multiLineCommentEnd = end; } /** * Set the delimiter strings for an alternative form of * multi line comment. See setMultiLineComment(). * @param intro the start comment string * @param end the end comment string */ void NativeImportBase::setMultiLineAltComment(const QString &intro, const QString &end) { m_multiLineAltCommentIntro = intro; m_multiLineAltCommentEnd = end; } /** * Advance m_srcIndex until m_source[m_srcIndex] contains the lexeme * given by `until'. * @param until the target string */ void NativeImportBase::skipStmt(const QString& until /* = ";" */) { const int srcLength = m_source.count(); while (m_srcIndex < srcLength && m_source[m_srcIndex] != until) m_srcIndex++; } /** * Advance m_srcIndex to the index of the corresponding closing character * of the given opening. Nested opening/closing pairs are respected. * Valid openers are: '{' '[' '(' '<' * @param opener the opener string * @return True for success, false for misuse (invalid opener) or * if no matching closing character is found in m_source. */ bool NativeImportBase::skipToClosing(QChar opener) { QString closing; switch (opener.toLatin1()) { case '{': closing = QLatin1String("}"); break; case '[': closing = QLatin1String("]"); break; case '(': closing = QLatin1String(")"); break; case '<': closing = QLatin1String(">"); break; default: uError() << "opener='" << opener << "': illegal input character"; return false; } const QString opening(opener); skipStmt(opening); const int srcLength = m_source.count(); int nesting = 0; while (m_srcIndex < srcLength) { QString nextToken = advance(); if (nextToken.isEmpty()) break; if (nextToken == closing) { if (nesting <= 0) break; nesting--; } else if (nextToken == opening) { nesting++; } } if (m_srcIndex == srcLength) return false; return true; } /** * Set package as current scope. * @param p UML package to set as current scope */ void NativeImportBase::pushScope(UMLPackage *p) { m_scope.append(p); } /** * Return previously defined scope. * * @return previous scope */ UMLPackage *NativeImportBase::popScope() { m_scope.takeLast(); UMLPackage *p = m_scope.last(); return p; } /** * Return current scope. * * @return scope */ UMLPackage *NativeImportBase::currentScope() { UMLPackage *p = m_scope.last(); return p; } /** * Return current scope index. * * @return >= 0 index, -1 empty */ int NativeImportBase::scopeIndex() { return m_scope.size() - 1; } /** * Get the next lexeme without advancing. * @return the next lexeme or an empty string */ QString NativeImportBase::lookAhead() { if (m_srcIndex < m_source.count() - 1) return m_source[m_srcIndex+1]; return QString(); } /** * Advance m_srcIndex until m_source[m_srcIndex] contains a non-comment. * Comments encountered during advancement are accumulated in `m_comment'. * If m_srcIndex hits the end of m_source then QString() is returned. * @return the current lexeme or an empty string */ QString NativeImportBase::advance() { while (m_srcIndex < m_source.count() - 1) { m_srcIndex++; if (m_source[m_srcIndex].startsWith(m_singleLineCommentIntro)) m_comment += m_source[m_srcIndex].mid(m_singleLineCommentIntro.length()); else break; } if (m_srcIndex >= m_source.count() - 1 || // if last item in m_source is a comment then it is dropped too (m_srcIndex == m_source.count() - 1 && m_source[m_srcIndex].startsWith(m_singleLineCommentIntro))) { return QString(); } return m_source[m_srcIndex]; } /** * Preprocess a line. * May modify the given line to remove items consumed by the * preprocessing such as comments or preprocessor directives. * The default implementation handles multi-line comments. * @param line The line to preprocess. * @return True if the line was completely consumed, * false if there are still items left in the line * for further analysis. */ bool NativeImportBase::preprocess(QString& line) { if (line.isEmpty()) return true; if (m_multiLineCommentIntro.isEmpty()) return false; // Check for end of multi line comment. if (m_inComment) { int delimiterLen = 0; int pos = line.indexOf(m_multiLineCommentEnd); if (pos == -1) { if (! m_multiLineAltCommentEnd.isEmpty()) pos = line.indexOf(m_multiLineAltCommentEnd); if (pos == -1) { m_comment += line + QLatin1Char('\n'); return true; // done } delimiterLen = m_multiLineAltCommentEnd.length(); } else { delimiterLen = m_multiLineCommentEnd.length(); } if (pos > 0) { QString text = line.mid(0, pos - 1); m_comment += text.trimmed(); } m_source.append(m_singleLineCommentIntro + m_comment); // denotes comments in `m_source' m_srcIndex++; m_comment = QString(); m_inComment = false; pos += delimiterLen; // pos now points behind the closed comment if (pos == (int)line.length()) return true; // done line = line.mid(pos); } // If we get here then m_inComment is false. // Check for start of multi line comment. int delimIntroLen = 0; int delimEndLen = 0; int pos = line.indexOf(m_multiLineCommentIntro); if (pos != -1) { delimIntroLen = m_multiLineCommentIntro.length(); } else if (!m_multiLineAltCommentIntro.isEmpty()) { pos = line.indexOf(m_multiLineAltCommentIntro); if (pos != -1) delimIntroLen = m_multiLineAltCommentIntro.length(); } if (pos != -1) { int sPos = line.indexOf(m_singleLineCommentIntro); if (sPos != -1 && sPos < pos) { // multi line comment intro found in single line comment pos = -1; // is no multi line comment after all } } if (pos != -1) { int endpos = line.indexOf(m_multiLineCommentEnd, pos + delimIntroLen); if (endpos != -1) { delimEndLen = m_multiLineCommentEnd.length(); } else if (!m_multiLineAltCommentEnd.isEmpty()) { endpos = line.indexOf(m_multiLineAltCommentEnd, pos + delimIntroLen); if (endpos != -1) delimEndLen = m_multiLineAltCommentEnd.length(); } if (endpos == -1) { m_inComment = true; if (pos + delimIntroLen < (int)line.length()) { QString cmnt = line.mid(pos + delimIntroLen); m_comment += cmnt.trimmed() + QLatin1Char('\n'); } if (pos == 0) return true; // done line = line.left(pos); } else { // It's a multiline comment on a single line. if (endpos > pos + delimIntroLen) { QString cmnt = line.mid(pos + delimIntroLen, endpos - pos - delimIntroLen); cmnt = cmnt.trimmed(); if (!cmnt.isEmpty()) m_source.append(m_singleLineCommentIntro + cmnt); } endpos++; // endpos now points at the slash of "*/" QString pre; if (pos > 0) pre = line.left(pos); QString post; if (endpos + delimEndLen < (int)line.length()) post = line.mid(endpos + 1); line = pre + post; } } return false; // The input was not completely consumed by preprocessing. } /** * Split the line so that a string is returned as a single element of the list. * When not in a string then split at white space. * The default implementation is suitable for C style strings and char constants. * @param line the line to split * @return the parts of the line */ QStringList NativeImportBase::split(const QString& line) { QStringList list; QString listElement; QChar stringIntro = 0; // buffers the string introducer character bool seenSpace = false; QString ln = line.trimmed(); for (int i = 0; i < ln.length(); ++i) { const QChar& c = ln[i]; if (stringIntro.toLatin1()) { // we are in a string listElement += c; if (c == stringIntro) { if (ln[i - 1] != QLatin1Char('\\')) { list.append(listElement); listElement.clear(); stringIntro = 0; // we are no longer in a string } } } else if (c == QLatin1Char('"') || c == QLatin1Char('\'')) { if (!listElement.isEmpty()) { list.append(listElement); } listElement = stringIntro = c; seenSpace = false; } else if (c == QLatin1Char(' ') || c == QLatin1Char('\t')) { if (seenSpace) continue; seenSpace = true; if (!listElement.isEmpty()) { list.append(listElement); listElement.clear(); } } else { listElement += c; seenSpace = false; } } if (!listElement.isEmpty()) list.append(listElement); return list; } /** * Scan a single line. * parseFile() calls this for each line read from the input file. * This in turn calls other methods such as preprocess() and fillSource(). * The lexer. Tokenizes the given string and fills `m_source'. * Stores possible comments in `m_comment'. * @param line The line to scan. */ void NativeImportBase::scan(const QString& line) { QString ln = line; if (preprocess(ln)) return; // Check for single line comment. int pos = ln.indexOf(m_singleLineCommentIntro); if (pos != -1) { QString cmnt = ln.mid(pos); m_source.append(cmnt); if (pos == 0) return; ln = ln.left(pos); } if (ln.contains(QRegExp(QLatin1String("^\\s*$")))) return; const QStringList words = split(ln); for (QStringList::ConstIterator it = words.begin(); it != words.end(); ++it) { QString word = *it; if (word[0] == QLatin1Char('"') || word[0] == QLatin1Char('\'')) m_source.append(word); // string constants are handled by split() else fillSource(word); } } /** * Initialize auxiliary variables. * This is called by the default implementation of parseFile() * after scanning (before parsing the QStringList m_source.) * The default implementation is empty. */ void NativeImportBase::initVars() { } /** * Import a single file. * The default implementation should be feasible for languages that * don't depend on an external preprocessor. * @param filename The file to import. * @return state of parsing - false means errors */ bool NativeImportBase::parseFile(const QString& filename) { QString nameWithoutPath = filename; nameWithoutPath.remove(QRegExp(QLatin1String("^.*/"))); if (m_parsedFiles.contains(nameWithoutPath)) return true; m_parsedFiles.append(nameWithoutPath); QString fname = filename; const QString msgPrefix = filename + QLatin1String(": "); if (filename.contains(QLatin1Char('/'))) { QString path = filename; path.remove(QRegExp(QLatin1String("/[^/]+$"))); uDebug() << msgPrefix << "adding path " << path; Import_Utils::addIncludePath(path); } if (!QFile::exists(filename)) { QFileInfo fi(filename); if (fi.isAbsolute()) { uError() << msgPrefix << "cannot find file"; return false; } bool found = false; const QStringList includePaths = Import_Utils::includePathList(); for (QStringList::ConstIterator pathIt = includePaths.begin(); pathIt != includePaths.end(); ++pathIt) { QString path = (*pathIt); if (! path.endsWith(QLatin1Char('/'))) { path.append(QLatin1Char('/')); } if (QFile::exists(path + filename)) { fname.prepend(path); found = true; break; } } if (! found) { uError() << msgPrefix << "cannot find file"; return false; } } QFile file(fname); if (! file.open(QIODevice::ReadOnly)) { uError() << msgPrefix << "cannot open file"; return false; } log(nameWithoutPath, QLatin1String("parsing...")); // Scan the input file into the QStringList m_source. m_source.clear(); m_srcIndex = 0; initVars(); QTextStream stream(&file); int lineCount = 0; while (! stream.atEnd()) { QString line = stream.readLine(); lineCount++; scan(line); } log(nameWithoutPath, QLatin1String("file size: ") + QString::number(file.size()) + QLatin1String(" / lines: ") + QString::number(lineCount)); file.close(); // Parse the QStringList m_source. m_klass = 0; m_currentAccess = Uml::Visibility::Public; m_scope.clear(); - pushScope(0); // index 0 is reserverd for the global scope + pushScope(Import_Utils::globalScope()); // index 0 is reserverd for the global scope const int srcLength = m_source.count(); for (m_srcIndex = 0; m_srcIndex < srcLength; ++m_srcIndex) { const QString& firstToken = m_source[m_srcIndex]; //uDebug() << '"' << firstToken << '"'; if (firstToken.startsWith(m_singleLineCommentIntro)) { m_comment = firstToken.mid(m_singleLineCommentIntro.length()); continue; } if (! parseStmt()) skipStmt(); m_comment.clear(); } log(nameWithoutPath, QLatin1String("...end of parse")); return true; } /** * Implement abstract operation from ClassImport. */ void NativeImportBase::initialize() { m_parsedFiles.clear(); }