GroupCloner.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5 *
6 * ArmarX is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * ArmarX is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @package
19 * @author
20 * @date
21 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22 * GNU General Public License
23 */
24#include "GroupCloner.h"
25
26#include <filesystem>
27
28#include <QDir>
29#include <QDirIterator>
30#include <QFileInfo>
31
32#include <IceUtil/UUID.h>
33
35
36using namespace armarx;
37using namespace armarx::statechartmodel;
38
39GroupCloner::GroupCloner(const std::weak_ptr<StateTreeModel>& treeModel,
40 const ArmarXPackageToolInterfacePtr& packageTool) :
41 treeModel(treeModel), packageTool(packageTool)
42{
43}
44
45QVector<StatechartGroupPtr>
47{
48 QVector<statechartmodel::StatePtr> states = group->getAllStates(false);
49
50 auto result = GetGroupsFromStates(treeModel.lock(), states);
51
52 if (!includeSelf)
53 {
54 auto it = std::remove_if(result.begin(),
55 result.end(),
56 [&](const StatechartGroupPtr& g)
57 { return g->getGroupPath() == group->getGroupPath(); });
58 result.erase(it, result.end());
59 }
60
61 return result;
62}
63
64QVector<StatechartGroupPtr>
66{
67 QVector<StatechartGroupPtr> result;
68
69 for (const auto& groupMapping : mapping.groupMappings)
70 {
71 auto group = treeModel.lock()->getGroupByName(groupMapping.groupName);
72
73 if (!group)
74 {
75 ARMARX_WARNING_S << "Group " << groupMapping.groupName << " was not loaded";
76 }
77 else
78 {
79 result.push_back(group);
80 }
81 }
82
83 return result;
84}
85
86std::optional<StatechartGroupMapping>
87GroupCloner::cloneGroupsTo(const QVector<QPair<StatechartGroupPtr, QString>>& groupsNames,
88 const QString& packageDir,
89 const StatechartGroupMapping& mapping,
90 bool createNewUUIDs)
91{
92 StatechartGroupMapping newMapping = mapping;
93 ARMARX_INFO_S << "Groups that will be cloned: ";
94
95 for (const auto& gEntry : groupsNames)
96 {
97 const auto& g = gEntry.first;
98 ARMARX_INFO_S << " " << g->getName() << " -> " << gEntry.second;
99
100 bool groupMapped = std::find_if(mapping.groupMappings.cbegin(),
101 mapping.groupMappings.cend(),
103 return gm.groupName == g->getName();
104 }) != mapping.groupMappings.cend();
105
106 if (groupMapped)
107 {
108 ARMARX_WARNING_S << "Mapping file seems to be inconsistent with folder structure";
109 return std::optional<StatechartGroupMapping>();
110 }
111
112 QVector<statechartmodel::StatePtr> states = g->getAllStates();
113
115 g->getName(), gEntry.second, g->getPackageName(), {}};
116
117 for (const statechartmodel::StatePtr& state : states)
118 {
119 bool stateMapped = std::find_if(groupMapping.stateMappings.cbegin(),
120 groupMapping.stateMappings.cend(),
122 return sm.stateName == state->getStateName();
123 }) != groupMapping.stateMappings.cend();
124
125 if (!stateMapped)
126 {
127 QString targetUUID = state->getUUID();
128
129 if (createNewUUIDs)
130 {
131 targetUUID = QString::fromUtf8(IceUtil::generateUUID().c_str());
132 }
133
134 groupMapping.stateMappings.insert(
135 {state->getStateName(), state->getUUID(), targetUUID});
136 }
137 }
138
139 newMapping.groupMappings.insert(groupMapping);
140 }
141
142 QVector<StatechartGroupPtr> groups;
143 std::transform(groupsNames.begin(),
144 groupsNames.end(),
145 std::back_inserter(groups),
146 [](const QPair<StatechartGroupPtr, QString>& p) { return p.first; });
147
148 for (const StatechartGroupPtr& g : groups)
149 {
150 if (!g->existsCMakeLists())
151 {
153 << g->getName() << " at " << g->getGroupPath()
154 << " contains no CMakeLists.txt - is it an installed package? Installed packages "
155 "cannot be cloned since they do not contain the CMakelists file and CPP files";
156 return std::optional<StatechartGroupMapping>();
157 }
158 }
159
160 registerGroups(groups, newMapping, packageDir);
161
162 for (const auto& g : groups)
163 {
164 if (!cloneGroupTo(g, packageDir, newMapping))
165 {
166 ARMARX_WARNING_S << "Cloning group '" << g->getName() << "' failed, aborting...";
167 return std::optional<StatechartGroupMapping>();
168 }
169 }
170
171 ARMARX_INFO_S << "Cloning done";
172
173 return std::optional<StatechartGroupMapping>(newMapping);
174}
175
176void
177GroupCloner::registerGroups(const QVector<StatechartGroupPtr>& groups,
178 const StatechartGroupMapping& mapping,
179 const QString& packageDir)
180{
181 const QString packageName = QFileInfo(packageDir).fileName();
182
183 for (const StatechartGroupPtr& g : groups)
184 {
185 if (auto groupName = mapping.queryMappedGroupName(g->getName()))
186 {
187 QString tempStateName = *groupName + "TempState";
188 packageTool->addStatechart(groupName->toUtf8().data(),
189 tempStateName.toUtf8().data(),
190 packageDir.toUtf8().data());
191 RemoveDir(packageDir + QDir::separator() + "source" + QDir::separator() + packageName +
192 QDir::separator() + "statecharts" + QDir::separator() + *groupName);
193 }
194 }
195}
196
197bool
198GroupCloner::cloneGroupTo(const StatechartGroupPtr& group,
199 const QString& packageFolder,
200 const StatechartGroupMapping& mapping)
201{
202 QFileInfo packageInfo(packageFolder);
203 ARMARX_CHECK_EXPRESSION(packageInfo.isDir() && packageInfo.exists());
204
205 auto newGroupNameOpt = mapping.queryMappedGroupName(group->getName());
206 ARMARX_CHECK_EXPRESSION(newGroupNameOpt);
207 QString newGroupName = *newGroupNameOpt;
208
209 const QString oldPackageName = group->getPackageName();
210 const QString newPackageName = QFileInfo(packageFolder).fileName();
211 const QString packageStatechartsDir = packageFolder + QDir::separator() + "source" +
212 QDir::separator() + newPackageName + QDir::separator() +
213 "statecharts";
214 QString newGroupPath = packageStatechartsDir + QDir::separator() + newGroupName;
215
216 ARMARX_INFO_S << "Cloning '" << group->getGroupPath() << "' to '" << newGroupPath << "'";
217
218 if (QDir(newGroupPath).exists())
219 {
220 ARMARX_WARNING_S << newGroupPath << " already exists, but should not exist. Skipping...";
221 return false;
222 }
223 else if (!QDir(newGroupPath).mkpath("."))
224 {
225 ARMARX_ERROR_S << "Could not create directory: " << newGroupPath;
226 return false;
227 }
228
229 QMap<QString, QString> renameCandidateMap;
230 renameCandidateMap.insert(group->getName() + ".scgxml", newGroupName + ".scgxml");
231 renameCandidateMap.insert(group->getName() + "RemoteStateOfferer.h",
232 newGroupName + "RemoteStateOfferer.h");
233 renameCandidateMap.insert(group->getName() + "RemoteStateOfferer.cpp",
234 newGroupName + "RemoteStateOfferer.cpp");
235 renameCandidateMap.insert(group->getName() + "StatechartContext.h",
236 newGroupName + "StatechartContext.h");
237 renameCandidateMap.insert(group->getName() + "StatechartContext.cpp",
238 newGroupName + "StatechartContext.cpp");
239
240 QMap<QString, bool> expectedFileSeenMap;
241 expectedFileSeenMap.insert("CMakeLists.txt", false);
242 expectedFileSeenMap.insert(group->getName() + ".scgxml", false);
243 expectedFileSeenMap.insert(group->getName() + "RemoteStateOfferer.h", false);
244 expectedFileSeenMap.insert(group->getName() + "RemoteStateOfferer.cpp", false);
245
246 // the context files don't have to exist; the contents are normally generated as part of the generated files
247 QVector<QString> optionalExpectedFiles;
248 optionalExpectedFiles.push_back(group->getName() + "StatechartContext.generated.h");
249 optionalExpectedFiles.push_back(group->getName() + "StatechartContext.h");
250 optionalExpectedFiles.push_back(group->getName() + "StatechartContext.cpp");
251
252 QVector<QString> doNotCopy;
253 doNotCopy.push_back(group->getName() + "StatechartContext.generated.h");
254
255 QVector<StateTreeNodePtr> allNodes = group->getAllNodes();
256
257 for (const StateTreeNodePtr& n : allNodes)
258 {
259 if (!n->isState())
260 {
261 continue;
262 }
263
264 const QString xml =
265 QString::fromUtf8(n->getBoostXmlFilePath(StateTreeNode::Path::RelativeToGroup).c_str());
266 const QString cpp =
267 QString::fromUtf8(n->getBoostCppFilePath(StateTreeNode::Path::RelativeToGroup).c_str());
268 const QString h =
269 QString::fromUtf8(n->getBoostHFilePath(StateTreeNode::Path::RelativeToGroup).c_str());
270 const QString gen = QString::fromUtf8(
271 n->getBoostGeneratedHFilePath(StateTreeNode::Path::RelativeToGroup).c_str());
272
273 expectedFileSeenMap.insert(xml, false);
274
275 if (n->checkCppExists())
276 {
277 expectedFileSeenMap.insert(cpp, false);
278 expectedFileSeenMap.insert(h, false);
279 optionalExpectedFiles.push_back(gen);
280 }
281
282 doNotCopy.push_back(gen);
283 }
284
285 QVector<QPair<QRegExp, QString>> codeFileReplaceList;
286 codeFileReplaceList.push_back(
287 {QRegExp(oldPackageName + "_" + group->getName()), newPackageName + "_" + newGroupName});
288 codeFileReplaceList.push_back(
289 {QRegExp(oldPackageName + "::" + group->getName()), newPackageName + "::" + newGroupName});
290 codeFileReplaceList.push_back(
291 {QRegExp("namespace[\\n\\s]*" + group->getName()), "namespace " + newGroupName});
292 codeFileReplaceList.push_back({QRegExp("namespace[\\n\\s]*armarx::" + group->getName()),
293 "namespace armarx::" + newGroupName});
294 codeFileReplaceList.push_back(
295 {QRegExp(group->getName() + "StatechartContext"), newGroupName + "StatechartContext"});
296 codeFileReplaceList.push_back(
297 {QRegExp(group->getName() + "RemoteStateOfferer"), newGroupName + "RemoteStateOfferer"});
298 codeFileReplaceList.push_back({QRegExp(oldPackageName + "/statecharts/" + group->getName()),
299 newPackageName + "/statecharts/" + newGroupName});
300
301 QVector<QPair<QRegExp, QString>> cmakeListsReplaceList;
302 cmakeListsReplaceList.push_back(
303 {QRegExp("armarx_component_set_name\\(\"" + group->getName() + "\"\\)"),
304 "armarx_component_set_name(\"" + newGroupName + "\")"});
305 cmakeListsReplaceList.push_back(
306 {QRegExp(group->getName() + "\\.scgxml"), newGroupName + ".scgxml"});
307 cmakeListsReplaceList.push_back(
308 {QRegExp(group->getName() + "StatechartContext"), newGroupName + "StatechartContext"});
309 cmakeListsReplaceList.push_back(
310 {QRegExp(group->getName() + "RemoteStateOfferer"), newGroupName + "RemoteStateOfferer"});
311
312 auto xmlProcessor = [&](const std::string&,
313 const std::string& attribName,
314 const std::string& attribValue) -> std::string
315 {
316 QString value = QString::fromUtf8(attribValue.c_str());
317
318 if (attribName == "refuuid" || attribName == "uuid")
319 {
320 QString value = QString::fromUtf8(attribValue.c_str());
321
322 if (auto qresult = mapping.queryMappedUuid(value))
323 {
324 return qresult->toUtf8().data();
325 }
326 else
327 {
328 ARMARX_WARNING_S << "No mapping was generated for uuid " << value;
329 }
330 }
331 else if (attribName == "proxyName")
332 {
333 value = value.replace("RemoteStateOfferer", "");
334
335 if (auto qresult = mapping.queryMappedGroupName(value))
336 {
337 return std::string(qresult->toUtf8().data()) + "RemoteStateOfferer";
338 }
339 else
340 {
341 ARMARX_WARNING_S << "No mapping was generated for group " << value;
342 }
343 }
344
345 return attribValue;
346 };
347
348 auto scgxmlProcessor = [&](const std::string&,
349 const std::string& attribName,
350 const std::string& attribValue) -> std::string
351 {
352 QString value = QString::fromUtf8(attribValue.c_str());
353
354 if (attribName == "package" && oldPackageName == value)
355 {
356 return newPackageName.toUtf8().data();
357 }
358 else if (attribName == "name")
359 {
360 if (auto newGroupName = mapping.queryMappedGroupName(value))
361 {
362 return newGroupName->toUtf8().data();
363 }
364 }
365
366 return attribValue;
367 };
368
369 QDirIterator it(group->getGroupPath(),
370 QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
371 QDirIterator::Subdirectories);
372
373 while (it.hasNext())
374 {
375 const auto f = QFileInfo(it.next());
376 QString relativePath = QDir(group->getGroupPath()).relativeFilePath(f.absoluteFilePath());
377 QString srcFilePath = group->getGroupPath() + QDir::separator() + relativePath;
378 QString tgtFilePath =
379 newGroupPath + QDir::separator() +
380 (renameCandidateMap.contains(relativePath) ? renameCandidateMap[relativePath]
381 : relativePath);
382
383 if (f.isDir())
384 {
385 if (!QDir(tgtFilePath).mkpath("."))
386 {
387 ARMARX_WARNING_S << "Could not create directory at path: " << tgtFilePath;
388 return false;
389 }
390
391 ARMARX_VERBOSE_S << "Created directory '" << tgtFilePath << "'";
392 continue;
393 }
394
395 if (doNotCopy.contains(relativePath))
396 {
397 ARMARX_VERBOSE_S << "Skipping file '" << relativePath << "'";
398 continue;
399 }
400
401 ARMARX_DEBUG_S << "Cloning file '" << srcFilePath << "' -> '" << tgtFilePath << "'";
402
403 if (expectedFileSeenMap.contains(relativePath))
404 {
405 expectedFileSeenMap[relativePath] = true;
406 }
407 else if (!optionalExpectedFiles.contains(relativePath))
408 {
409 ARMARX_WARNING_S << "Unexpected file '" << relativePath
410 << "', processing anyway... - expected files"
411 << expectedFileSeenMap.toStdMap();
412 }
413
414 if (f.suffix() == "xml" || f.suffix() == "scgxml")
415 {
416 ARMARX_VERBOSE_S << "Processing xml file: " << relativePath;
417 auto readerPtr = RapidXmlReader::FromFile(srcFilePath.toUtf8().data());
418 RapidXmlReaderNode rootNode = readerPtr->getRoot();
419 RapidXmlWriter writer;
420 auto writerNode = writer.createRootNode(rootNode.name());
421
422 if (f.suffix() == "xml")
423 {
424 ProcessXMLFile(rootNode, writerNode, xmlProcessor);
425 }
426 else
427 {
428 ProcessXMLFile(rootNode, writerNode, scgxmlProcessor);
429 }
430
431 writer.saveToFile(tgtFilePath.toUtf8().data(), true);
432 }
433 else if (f.suffix() == "cpp" || f.suffix() == "h")
434 {
435 ARMARX_VERBOSE_S << "Processing code file: " << relativePath;
436 RegexReplaceFile(srcFilePath, tgtFilePath, codeFileReplaceList);
437 }
438 else if (f.fileName() == "CMakeLists.txt")
439 {
440 ARMARX_VERBOSE_S << "Processing CMakeLists file: " << relativePath;
441 RegexReplaceFile(srcFilePath, tgtFilePath, cmakeListsReplaceList);
442 }
443 else
444 {
445 ARMARX_VERBOSE_S << "Copying file: " << relativePath;
446 QFile::copy(srcFilePath, tgtFilePath);
447 }
448 }
449
450 for (const auto& f : expectedFileSeenMap.toStdMap())
451 {
452 if (!f.second)
453 {
454 ARMARX_WARNING_S << "Expected file '" << f.first << "' was not found while cloning";
455 }
456 }
457
458 ARMARX_INFO_S << "Cloned group " << group->getName();
459 return true;
460}
461
462QVector<StatechartGroupPtr>
464 const QVector<statechartmodel::StatePtr>& states)
465{
466 QVector<StatechartGroupPtr> result;
467
468 for (const auto& state : states)
469 {
470 auto stateGroup = treeModel->getNodeByState(state)->getGroup();
471 ARMARX_CHECK_EXPRESSION(stateGroup);
472 bool alreadyExists = std::find_if(result.constBegin(),
473 result.constEnd(),
474 [&](const StatechartGroupPtr& depGroup) {
475 return stateGroup->getDefinitionFilePath() ==
476 depGroup->getDefinitionFilePath();
477 }) != result.constEnd();
478
479 if (!alreadyExists)
480 {
481 result.push_back(stateGroup);
482 }
483 }
484
485 return result;
486}
487
488bool
489GroupCloner::CopyRecursively(const QString& srcPath, const QString& tgtParentDirPath)
490{
491 QFileInfo srcInfo(srcPath);
492 QFileInfo tgtInfo(tgtParentDirPath);
493
494 if (srcInfo.fileName() == tgtInfo.fileName())
495 {
496 auto newDir = QDir(tgtParentDirPath);
497 newDir.cdUp();
498 return CopyRecursively(srcPath, newDir.path());
499 }
500
501 if (!tgtInfo.isDir() && tgtInfo.exists())
502 {
503 ARMARX_WARNING_S << "Destination '" << tgtParentDirPath << "' has to be a folder" << flush;
504 return false;
505 }
506
507 if (srcInfo.isDir())
508 {
509 const QString newTgtDirPath = tgtParentDirPath + QDir::separator() + srcInfo.fileName();
510 const auto newDir = QDir(newTgtDirPath);
511
512 if (newDir.exists())
513 {
514 ARMARX_IMPORTANT_S << "Target dir already exists: " << newTgtDirPath;
515 return false;
516 }
517
518 if (!newDir.mkpath("."))
519 {
520 ARMARX_IMPORTANT_S << "Failed creating dir: " << newTgtDirPath;
521 return false;
522 }
523
524 QDir sourceDir(srcPath);
525 QStringList fileNames =
526 sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
527
528 for (const QString& fileName : fileNames)
529 {
530 const QString newSrcFilePath = srcPath + QDir::separator() + fileName;
531
532 if (!CopyRecursively(newSrcFilePath, newTgtDirPath))
533 {
534 return false;
535 }
536 }
537 }
538 else
539 {
540 const QString newTgtFilePath = tgtParentDirPath + QDir::separator() + srcInfo.fileName();
541
542 if (!QFile::copy(srcPath, newTgtFilePath))
543 {
544 ARMARX_IMPORTANT_S << "Could not copy file: " << srcPath << " -> " << newTgtFilePath;
545 return false;
546 }
547 }
548
549 return true;
550}
551
552void
553GroupCloner::RegexReplaceFile(const QString& srcFilePath,
554 const QString& tgtFilePath,
555 const QVector<QPair<QRegExp, QString>>& replaceList)
556{
557 QString fileContent = GuiStatechartGroupXmlReader::ReadFileContents(srcFilePath);
558
559 for (const auto& replace : replaceList)
560 {
561 fileContent = fileContent.replace(replace.first, replace.second);
562 }
563
564 GroupXmlWriter::WriteFileContents(tgtFilePath, fileContent);
565}
566
567bool
568GroupCloner::RemoveDir(const QString& dirName)
569{
570 bool result = true;
571 QDir dir(dirName);
572
573 if (dir.exists(dirName))
574 {
575 for (QFileInfo info :
576 dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst))
577 {
578 if (info.isDir())
579 {
580 result = RemoveDir(info.absoluteFilePath());
581 }
582 else
583 {
584 result = QFile::remove(info.absoluteFilePath());
585 }
586
587 if (!result)
588 {
589 return result;
590 }
591 }
592
593 result = dir.rmdir(dirName);
594 }
595
596 return result;
597}
static void ProcessXMLFile(RapidXmlReaderNode source, RapidXmlWriterNode target, const Function &processorFunction)
Definition GroupCloner.h:66
static void RegexReplaceFile(const QString &srcFilePath, const QString &tgtFilePath, const QVector< QPair< QRegExp, QString > > &replaceList)
GroupCloner(const std::weak_ptr< StateTreeModel > &treeModel, const ArmarXPackageToolInterfacePtr &packageTool)
static QVector< StatechartGroupPtr > GetGroupsFromStates(const StateTreeModelPtr &treeModel, const QVector< statechartmodel::StatePtr > &states)
QVector< StatechartGroupPtr > getMappedGroups(const StatechartGroupMapping &mapping)
std::optional< StatechartGroupMapping > cloneGroupsTo(const QVector< QPair< StatechartGroupPtr, QString > > &groupsNames, const QString &packageDir, const StatechartGroupMapping &mapping=StatechartGroupMapping(), bool createNewUUIDs=true)
static bool RemoveDir(const QString &dirName)
QVector< StatechartGroupPtr > getGroupDependencies(const StatechartGroupPtr &group, bool includeSelf=false)
static void WriteFileContents(QString path, QString contents)
static QString ReadFileContents(QString path)
std::string name() const
static RapidXmlReaderPtr FromFile(const std::string &path)
void saveToFile(const std::string &path, bool indent)
RapidXmlWriterNode createRootNode(const std::string &name)
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_DEBUG_S
The logging level for output that is only interesting while debugging.
Definition Logging.h:205
#define ARMARX_ERROR_S
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:216
#define ARMARX_INFO_S
Definition Logging.h:202
#define ARMARX_VERBOSE_S
Definition Logging.h:207
#define ARMARX_IMPORTANT_S
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:210
#define ARMARX_WARNING_S
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:213
std::shared_ptr< State > StatePtr
Definition State.h:48
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::shared_ptr< StatechartGroup > StatechartGroupPtr
std::shared_ptr< StateTreeModel > StateTreeModelPtr
const LogSender::manipulator flush
Definition LogSender.h:251
std::shared_ptr< StateTreeNode > StateTreeNodePtr
std::shared_ptr< ArmarXPackageToolInterface > ArmarXPackageToolInterfacePtr
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
constexpr auto n() noexcept
std::optional< QString > queryMappedUuid(const QString &sourceUuid) const
std::set< GroupMapping > groupMappings
std::optional< QString > queryMappedGroupName(const QString &sourceGroupName) const