Compare commits
434
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09b6df8bc3 | ||
|
|
a62b729577 | ||
|
|
4bc64639e4 | ||
|
|
0f99d2c859 | ||
|
|
c12287471c | ||
|
|
7c1c22637e | ||
|
|
5183c9419c | ||
|
|
7fc6f66649 | ||
|
|
2b1c3dc241 | ||
|
|
42f9fbeebe | ||
|
|
c30bf7ac6b | ||
|
|
e4de02f27c | ||
|
|
5538ff7c6e | ||
|
|
751c990440 | ||
|
|
358aa5b47f | ||
|
|
5aefc953f8 | ||
|
|
b24051cd0d | ||
|
|
bed6f077e6 | ||
|
|
40b5a09489 | ||
|
|
e97806442f | ||
|
|
37dffeab46 | ||
|
|
e563800a85 | ||
|
|
2db87c4433 | ||
|
|
8716c392c6 | ||
|
|
b12ccbc9a3 | ||
|
|
b521638c03 | ||
|
|
9ac37ce6a7 | ||
|
|
fbcecd12df | ||
|
|
bbff0833ca | ||
|
|
d2139cd7e3 | ||
|
|
cfad8e695e | ||
|
|
65b997f993 | ||
|
|
16cf571caf | ||
|
|
8d511d935c | ||
|
|
68381278dd | ||
|
|
0c1f111d11 | ||
|
|
7a1c9d1c5f | ||
|
|
1123494db0 | ||
|
|
ac967a77d9 | ||
|
|
35c6f2d295 | ||
|
|
0f27a5bc14 | ||
|
|
456f37548e | ||
|
|
e40c9a2d40 | ||
|
|
1a4d372d6c | ||
|
|
c9d6dac20f | ||
|
|
597b4bb42d | ||
|
|
d3110cde5d | ||
|
|
87235db012 | ||
|
|
062bf8f96e | ||
|
|
d130c55be5 | ||
|
|
f6de7a758b | ||
|
|
fda39f40ce | ||
|
|
c03a802b7a | ||
|
|
f617169a6f | ||
|
|
3c1bc0f341 | ||
|
|
0476030db2 | ||
|
|
72980bfdb7 | ||
|
|
bf14574451 | ||
|
|
3d31171f7b | ||
|
|
750168e9ba | ||
|
|
815e16368e | ||
|
|
b7bdc6e06a | ||
|
|
09b5e99729 | ||
|
|
13f0c3ffbc | ||
|
|
0e2100f383 | ||
|
|
26f702b7a4 | ||
|
|
37bd700564 | ||
|
|
684e05109d | ||
|
|
676ae05335 | ||
|
|
a17b431669 | ||
|
|
493893d2e6 | ||
|
|
8976b5aedb | ||
|
|
6e7b09cf6f | ||
|
|
11501ff7bb | ||
|
|
59f0c025e5 | ||
|
|
cebc456bc1 | ||
|
|
2d1b12b5d8 | ||
|
|
bf3fecf5c0 | ||
|
|
5c012ba228 | ||
|
|
5644914fe3 | ||
|
|
b662e4daf9 | ||
|
|
bdb93798e5 | ||
|
|
1dd1e86355 | ||
|
|
52df1c8ae9 | ||
|
|
23e694afc2 | ||
|
|
217bb54bc0 | ||
|
|
ba99b74608 | ||
|
|
862d0dbe2b | ||
|
|
5554704174 | ||
|
|
14dde18e8a | ||
|
|
65ca0d8b5e | ||
|
|
23cf641d36 | ||
|
|
a32481de09 | ||
|
|
a3ff160086 | ||
|
|
42f6e83c2c | ||
|
|
855b0a6101 | ||
|
|
eb4d067365 | ||
|
|
8b3306732d | ||
|
|
5823cf5dd9 | ||
|
|
5a96bd5d58 | ||
|
|
e4743d631d | ||
|
|
03e5fa7160 | ||
|
|
5d6ac867cb | ||
|
|
33d611cf6f | ||
|
|
64408b347d | ||
|
|
3950641c72 | ||
|
|
3347ae842f | ||
|
|
94c028d95e | ||
|
|
372f695eec | ||
|
|
6dd243afa5 | ||
|
|
ffcb124dc0 | ||
|
|
e564da50ed | ||
|
|
56b589ee4f | ||
|
|
1a38b88229 | ||
|
|
444ac417bf | ||
|
|
72a30c1e30 | ||
|
|
0e22c5b029 | ||
|
|
167a826cbc | ||
|
|
c9411c04e1 | ||
|
|
1b29039a28 | ||
|
|
b54fd0caa5 | ||
|
|
3e15fb9281 | ||
|
|
5d2b5761a4 | ||
|
|
ac4f2e1aaa | ||
|
|
e7a062e531 | ||
|
|
3785f2c036 | ||
|
|
1589d8f17d | ||
|
|
a7a5782fc8 | ||
|
|
bb58062c6f | ||
|
|
6d3a06f90a | ||
|
|
62a017cf27 | ||
|
|
78ff6f7df9 | ||
|
|
4ea6ed119c | ||
|
|
4a3fb0bfe3 | ||
|
|
0d67b3a8c9 | ||
|
|
04899a7160 | ||
|
|
b877bc4af5 | ||
|
|
0e15524181 | ||
|
|
6177fd00fb | ||
|
|
6cc19e138d | ||
|
|
75f7f52230 | ||
|
|
b915bacc62 | ||
|
|
9992af628a | ||
|
|
1c77f995e2 | ||
|
|
9caa2573ca | ||
|
|
e771c1252f | ||
|
|
980dd26106 | ||
|
|
143b594a59 | ||
|
|
6c8e5d663a | ||
|
|
34b9cd10f3 | ||
|
|
d8a7a5ed27 | ||
|
|
cf358fe527 | ||
|
|
88902aa572 | ||
|
|
d7f58c558d | ||
|
|
87fe045bbe | ||
|
|
220529c74a | ||
|
|
6b8f2efabb | ||
|
|
bc26b291f2 | ||
|
|
95f7d78569 | ||
|
|
c7d1c250dd | ||
|
|
8436f113e3 | ||
|
|
f345ebc16f | ||
|
|
ce154f6a1d | ||
|
|
eb1d949edc | ||
|
|
78253c60f4 | ||
|
|
1e07bf76ba | ||
|
|
985aa9787b | ||
|
|
ed81fccb1d | ||
|
|
43300efaa7 | ||
|
|
3f7beb3c7e | ||
|
|
7b090a26ca | ||
|
|
040bdd558f | ||
|
|
1bcc7e4855 | ||
|
|
ab1cf60643 | ||
|
|
3f8a7eccfd | ||
|
|
d8ec9e8d74 | ||
|
|
8345470273 | ||
|
|
593c221d23 | ||
|
|
b2e787a70b | ||
|
|
9c1ac7cbef | ||
|
|
36f077bce4 | ||
|
|
a410019553 | ||
|
|
b18844a202 | ||
|
|
4d6bef5fa2 | ||
|
|
97a9992ce1 | ||
|
|
168d03866e | ||
|
|
2b8f3f02c7 | ||
|
|
1e1a3e6f30 | ||
|
|
db8adbfe3b | ||
|
|
c664611b80 | ||
|
|
e748e82c84 | ||
|
|
4223ca2ff5 | ||
|
|
5381c65fee | ||
|
|
b6a97c39e5 | ||
|
|
9f0076a197 | ||
|
|
fd3e5e8380 | ||
|
|
881d44f4dd | ||
|
|
ed9fc51e7a | ||
|
|
cdb8c754e9 | ||
|
|
0997dcba79 | ||
|
|
f6292cc244 | ||
|
|
ef20ac3d95 | ||
|
|
ab29b7b2d5 | ||
|
|
7cd3e43959 | ||
|
|
4bf53db7fa | ||
|
|
5485a90385 | ||
|
|
29002f2a48 | ||
|
|
c3eda18234 | ||
|
|
6a8c31b735 | ||
|
|
3ec4208139 | ||
|
|
95071993f5 | ||
|
|
bdb2461737 | ||
|
|
a09979afdd | ||
|
|
2df80f90a7 | ||
|
|
40f767fc4d | ||
|
|
82356f23e5 | ||
|
|
472f537313 | ||
|
|
e8d07af320 | ||
|
|
cf62b17d58 | ||
|
|
220ca64679 | ||
|
|
a1914cb7d9 | ||
|
|
4e29022c06 | ||
|
|
501bd2b4b8 | ||
|
|
ce47b9236f | ||
|
|
b8e2ed5344 | ||
|
|
58a383d212 | ||
|
|
9e553e5d7a | ||
|
|
9a9a023b70 | ||
|
|
7d1074588c | ||
|
|
a3e44a5bd8 | ||
|
|
bd3cccbc15 | ||
|
|
b96dec41e4 | ||
|
|
3c058facd7 | ||
|
|
e156fbb5e4 | ||
|
|
d79d89c352 | ||
|
|
4ee52e2f52 | ||
|
|
c32c9031e7 | ||
|
|
9d1419a5b1 | ||
|
|
5d6807f256 | ||
|
|
619c7683d4 | ||
|
|
a318dbb64a | ||
|
|
7f9a59004a | ||
|
|
4cab91a948 | ||
|
|
6794412c29 | ||
|
|
f974625b0f | ||
|
|
15a961182a | ||
|
|
35bfe2be39 | ||
|
|
851cbd5cec | ||
|
|
c066c74d7f | ||
|
|
9e38583785 | ||
|
|
882f783bcb | ||
|
|
4de31193b6 | ||
|
|
82da99b41c | ||
|
|
f42bd0276c | ||
|
|
8e2acb6707 | ||
|
|
86f331341a | ||
|
|
778866b7cd | ||
|
|
80d73fc330 | ||
|
|
a03fa59e35 | ||
|
|
d911e95d05 | ||
|
|
7c2196bd6d | ||
|
|
5865239b07 | ||
|
|
a5e5560478 | ||
|
|
dba7759292 | ||
|
|
691bf21f3e | ||
|
|
9db7dd604c | ||
|
|
03c698d1eb | ||
|
|
7173785915 | ||
|
|
f23384bb68 | ||
|
|
c29ffee9d4 | ||
|
|
1fa7897671 | ||
|
|
60f7ea0d9a | ||
|
|
44fdf2ac91 | ||
|
|
92ad7d4686 | ||
|
|
be912de9d6 | ||
|
|
eb02c1f001 | ||
|
|
1cb7a2ea22 | ||
|
|
f217207675 | ||
|
|
1157e12e0f | ||
|
|
290d9c1f5d | ||
|
|
6aa040abb3 | ||
|
|
b21bdbf410 | ||
|
|
969e273904 | ||
|
|
710f7ad475 | ||
|
|
50c4a6fbc0 | ||
|
|
524f076c2a | ||
|
|
2ed0a944d1 | ||
|
|
db81f2de26 | ||
|
|
2a0192ce1d | ||
|
|
3aa0b04ebd | ||
|
|
67491249c2 | ||
|
|
8d1897d297 | ||
|
|
18ef659723 | ||
|
|
8cec9973e7 | ||
|
|
ba1d9e2c04 | ||
|
|
758ae0f53f | ||
|
|
283e89e3c9 | ||
|
|
6513a267e2 | ||
|
|
b656e1aaa9 | ||
|
|
e398f4903d | ||
|
|
afda2a82a8 | ||
|
|
c5c54cb268 | ||
|
|
1445fc659d | ||
|
|
c270b7ebe7 | ||
|
|
f528f9b609 | ||
|
|
8a26ac0a5d | ||
|
|
4e548318b5 | ||
|
|
0c1503d85c | ||
|
|
ad07345480 | ||
|
|
497baa804b | ||
|
|
a9c5b2181e | ||
|
|
851d47e114 | ||
|
|
eafbbbbbe9 | ||
|
|
115243d47c | ||
|
|
f74ff251a2 | ||
|
|
e5d8bfa432 | ||
|
|
c21083700a | ||
|
|
bb1447c858 | ||
|
|
8050bca545 | ||
|
|
1edc9934fa | ||
|
|
d5af8b9764 | ||
|
|
c143ff8a4f | ||
|
|
ec83f017be | ||
|
|
71636a42c4 | ||
|
|
d5c13033e2 | ||
|
|
01dce5fe52 | ||
|
|
45eb8ee689 | ||
|
|
079175a055 | ||
|
|
0ca0e9ac60 | ||
|
|
060792032b | ||
|
|
bf0b1a3ddd | ||
|
|
f15f342a47 | ||
|
|
793c9f8b81 | ||
|
|
4969e4ff9b | ||
|
|
f6c2ac04f9 | ||
|
|
92b7f26fc8 | ||
|
|
47985c5a88 | ||
|
|
d7c8c9e6bd | ||
|
|
7a6be7474e | ||
|
|
3d3c3b48fe | ||
|
|
8d456c7d1a | ||
|
|
ec75789fae | ||
|
|
c042faae9e | ||
|
|
f6cbb9021f | ||
|
|
d757420cb0 | ||
|
|
93020295ad | ||
|
|
3cc3129de0 | ||
|
|
6588fc7d3f | ||
|
|
950d669538 | ||
|
|
c599be63e3 | ||
|
|
22a3a8d5b1 | ||
|
|
418443c841 | ||
|
|
ffa25fb394 | ||
|
|
3bd40639a3 | ||
|
|
016371c076 | ||
|
|
d890b11c5f | ||
|
|
27fe03a03a | ||
|
|
85bae2e60a | ||
|
|
7a80c64b04 | ||
|
|
d17d397fe7 | ||
|
|
bd10a8db89 | ||
|
|
c7fa97c8bf | ||
|
|
e22dd98040 | ||
|
|
05095c6813 | ||
|
|
b9a807c92a | ||
|
|
5e0a20094e | ||
|
|
b1ff110439 | ||
|
|
8c824f87b4 | ||
|
|
32041db9a7 | ||
|
|
b0a281ddf4 | ||
|
|
fdf2bba69d | ||
|
|
9c1463546e | ||
|
|
734a5e5315 | ||
|
|
cac1dd809e | ||
|
|
72cbe318b6 | ||
|
|
19ab996be2 | ||
|
|
13c341c7c7 | ||
|
|
1632478122 | ||
|
|
a1185399d4 | ||
|
|
0fcfac2f26 | ||
|
|
4f09d01afb | ||
|
|
2a127de21f | ||
|
|
52d82529f4 | ||
|
|
6111fc86ab | ||
|
|
c8dc6f387f | ||
|
|
ec2f8f1691 | ||
|
|
81a85ad3dc | ||
|
|
f58ba53a1e | ||
|
|
38239bcebc | ||
|
|
4de8d75015 | ||
|
|
6e06e6793b | ||
|
|
f66c6a1bc0 | ||
|
|
ca9d89c2ff | ||
|
|
6c668cfbfa | ||
|
|
f48e645973 | ||
|
|
c3f3920380 | ||
|
|
5d1768c51c | ||
|
|
0a288a3bff | ||
|
|
00a37f1fd6 | ||
|
|
ccc31f8fff | ||
|
|
29fa8876fb | ||
|
|
989e5e32c8 | ||
|
|
135810bbb9 | ||
|
|
98d671a96d | ||
|
|
0740625839 | ||
|
|
ed28c9a58b | ||
|
|
c430b46289 | ||
|
|
a281671919 | ||
|
|
e66a627d8d | ||
|
|
8d3a033aa1 | ||
|
|
013511ab1d | ||
|
|
d7effabf17 | ||
|
|
e8d501773b | ||
|
|
d7f6ff1f9e | ||
|
|
5b429564dd | ||
|
|
022dcba077 | ||
|
|
d6b6ffce7c | ||
|
|
1881d68aef | ||
|
|
e6a295bb5b | ||
|
|
bafed3705f | ||
|
|
3bb6e89496 | ||
|
|
8453e104c5 | ||
|
|
0c1c1ceb85 | ||
|
|
4c9861a469 | ||
|
|
b774f4035c | ||
|
|
010f91928a | ||
|
|
4a143f09b3 | ||
|
|
f14faa04e5 | ||
|
|
cf6d77c030 | ||
|
|
514ded2739 | ||
|
|
6a2a0667ab | ||
|
|
ba4121728b | ||
|
|
e3e87f886e | ||
|
|
d176b5e0d7 |
No files matched your search
@@ -1,28 +0,0 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
ColumnLimit: 120
|
||||
Language: Cpp
|
||||
Standard: c++20
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Regroup
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
NamespaceIndentation: None
|
||||
IndentCaseLabels: true
|
||||
AlignTrailingComments: true
|
||||
SeparateDefinitionBlocks: Always
|
||||
SpaceBeforeParens: ControlStatements
|
||||
Cpp11BracedListStyle: true
|
||||
@@ -1,5 +0,0 @@
|
||||
tmp/
|
||||
settings.local.json
|
||||
metadata/
|
||||
logs/
|
||||
redactors/local/
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"telemetry": true,
|
||||
"checkpoints": {
|
||||
"primary": {
|
||||
"type": "git-branch"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
export STRIX_LLM="qwen3.5-397b-a17b"
|
||||
export SCW_SECRET_KEY="REPLACE_WITH_YOURS"
|
||||
|
||||
export COPILOT_PROVIDER_TYPE="openai"
|
||||
export COPILOT_PROVIDER_API_KEY="${SCW_SECRET_KEY}"
|
||||
export COPILOT_PROVIDER_BASE_URL="https://api.scaleway.ai/v1"
|
||||
export COPILOT_MODEL="${STRIX_LLM}"
|
||||
|
||||
export COPILOT_PROVIDER_MAX_PROMPT_TOKENS="32768"
|
||||
export COPILOT_PROVIDER_MAX_OUTPUT_TOKENS="8192"
|
||||
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH=$HOME/.config/entire/tokens.json
|
||||
export ACCESSIBLE=1
|
||||
@@ -1,56 +0,0 @@
|
||||
# GitHub Copilot Repository Instructions: C++23 Modernization
|
||||
|
||||
## Context
|
||||
- Language: C++
|
||||
- Target standard: C++23
|
||||
- Goal: modernize legacy C++11/14/17 code safely and incrementally
|
||||
|
||||
## Primary Rule
|
||||
Preserve existing behavior unless functional changes are explicitly requested.
|
||||
|
||||
## Balanced Modernization Policy
|
||||
- Prefer modern C++23 constructs when they clearly improve safety, readability, or maintainability.
|
||||
- Keep refactors focused and reviewable; avoid broad rewrites unless requested.
|
||||
- Prefer standard library solutions over custom or legacy patterns.
|
||||
- Respect toolchain limitations, ABI-sensitive interfaces, and repository-local style.
|
||||
|
||||
## Preferred Modernizations
|
||||
- Use `auto` where the type is obvious from the initializer; avoid it when it hides important type information.
|
||||
- Prefer structured bindings over `std::tie` or manual pair/tuple access when clearer.
|
||||
- Replace raw owning pointers and manual `delete` with RAII, usually `std::unique_ptr`.
|
||||
- Use `std::make_unique` and `std::make_shared`; prefer `std::shared_ptr` only for real shared ownership.
|
||||
- Use `std::string_view` for read-only string parameters only when lifetime safety is clear.
|
||||
- Prefer `std::span` for non-owning contiguous ranges.
|
||||
- Use `std::mdspan` only when multidimensional access is central and the change improves clarity or safety.
|
||||
- Prefer `.contains()` over `find(...) != end()` where available and clearer.
|
||||
- Prefer standard algorithms and ranges over manual loops when readability improves.
|
||||
- Use `[[nodiscard]]` where ignoring the result is likely a bug.
|
||||
- Prefer early returns over deeply nested control flow.
|
||||
- Use `std::optional` monadic operations (`and_then`, `transform`, `or_else`) when they make the code simpler.
|
||||
- Prefer `constexpr`, `if constexpr`, concepts, and `requires` over legacy `std::enable_if` / SFINAE where practical.
|
||||
- Add `noexcept`, `override`, `final`, `= default`, and `= delete` where correct and useful.
|
||||
- Prefer `enum class` over unscoped enums where practical.
|
||||
- Prefer `std::print` / `std::println` for console output when supported by the project toolchain.
|
||||
- Prefer typed `std::chrono` durations and `std::jthread` where appropriate.
|
||||
|
||||
## Avoid
|
||||
- Do not modernize mechanically if readability becomes worse.
|
||||
- Do not introduce unsupported C++23 features.
|
||||
- Do not introduce dangling references, dangling `std::string_view`, or unclear ownership.
|
||||
- Do not silently change ABI, object lifetime, exception behavior, or performance characteristics.
|
||||
- Do not rewrite unrelated code.
|
||||
|
||||
## Expected Copilot Behavior
|
||||
When making migration suggestions:
|
||||
- produce small, focused updates
|
||||
- explain non-trivial changes briefly
|
||||
- call out risky refactors explicitly
|
||||
- note compatibility or toolchain caveats
|
||||
- leave optional follow-up improvements separate
|
||||
|
||||
## Preferred Output
|
||||
Provide:
|
||||
- updated code
|
||||
- short rationale
|
||||
- compatibility/toolchain caveats
|
||||
- optional next modernization steps
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"agentStop": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli agent-stop'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"errorOccurred": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli error-occurred'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"postToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli post-tool-use'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"preToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli pre-tool-use'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"sessionEnd": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli session-end'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"sessionStart": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli session-start'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"subagentStop": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli subagent-stop'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"userPromptSubmitted": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli user-prompt-submitted'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
# GitHub Copilot Instructions: Qt5/C++11 to Qt6/C++23 Migration
|
||||
|
||||
## Mission
|
||||
Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23, preserve behavior unless change is strictly required, and validate the result completely in the repository’s Docker-based CI workflow.
|
||||
|
||||
## Operating Mode
|
||||
- Work autonomously from analysis through validation.
|
||||
- Do not stop after editing code.
|
||||
- Continue until configure, build, tests, and policy checks have been attempted in Docker.
|
||||
- Do not ask for confirmation during normal implementation.
|
||||
- Stop early only if required source material is missing, the task is impossible, or a destructive action would be required.
|
||||
|
||||
## Boundaries
|
||||
- Read the legacy project from `${PROJECT_DIR}`.
|
||||
- Write the migrated project to `qt6-after/qt6-migrated-benchmark`.
|
||||
- Do not modify the legacy source tree in place.
|
||||
- Limit changes to the target tree and only the CI/build/test files needed to support migration and validation.
|
||||
|
||||
## Main Migration Targets
|
||||
- Upgrade Qt5 to Qt6.
|
||||
- Upgrade C++11/14/17 code to C++23.
|
||||
- Keep Qt Core, Gui, and Widgets explicitly linked.
|
||||
- Replace `QRegExp` with `QRegularExpression`.
|
||||
- Replace `QDesktopWidget` and `QApplication::desktop()` with `QScreen`-based Qt6 APIs.
|
||||
- Remove legacy `QTextCodec` usage and adopt Qt6-safe text handling.
|
||||
- Replace old `SIGNAL`/`SLOT` syntax with function-pointer connect syntax.
|
||||
- Replace `0`/`NULL` pointer literals with `nullptr`.
|
||||
- Preserve `.ui` and `.qrc` integration.
|
||||
- Preserve behavior unless Qt6 compatibility requires adaptation.
|
||||
|
||||
## C++23 Modernization Rules
|
||||
- Preserve semantics first; modernize second.
|
||||
- Use `auto` when the type is obvious; avoid it when it hides important type information.
|
||||
- Prefer structured bindings where clearer.
|
||||
- Replace raw owning pointers/manual `delete` with RAII, usually `std::unique_ptr`.
|
||||
- Use `std::make_unique` / `std::make_shared`; use `std::shared_ptr` only for real shared ownership.
|
||||
- Use `std::string_view` only when lifetime safety is clear.
|
||||
- Prefer `std::span` for non-owning contiguous data; use `std::mdspan` only when multidimensional access clearly benefits.
|
||||
- Prefer `.contains()` over `find(...) != end()` where available.
|
||||
- Prefer algorithms and ranges over manual loops when readability improves.
|
||||
- Use `[[nodiscard]]` when ignoring a result is likely a bug.
|
||||
- Prefer early returns over deeply nested control flow.
|
||||
- Use `std::optional` monadic operations (`and_then`, `transform`, `or_else`) when clearer.
|
||||
- Prefer `constexpr`, `if constexpr`, concepts, and `requires` over legacy `std::enable_if`/SFINAE where practical.
|
||||
- Add `noexcept`, `override`, `final`, `= default`, and `= delete` where correct and useful.
|
||||
- Prefer `enum class` over unscoped enums where practical.
|
||||
- Prefer `std::print` / `std::println` when supported by the toolchain.
|
||||
- Prefer typed `std::chrono` durations and `std::jthread` where appropriate.
|
||||
|
||||
## Safety Rules
|
||||
- Do not change business logic unless explicitly requested or required for Qt6 compatibility.
|
||||
- Do not introduce unsupported Qt6/C++23 features.
|
||||
- Do not silently change ABI, ownership, lifetime, exception behavior, performance, UI behavior, or event ordering.
|
||||
- Do not worsen readability for the sake of modernization.
|
||||
- Avoid dangling references, dangling `std::string_view`, and ownership ambiguity.
|
||||
- Keep includes minimal and preserve repository-local formatting/style.
|
||||
|
||||
## Tests
|
||||
- For every code change inside a method, add at least three tests covering the changed behavior.
|
||||
- Place generated tests in `qt6-after/qt6-migrated-benchmark/tests`.
|
||||
- Use policy-compatible test filenames such as:
|
||||
- `test_<name>.cpp`
|
||||
- `<name>_test.cpp`
|
||||
- `<name>test.cpp`
|
||||
- `<name>gtest.cpp`
|
||||
- Update CMake so the migrated app and all tests build and run with CTest.
|
||||
- Ensure all tests are registered with CTest.
|
||||
|
||||
## Docker-First Validation
|
||||
Use `lifemaps/ci-cpp:latest` as the authoritative environment. Do not substitute host tools for missing container tools. Treat `ci/Dockerfile.ci` as the source of truth for CI dependencies.
|
||||
|
||||
### Required validation sequence
|
||||
1. Configure in Docker
|
||||
2. Build in Docker
|
||||
3. Run tests in Docker
|
||||
4. If tests pass, run `python3 ci/check_pr_policies.py` in Docker
|
||||
|
||||
### Recommended commands
|
||||
- Configure:
|
||||
`cmake -S qt6-after/qt6-migrated-benchmark -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug`
|
||||
- Build:
|
||||
`cmake --build build --parallel`
|
||||
- Test:
|
||||
`ctest --test-dir build --output-on-failure`
|
||||
- Policy:
|
||||
`python3 ci/check_pr_policies.py`
|
||||
|
||||
Adapt paths/flags only if repository inspection proves they must differ.
|
||||
|
||||
## Missing-Tool Remediation
|
||||
If Docker validation fails because a required tool/package is missing:
|
||||
- identify the exact missing dependency
|
||||
- update `Dockerfile.ci` minimally
|
||||
- rebuild `lifemaps/ci-cpp:latest`
|
||||
- rerun the full validation sequence
|
||||
|
||||
Continue until:
|
||||
- validation succeeds, or
|
||||
- the next blocker is a genuine project/code issue
|
||||
|
||||
## Failure Classification
|
||||
Environment failures:
|
||||
- command not found
|
||||
- missing compiler/toolchain utility
|
||||
- missing Qt dev package
|
||||
- missing CMake/Ninja/CTest support tool
|
||||
- missing dependency required by `ci/check_pr_policies.py`
|
||||
|
||||
Project failures:
|
||||
- compile errors
|
||||
- linker errors
|
||||
- failing tests
|
||||
- formatting/policy failures caused by migration changes
|
||||
|
||||
## Policy Awareness
|
||||
- `ci/check_pr_policies.py` checks formatting for changed C/C++ files, expects related tests, and verifies commit-signature conditions in its diff range.
|
||||
- Ensure Docker validation runs with mounted workspace and git metadata available.
|
||||
- Do not claim success unless the policy script passes.
|
||||
|
||||
## Execution Plan
|
||||
1. Inspect `${PROJECT_DIR}` and the target tree.
|
||||
2. Identify Qt5 and legacy C++ constructs to migrate.
|
||||
3. Migrate incrementally in `qt6-after/qt6-migrated-benchmark`.
|
||||
4. Add/update tests with each behavior-relevant change.
|
||||
5. Update CMake/test registration.
|
||||
6. Validate in Docker.
|
||||
7. If missing dependencies are found, update `Dockerfile.ci`, rebuild, and rerun.
|
||||
8. Repeat until success or a real project defect remains.
|
||||
|
||||
## Reporting
|
||||
Group change explanations by:
|
||||
- CMake / build-system migration
|
||||
- Qt API replacements
|
||||
- C++ modernization
|
||||
- UI/resource preservation
|
||||
- tests added
|
||||
- `Dockerfile.ci` / container updates
|
||||
|
||||
Also provide a concise changed-files summary.
|
||||
|
||||
## Completion Criteria
|
||||
Success requires:
|
||||
- migrated project exists under `qt6-after/qt6-migrated-benchmark`
|
||||
- configure succeeds in Docker
|
||||
- build succeeds in Docker
|
||||
- tests succeed in Docker
|
||||
- `python3 ci/check_pr_policies.py` succeeds in Docker after tests pass
|
||||
|
||||
## Final Response
|
||||
- State whether migration is fully validated or which blocker remains.
|
||||
- List the exact Docker commands used for image rebuild, configure, build, test, and policy check.
|
||||
- For every failed attempt, include:
|
||||
- exact command
|
||||
- key error
|
||||
- environment vs project classification
|
||||
- fix applied
|
||||
- End with a short summary of any remaining blocker.
|
||||
+10
-3
@@ -1,4 +1,11 @@
|
||||
.envrc
|
||||
.*~
|
||||
*~
|
||||
build-rti-full
|
||||
build-rti-off
|
||||
build-rti-off-notests
|
||||
build-*
|
||||
build_*
|
||||
CMakeFiles
|
||||
fix_qt6_gui_deps2.py
|
||||
fix_qt6_gui_deps.py
|
||||
fix_rti_conditionals.py
|
||||
fix_target_checks.py
|
||||
build
|
||||
Vendored
-90
@@ -1,90 +0,0 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'lifemaps/ci-cpp:latest'
|
||||
reuseNode true
|
||||
args '-u root:root'
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
environment {
|
||||
PROJECT_DIR = "qt6-after/qt6-migrated-benchmark"
|
||||
BUILD_DIR = "build"
|
||||
CHANGE_TARGET = "${env.CHANGE_TARGET ?: 'origin/main'}"
|
||||
QT_QPA_PLATFORM = "offscreen"
|
||||
LANG = "C.UTF-8"
|
||||
LC_ALL = "C.UTF-8"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
sh 'git fetch --all --prune'
|
||||
sh 'git submodule update --init --recursive || true'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Tool Versions') {
|
||||
steps {
|
||||
sh '''
|
||||
python3 --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
g++ --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Qt Diagnostics') {
|
||||
steps {
|
||||
sh '''
|
||||
echo "QT_QPA_PLATFORM=$QT_QPA_PLATFORM"
|
||||
echo "LANG=$LANG"
|
||||
echo "LC_ALL=$LC_ALL"
|
||||
dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true
|
||||
pkg-config --list-all | grep -i qt || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Policy Checks') {
|
||||
steps {
|
||||
sh 'python3 ci/check_pr_policies.py'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Configure') {
|
||||
steps {
|
||||
sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'cmake --build ${BUILD_DIR} --parallel'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'QT_QPA_PLATFORM=offscreen LANG=C.UTF-8 LC_ALL=C.UTF-8 ctest --test-dir ${BUILD_DIR} --output-on-failure'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
if (getContext(hudson.FilePath)) {
|
||||
cleanWs deleteDirs: true, disableDeferredWipeout: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 lifemaps
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,30 +0,0 @@
|
||||
FROM ubuntu:26.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
clang-format \
|
||||
cmake \
|
||||
ninja-build \
|
||||
git \
|
||||
g++ \
|
||||
make \
|
||||
ca-certificates \
|
||||
openssh-client \
|
||||
pkg-config \
|
||||
qtbase5-dev \
|
||||
qt6-base-dev \
|
||||
qt6-base-dev-tools \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set Qt platform for headless testing
|
||||
ENV QT_QPA_PLATFORM=offscreen
|
||||
|
||||
# Configure git for containerized environments
|
||||
RUN git config --global --add safe.directory "*"
|
||||
|
||||
WORKDIR /workspace
|
||||
@@ -1,2 +0,0 @@
|
||||
torsten.wierschin@life-maps.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO+c+9jJKrLpTk4pLg+p1Qo0CjCYzfKfEhXEu0MAlZpj
|
||||
nkana@outlook.de ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBR68AzU7UtY+NJPIRloEeIlQIZIP3xhvkWB8BediMeQ
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(".").resolve()
|
||||
TEST_DIRS = [
|
||||
ROOT / "tests",
|
||||
ROOT / "test",
|
||||
ROOT / "qt6-after" / "qt6-migrated-benchmark" / "tests",
|
||||
]
|
||||
CPP_EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"}
|
||||
ALLOWED_SIGNERS_FILE = ROOT / "ci" / "allowed_signers"
|
||||
|
||||
TEST_PATTERNS = [
|
||||
"test_{name}.cpp",
|
||||
"test_{name}.h",
|
||||
"{name}_test.cpp",
|
||||
"{name}_test.h",
|
||||
"{name}test.cpp",
|
||||
"{name}test.h",
|
||||
"{name}gtest.cpp",
|
||||
"{name}gtest.h",
|
||||
]
|
||||
|
||||
|
||||
def run(cmd: str) -> str:
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
|
||||
def git_ref_exists(ref: str) -> bool:
|
||||
try:
|
||||
subprocess.check_call(
|
||||
f"git rev-parse --verify --quiet '{ref}'",
|
||||
shell=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def is_pull_request_build() -> bool:
|
||||
return bool(os.environ.get("CHANGE_ID"))
|
||||
|
||||
|
||||
def resolve_target_ref() -> str | None:
|
||||
raw = os.environ.get("CHANGE_TARGET", "").strip()
|
||||
|
||||
candidates = []
|
||||
if raw:
|
||||
candidates.append(raw)
|
||||
if not raw.startswith("origin/"):
|
||||
candidates.append(f"origin/{raw}")
|
||||
|
||||
candidates.extend(["origin/main", "main"])
|
||||
|
||||
seen = set()
|
||||
for ref in candidates:
|
||||
if ref and ref not in seen:
|
||||
seen.add(ref)
|
||||
if git_ref_exists(ref):
|
||||
return ref
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def changed_files_and_base() -> tuple[list[str], str]:
|
||||
target = resolve_target_ref()
|
||||
|
||||
if target:
|
||||
print(f"Vergleiche gegen Ziel-Ref: {target}")
|
||||
out = run(f"git diff --name-only {target}...HEAD")
|
||||
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||
return files, target
|
||||
|
||||
if is_pull_request_build():
|
||||
print("ERROR: Kein gültiger Ziel-Branch-Ref für PR-Build gefunden.")
|
||||
sys.exit(1)
|
||||
|
||||
if git_ref_exists("HEAD~1"):
|
||||
print("Kein Ziel-Branch-Ref gefunden, verwende HEAD~1 für Branch-Build.")
|
||||
out = run("git diff --name-only HEAD~1..HEAD")
|
||||
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||
return files, "HEAD~1"
|
||||
|
||||
print("Keine Vergleichsbasis gefunden, behandle alle versionierten Dateien als geändert.")
|
||||
out = run("git ls-files")
|
||||
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||
return files, ""
|
||||
|
||||
|
||||
def commits_in_range(base: str) -> list[str]:
|
||||
if base:
|
||||
if base == "HEAD~1":
|
||||
out = run("git rev-list HEAD~1..HEAD")
|
||||
else:
|
||||
out = run(f"git rev-list {base}..HEAD")
|
||||
else:
|
||||
out = run("git rev-list HEAD")
|
||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def is_cpp_file(path: Path) -> bool:
|
||||
return path.suffix.lower() in CPP_EXTENSIONS
|
||||
|
||||
|
||||
def require_clang_format():
|
||||
if not (ROOT / ".clang-format").exists():
|
||||
print("ERROR: .clang-format fehlt im Repository-Root.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def require_allowed_signers():
|
||||
if not ALLOWED_SIGNERS_FILE.exists():
|
||||
print(f"ERROR: {ALLOWED_SIGNERS_FILE} fehlt.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_format(path: Path):
|
||||
subprocess.check_call(f"clang-format --dry-run --Werror '{path}'", shell=True)
|
||||
|
||||
|
||||
def candidate_names(path: Path):
|
||||
stem = path.stem.lower()
|
||||
names = {stem}
|
||||
for suffix in ["_p", "_impl", "_private", "_priv", "_ui"]:
|
||||
if stem.endswith(suffix):
|
||||
names.add(stem[:-len(suffix)])
|
||||
return sorted(n for n in names if n)
|
||||
|
||||
|
||||
def iter_test_files():
|
||||
for test_dir in TEST_DIRS:
|
||||
if test_dir.exists():
|
||||
yield from (p for p in test_dir.rglob("*") if p.is_file())
|
||||
|
||||
|
||||
def has_related_test(path: Path) -> bool:
|
||||
test_files = list(iter_test_files())
|
||||
if not test_files:
|
||||
return False
|
||||
|
||||
test_names = [p.name.lower() for p in test_files]
|
||||
|
||||
for name in candidate_names(path):
|
||||
for pattern in TEST_PATTERNS:
|
||||
if pattern.format(name=name).lower() in test_names:
|
||||
return True
|
||||
for test_name in test_names:
|
||||
if name in test_name and ("test" in test_name or "gtest" in test_name):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def verify_commit_signature(commit: str):
|
||||
env = os.environ.copy()
|
||||
env["GIT_CONFIG_COUNT"] = "1"
|
||||
env["GIT_CONFIG_KEY_0"] = "gpg.ssh.allowedSignersFile"
|
||||
env["GIT_CONFIG_VALUE_0"] = str(ALLOWED_SIGNERS_FILE)
|
||||
|
||||
subprocess.check_call(
|
||||
f"git verify-commit {commit}",
|
||||
shell=True,
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
require_clang_format()
|
||||
require_allowed_signers()
|
||||
|
||||
files, base = changed_files_and_base()
|
||||
if not files:
|
||||
print("Keine geänderten Dateien erkannt.")
|
||||
return
|
||||
|
||||
commits = commits_in_range(base)
|
||||
if not commits:
|
||||
print("Keine neuen Commits im Vergleichsbereich erkannt.")
|
||||
else:
|
||||
for commit in commits:
|
||||
print(f"Prüfe SSH-Signatur: {commit}")
|
||||
try:
|
||||
verify_commit_signature(commit)
|
||||
except subprocess.CalledProcessError:
|
||||
print(
|
||||
"ERROR: Commit ist nicht gültig mit erlaubtem SSH-Signierer signiert: "
|
||||
f"{commit}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
for rel in files:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
continue
|
||||
if is_cpp_file(path):
|
||||
print(f"Prüfe: {rel}")
|
||||
try:
|
||||
check_format(path)
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"ERROR: Formatfehler in {rel}")
|
||||
sys.exit(1)
|
||||
|
||||
if not has_related_test(path):
|
||||
print(
|
||||
"ERROR: Kein passender Test unter tests/, test/ oder "
|
||||
"qt6-after/qt6-migrated-benchmark/tests/ gefunden für: "
|
||||
f"{rel}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
print("PR-Policy-Prüfungen erfolgreich.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,337 +0,0 @@
|
||||
# Entwicklerhinweise: Anforderungen bei Push und Pull Request
|
||||
|
||||
Dieses Repository verwendet Jenkins-basierte PR- und Branch-Policies.
|
||||
Ziel ist, dass Code bereits **vor dem Merge** bestimmte Qualitätsanforderungen erfüllt.
|
||||
|
||||
Diese Datei beschreibt, **was bei einem Push erwartet wird**, **was in Jenkins geprüft wird** und **wie du Fehler lokal vermeiden kannst**.
|
||||
|
||||
---
|
||||
|
||||
## Überblick
|
||||
|
||||
Bei Pushes und Pull Requests werden automatisierte Policies ausgeführt.
|
||||
|
||||
Je nach Build-Typ gelten leicht unterschiedliche Vergleichsregeln:
|
||||
|
||||
- **bei Pull Requests** wird gegen den Zielbranch verglichen, z. B. `origin/main`
|
||||
- **bei normalen Branch-Builds** wird auf eine lokale Vergleichsbasis zurückgefallen, z. B. `HEAD~1`, falls kein Zielbranch-Ref verfügbar ist
|
||||
|
||||
Die eigentlichen Qualitätsanforderungen bleiben gleich.
|
||||
|
||||
---
|
||||
|
||||
## Was beim Push / PR geprüft wird
|
||||
|
||||
### 1. `.clang-format` muss vorhanden sein
|
||||
Im Repository-Root muss eine Datei `.clang-format` liegen.
|
||||
|
||||
Ohne diese Datei schlägt der Policy-Check fehl.
|
||||
|
||||
---
|
||||
|
||||
### 2. Geänderte C/C++-Dateien müssen korrekt formatiert sein
|
||||
Für geänderte C/C++-Dateien wird `clang-format` geprüft.
|
||||
|
||||
Betroffen sind insbesondere Dateien mit diesen Endungen:
|
||||
|
||||
- `.c`
|
||||
- `.cc`
|
||||
- `.cpp`
|
||||
- `.cxx`
|
||||
- `.h`
|
||||
- `.hh`
|
||||
- `.hpp`
|
||||
- `.hxx`
|
||||
|
||||
Wenn eine Datei nicht korrekt formatiert ist, schlägt der Build fehl.
|
||||
|
||||
#### Lokal prüfen
|
||||
```bash
|
||||
clang-format --dry-run --Werror <datei>
|
||||
```
|
||||
|
||||
#### Lokal automatisch formatieren
|
||||
```bash
|
||||
clang-format -i <datei>
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
```bash
|
||||
clang-format -i main.cpp tests/test_main.cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Zu geänderten C/C++-Dateien muss ein passender Test existieren
|
||||
Wenn du eine C/C++-Datei änderst, muss ein passender Test vorhanden sein.
|
||||
|
||||
Es wird nach Tests in diesen Verzeichnissen gesucht:
|
||||
|
||||
- `tests/`
|
||||
- `test/`
|
||||
- `qt6-after/qt6-migrated-benchmark/tests/`
|
||||
|
||||
Unterstützte typische Testnamen sind zum Beispiel:
|
||||
|
||||
- `test_<name>.cpp`
|
||||
- `<name>_test.cpp`
|
||||
- `<name>test.cpp`
|
||||
- `<name>gtest.cpp`
|
||||
|
||||
Zusätzlich erkennt die Policy auch ähnliche Dateinamen heuristisch, solange `test` oder `gtest` im Dateinamen vorkommt.
|
||||
|
||||
#### Beispiel
|
||||
Wenn du `main.cpp` änderst, ist z. B. passend:
|
||||
|
||||
```text
|
||||
tests/test_main.cpp
|
||||
```
|
||||
|
||||
Wenn kein passender Test gefunden wird, schlägt der Build fehl.
|
||||
|
||||
---
|
||||
|
||||
### 4. Nur gültig SSH-signierte Commits sind erlaubt
|
||||
Neue Commits im geprüften Bereich müssen gültig **mit SSH signiert** sein.
|
||||
|
||||
Die Jenkins-Policy prüft Commit-Signaturen gegen eine erlaubte Signiererdatei:
|
||||
|
||||
```text
|
||||
ci/allowed_signers
|
||||
```
|
||||
|
||||
Wenn ein Commit:
|
||||
|
||||
- nicht signiert ist
|
||||
- nicht per SSH signiert ist
|
||||
- mit einem nicht erlaubten SSH-Schlüssel signiert ist
|
||||
|
||||
dann schlägt der Build fehl.
|
||||
|
||||
---
|
||||
|
||||
## SSH-signierte Commits einrichten
|
||||
|
||||
### 1. SSH-Signing-Key erzeugen
|
||||
Falls du noch keinen dedizierten Signing-Key hast:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_signing -C "dein.name@example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Git für SSH-Signing konfigurieren
|
||||
|
||||
```bash
|
||||
git config --global gpg.format ssh
|
||||
git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
|
||||
Damit werden neue Commits automatisch mit deinem SSH-Key signiert.
|
||||
|
||||
---
|
||||
|
||||
### 3. Öffentlichen Schlüssel zur Allowlist hinzufügen
|
||||
Dein öffentlicher Schlüssel muss in `ci/allowed_signers` eingetragen sein.
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
<principal> <public-key>
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
max.mustermann@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
|
||||
```
|
||||
|
||||
Ohne Eintrag in `ci/allowed_signers` wird dein signierter Commit in Jenkins trotzdem abgelehnt.
|
||||
|
||||
---
|
||||
|
||||
### 4. Optional: lokale Verifikation aktivieren
|
||||
Wenn du lokal dieselbe Vertrauenskette wie Jenkins verwenden willst:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/git
|
||||
cp ci/allowed_signers ~/.config/git/allowed_signers
|
||||
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Signierten Commit erstellen
|
||||
```bash
|
||||
git add .
|
||||
git commit -S -m "Meine Änderung"
|
||||
git push
|
||||
```
|
||||
|
||||
Wenn `commit.gpgsign=true` gesetzt ist, signiert Git Commits in der Regel automatisch.
|
||||
|
||||
---
|
||||
|
||||
### 6. Signatur lokal prüfen
|
||||
```bash
|
||||
git log --show-signature -1
|
||||
git verify-commit HEAD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lokaler Minimal-Check vor dem Push
|
||||
|
||||
Vor dem Push solltest du mindestens diese Schritte ausführen:
|
||||
|
||||
### Format anwenden
|
||||
```bash
|
||||
clang-format -i main.cpp tests/test_main.cpp
|
||||
```
|
||||
|
||||
### Policy lokal ausführen
|
||||
```bash
|
||||
python3 ci/check_pr_policies.py
|
||||
```
|
||||
|
||||
## Projekt lokal im CI-Container bauen
|
||||
|
||||
Zuerst das lokale CI-Image bauen:
|
||||
|
||||
```bash
|
||||
docker build -t lifemaps/ci-cpp:latest -f Dockerfile.ci .
|
||||
```
|
||||
|
||||
Danach das Projekt im Container bauen und testen:
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
-v "$PWD:/workspace" \
|
||||
-w /workspace \
|
||||
lifemaps/ci-cpp:latest \
|
||||
bash -lc '
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug &&
|
||||
cmake --build build --parallel &&
|
||||
ctest --test-dir build --output-on-failure
|
||||
'
|
||||
```
|
||||
|
||||
Damit wird dieselbe Toolchain verwendet wie in der Jenkins-CI.
|
||||
|
||||
|
||||
## Policy-Check lokal im CI-Container ausführen
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
-v "$PWD:/workspace" \
|
||||
-w /workspace \
|
||||
lifemaps/ci-cpp:latest \
|
||||
bash -lc 'python3 ci/check_pr_policies.py'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verhalten in Jenkins
|
||||
|
||||
Die Jenkins-Pipeline umfasst typischerweise diese Stages:
|
||||
|
||||
1. **Checkout**
|
||||
2. **Policy Checks**
|
||||
3. **Configure**
|
||||
4. **Build**
|
||||
5. **Test**
|
||||
6. **Cleanup**
|
||||
|
||||
Wenn die Policy-Checks fehlschlagen, werden die späteren Build- und Test-Stages nicht mehr ausgeführt.
|
||||
|
||||
---
|
||||
|
||||
## Typische Fehlerursachen
|
||||
|
||||
### Formatfehler
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: Formatfehler in tests/test_main.cpp
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
```bash
|
||||
clang-format -i tests/test_main.cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Kein passender Test gefunden
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: Kein passender Test unter tests/, test/ oder qt6-after/qt6-migrated-benchmark/tests für main.cpp gefunden.
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
- passenden Test anlegen
|
||||
- Testdatei passend benennen
|
||||
- Test in einem der erlaubten Testverzeichnisse ablegen
|
||||
|
||||
---
|
||||
|
||||
### Commit nicht gültig SSH-signiert
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: Commit ist nicht gültig mit erlaubtem SSH-Signierer signiert: <sha>
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
- SSH-Signing lokal korrekt konfigurieren
|
||||
- prüfen, ob der richtige Schlüssel verwendet wird
|
||||
- prüfen, ob der öffentliche Schlüssel in `ci/allowed_signers` eingetragen ist
|
||||
- Commit neu erstellen oder neu signieren
|
||||
|
||||
---
|
||||
|
||||
### `.clang-format` fehlt
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: .clang-format fehlt im Repository-Root.
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
- `.clang-format` im Repository-Root anlegen oder wiederherstellen
|
||||
|
||||
---
|
||||
|
||||
## Empfohlener Workflow
|
||||
|
||||
1. Feature-Branch erstellen
|
||||
2. Änderungen implementieren
|
||||
3. Test ergänzen oder anpassen
|
||||
4. Code formatieren
|
||||
5. Commit SSH-signiert erstellen
|
||||
6. lokal Policy, Build und Tests ausführen
|
||||
7. pushen
|
||||
8. Pull Request öffnen
|
||||
9. Jenkins-Ergebnis prüfen
|
||||
10. erst bei grünem Status mergen
|
||||
|
||||
---
|
||||
|
||||
## Kurz-Checkliste vor jedem Push
|
||||
|
||||
- [ ] C/C++-Dateien sind formatiert
|
||||
- [ ] passender Test existiert
|
||||
- [ ] Commit ist SSH-signiert
|
||||
- [ ] öffentlicher Schlüssel steht in `ci/allowed_signers`
|
||||
- [ ] `python3 ci/check_pr_policies.py` läuft erfolgreich
|
||||
- [ ] Build und Tests laufen lokal
|
||||
|
||||
---
|
||||
|
||||
## Ziel der Regeln
|
||||
|
||||
Die Policies sollen sicherstellen, dass:
|
||||
|
||||
- Code konsistent formatiert ist
|
||||
- Änderungen nachvollziehbar getestet sind
|
||||
- nur autorisierte Signierer Commits einbringen
|
||||
- PRs frühzeitig automatisiert validiert werden
|
||||
@@ -1,336 +0,0 @@
|
||||
# Entire auf Headless-Server mit EU-Kontext einrichten
|
||||
|
||||
Diese Anleitung beschreibt die Einrichtung von Entire auf einem Headless-Host mit bestehendem Git-Repository auf einem eigenen Gitea-Server und Nutzung des **EU-Kontexts**.
|
||||
|
||||
## Ziel
|
||||
|
||||
- Entire ohne GUI/Browser auf dem Server verwenden
|
||||
- Login über Device-Code-Flow
|
||||
- Tokens ohne OS-Keyring in Datei speichern
|
||||
- EU-Jurisdiktion / EU-Cluster verwenden
|
||||
- Entire lokal in einem bestehenden Git-Repository aktivieren
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- `entire` CLI ist installiert
|
||||
- Zugriff per SSH auf den Headless-Server
|
||||
- funktionierendes lokales Git-Checkout
|
||||
- `origin`-Remote ist gesetzt
|
||||
- Browser auf einem **anderen lokalen Rechner** verfügbar
|
||||
|
||||
Beispiel prüfen:
|
||||
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
Beispielausgabe:
|
||||
|
||||
```bash
|
||||
origin git@devel:lifemaps/qt-migration-copilot-demo.git (fetch)
|
||||
origin git@devel:lifemaps/qt-migration-copilot-demo.git (push)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Token-Speicherung für Headless-Betrieb aktivieren
|
||||
|
||||
Auf Headless-Systemen gibt es oft keinen Linux-Keyring (`org.freedesktop.secrets`).
|
||||
Deshalb sollten Entire-Tokens in einer Datei gespeichert werden.
|
||||
|
||||
Empfohlene Umgebungsvariablen:
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"
|
||||
export ACCESSIBLE=1
|
||||
```
|
||||
|
||||
Temporär in der aktuellen Shell:
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"
|
||||
export ACCESSIBLE=1
|
||||
```
|
||||
|
||||
Dauerhaft in `~/.bashrc`:
|
||||
|
||||
```bash
|
||||
echo 'export ENTIRE_TOKEN_STORE=file' >> ~/.bashrc
|
||||
echo 'export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"' >> ~/.bashrc
|
||||
echo 'export ACCESSIBLE=1' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Im EU-Kontext anmelden
|
||||
|
||||
Für Headless-Systeme den Device-Code-Flow verwenden:
|
||||
|
||||
```bash
|
||||
entire login --device --server https://eu.auth.entire.io
|
||||
```
|
||||
|
||||
Das CLI zeigt dann etwa:
|
||||
|
||||
- einen **Device Code**
|
||||
- eine **Login-URL**
|
||||
|
||||
Die URL im Browser **auf dem lokalen Rechner** öffnen und die Anmeldung dort bestätigen.
|
||||
|
||||
### Wichtiger Hinweis
|
||||
|
||||
Wenn auf dem Server kein Browser verfügbar ist, ist eine Meldung wie diese normal:
|
||||
|
||||
```text
|
||||
Warning: failed to open browser: xdg-open not found
|
||||
```
|
||||
|
||||
Das ist **nicht** das eigentliche Problem.
|
||||
|
||||
Falls Entire versucht, Tokens im Keyring zu speichern und dabei fehlschlägt, war `ENTIRE_TOKEN_STORE=file` nicht gesetzt.
|
||||
|
||||
---
|
||||
|
||||
## 3. Login prüfen
|
||||
|
||||
Nach erfolgreicher Anmeldung:
|
||||
|
||||
```bash
|
||||
entire auth status
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
Logged in to https://eu.auth.entire.io
|
||||
Context: eu.auth.entire.io
|
||||
Token: stored in file /home/<user>/.config/entire/tokens.json
|
||||
```
|
||||
|
||||
Falls mehrere Kontexte existieren:
|
||||
|
||||
```bash
|
||||
entire auth contexts
|
||||
entire auth use --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Prüfen, ob wirklich EU verwendet wird
|
||||
|
||||
Zusätzlich zum Login-Kontext sollte geprüft werden, auf welchem Cluster die Entire-Repositories liegen.
|
||||
|
||||
Projekt-Repositories anzeigen:
|
||||
|
||||
```bash
|
||||
entire repo list <project-id>
|
||||
```
|
||||
|
||||
Wenn in der Spalte `CLUSTER` z. B. Folgendes erscheint:
|
||||
|
||||
```text
|
||||
aws-eu-central-1.entire.io
|
||||
```
|
||||
|
||||
dann liegen diese Repositories auf einem **EU-Cluster**.
|
||||
|
||||
### Empfohlener Doppel-Check
|
||||
|
||||
```bash
|
||||
entire auth status
|
||||
entire repo list <project-id>
|
||||
```
|
||||
|
||||
EU ist praktisch bestätigt, wenn:
|
||||
|
||||
- `Context: eu.auth.entire.io` aktiv ist
|
||||
- die Repositories auf `aws-eu-central-1.entire.io` liegen
|
||||
|
||||
---
|
||||
|
||||
## 5. Entire im lokalen Repository aktivieren
|
||||
|
||||
In das lokale Repository wechseln:
|
||||
|
||||
```bash
|
||||
cd ~/coplint.d/qt-migration-copilot-demo
|
||||
```
|
||||
|
||||
Dann Entire aktivieren:
|
||||
|
||||
```bash
|
||||
entire enable
|
||||
```
|
||||
|
||||
Status prüfen:
|
||||
|
||||
```bash
|
||||
entire status
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
● Enabled · branch bugfix/ci-pipeline-commissioning
|
||||
Agents · Copilot CLI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Agent-Integration prüfen oder ergänzen
|
||||
|
||||
Verfügbare Agenten anzeigen:
|
||||
|
||||
```bash
|
||||
entire agent list
|
||||
```
|
||||
|
||||
Hilfe für das Hinzufügen:
|
||||
|
||||
```bash
|
||||
entire agent add --help
|
||||
```
|
||||
|
||||
Neuen Agenten hinzufügen:
|
||||
|
||||
```bash
|
||||
entire agent add <name>
|
||||
```
|
||||
|
||||
Danach erneut prüfen:
|
||||
|
||||
```bash
|
||||
entire status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Unterschied zwischen lokalem Git-Repo und Entire-Repo verstehen
|
||||
|
||||
Wichtig: Entire verwendet hier **zwei verschiedene Konzepte**.
|
||||
|
||||
### Lokales Git-Repository
|
||||
Das echte Code-Repository, z. B.:
|
||||
|
||||
```bash
|
||||
git@devel:lifemaps/qt-migration-copilot-demo.git
|
||||
```
|
||||
|
||||
### Entire-Repository
|
||||
Ein separates Objekt in Entire, verwaltet über:
|
||||
|
||||
```bash
|
||||
entire repo list <project-id>
|
||||
entire repo get <repo> --project <project-id>
|
||||
entire repo create <name> --project <project-id>
|
||||
```
|
||||
|
||||
### Konsequenz
|
||||
|
||||
`entire enable` aktiviert das **lokale Git-Repository**,
|
||||
aber es erzeugt **nicht automatisch** ein neues `entire repo`-Objekt.
|
||||
|
||||
Darum kann es sein, dass:
|
||||
|
||||
- `entire status` = aktiviert
|
||||
- aber `entire repo list <project-id>` dein lokales Repo **nicht** zeigt
|
||||
|
||||
Das ist normales Verhalten.
|
||||
|
||||
---
|
||||
|
||||
## 8. Optional: Entire-Repo im Projekt anlegen
|
||||
|
||||
Wenn zusätzlich ein eigenes Entire-Repo-Objekt für das lokale Projekt gewünscht ist:
|
||||
|
||||
```bash
|
||||
entire repo create qt-migration-copilot-demo --project <project-id>
|
||||
```
|
||||
|
||||
Danach prüfen:
|
||||
|
||||
```bash
|
||||
entire repo list <project-id>
|
||||
```
|
||||
|
||||
### Hinweis
|
||||
|
||||
Nach bisherigem CLI-Verhalten wird dabei nur ein **Entire-internes Repo-Objekt** erzeugt.
|
||||
Es gibt in der gezeigten Hilfe keinen Hinweis darauf, dass dabei automatisch eine Git-URL aus Gitea importiert oder synchronisiert wird.
|
||||
|
||||
---
|
||||
|
||||
## 9. Typischer Minimal-Workflow
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"
|
||||
export ACCESSIBLE=1
|
||||
|
||||
entire login --device --server https://eu.auth.entire.io
|
||||
entire auth status
|
||||
|
||||
cd ~/coplint.d/qt-migration-copilot-demo
|
||||
entire enable
|
||||
entire status
|
||||
entire agent list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Troubleshooting
|
||||
|
||||
### Kein Browser auf dem Server
|
||||
Normal auf Headless-Hosts.
|
||||
Device-Code-URL auf lokalem Rechner öffnen.
|
||||
|
||||
### Login erfolgreich, aber `Not logged in`
|
||||
Ursache meist fehlender Keyring und keine Datei-Speicherung.
|
||||
|
||||
Lösung:
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
entire login --device --server https://eu.auth.entire.io
|
||||
```
|
||||
|
||||
### Entire ist aktiviert, aber nicht in `entire repo list`
|
||||
Das ist erwartbar, weil lokales Git-Repo und Entire-Projektrepo getrennte Objekte sind.
|
||||
|
||||
### Diagnose
|
||||
Bei Problemen:
|
||||
|
||||
```bash
|
||||
entire doctor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Empfehlung für dieses Setup
|
||||
|
||||
Für ein Setup mit **Gitea + Jenkins + Entire** gilt:
|
||||
|
||||
- **Gitea** bleibt Source-of-Truth für den Code
|
||||
- **Jenkins** bleibt CI/CD-System
|
||||
- **Entire** ergänzt Session-, Agent- und Checkpoint-Tracking im lokalen Repository
|
||||
- **Entire-Projekte/Repos** sind zusätzliche Verwaltungsobjekte und nicht identisch mit dem Gitea-Remote
|
||||
|
||||
---
|
||||
|
||||
## 12. Nützliche Befehle
|
||||
|
||||
```bash
|
||||
entire auth status
|
||||
entire auth contexts
|
||||
entire project list
|
||||
entire repo list <project-id>
|
||||
entire enable
|
||||
entire status
|
||||
entire agent list
|
||||
entire doctor
|
||||
```
|
||||
@@ -1,280 +0,0 @@
|
||||
# scripts/
|
||||
|
||||
Dieses Verzeichnis enthält Hilfsskripte für den Demo- und Benchmark-Workflow rund um:
|
||||
|
||||
- **Qt5 → Qt6**
|
||||
- **C++11 → C++23**
|
||||
- **Copilot CLI**
|
||||
- **Entire CLI**
|
||||
- **Scaleway BYOK / OpenAI-kompatibler Provider**
|
||||
- **lokale Reproduzierbarkeit der Migration**
|
||||
|
||||
## Übersicht
|
||||
|
||||
### `create-qt5-legacy-benchmark.sh`
|
||||
Erzeugt das Legacy-Beispielprojekt unter:
|
||||
|
||||
```text
|
||||
qt5-before/qt5-legacy-migration-benchmark
|
||||
```
|
||||
|
||||
Das Projekt ist absichtlich veraltet aufgebaut, um eine realistische Migration zu erzwingen, z. B. mit:
|
||||
|
||||
- `QRegExp`
|
||||
- `QDesktopWidget`
|
||||
- `QTextCodec`
|
||||
- alter `SIGNAL/SLOT`-Syntax
|
||||
- `Qt5::Core`, `Qt5::Gui`, `Qt5::Widgets`
|
||||
- `C++11`
|
||||
|
||||
### `run-entire-copilot-migration.sh`
|
||||
Standard-Workflow für eine Copilot-CLI-Migration mit Entire:
|
||||
|
||||
1. Entire für Copilot CLI aktivieren
|
||||
2. Baseline committen
|
||||
3. Copilot-Session starten
|
||||
4. Ergebnis committen
|
||||
5. Checkpoints anzeigen
|
||||
6. letzten Stand mit Entire erklären
|
||||
|
||||
Dieses Skript ist für den normalen Migrationslauf gedacht.
|
||||
|
||||
**Modellwahl:**
|
||||
Das Skript akzeptiert optional ein Modell als zweiten Parameter.
|
||||
|
||||
Default:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh
|
||||
```
|
||||
|
||||
verwendet intern:
|
||||
|
||||
```bash
|
||||
copilot --model "qwen3.5-397b-a17b"
|
||||
```
|
||||
|
||||
Mit explizitem Modell:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark gpt-5
|
||||
```
|
||||
|
||||
### `run-entire-copilot-migration-with-optional-rewind-resume.sh`
|
||||
Wie das Standardskript, aber zusätzlich mit optionalem:
|
||||
|
||||
- `entire checkpoint rewind`
|
||||
- `entire session resume <branch>`
|
||||
|
||||
**Modellwahl:**
|
||||
Auch dieses Skript akzeptiert optional ein Modell als zweiten Parameter.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh qt5-before/qt5-legacy-migration-benchmark claude-sonnet-4
|
||||
```
|
||||
|
||||
**Wichtig:**
|
||||
Die Entire-Dokumentation ist bei Copilot CLI in Bezug auf Rewind/Resume nicht ganz konsistent.
|
||||
Die Copilot-CLI-Seite beschreibt Rewind und Resume, während die allgemeine Agent-Übersicht teils eingeschränkte Aussagen macht.
|
||||
|
||||
Darum behandelt dieses Skript Rewind/Resume bewusst als **optional** und **fehlertolerant**.
|
||||
|
||||
### `use-scaleway-copilot-debug.sh`
|
||||
Hilfsskript zum Setzen einer **Scaleway OpenAI-kompatiblen BYOK-Konfiguration** für Copilot CLI.
|
||||
|
||||
Typischer Zweck:
|
||||
|
||||
- Provider-Umgebungsvariablen setzen
|
||||
- Copilot CLI mit Scaleway testen
|
||||
- Modellname konfigurieren
|
||||
- Token-Limits für nicht im Copilot-Katalog enthaltene Modelle explizit setzen
|
||||
- Debugging eines externen Providers vereinfachen
|
||||
|
||||
Ein typischer Inhalt wäre z. B.:
|
||||
|
||||
- `COPILOT_PROVIDER_TYPE=openai`
|
||||
- `COPILOT_PROVIDER_BASE_URL=https://api.scaleway.ai/v1`
|
||||
- `COPILOT_PROVIDER_API_KEY=...`
|
||||
- `COPILOT_MODEL=qwen3.5-397b-a17b`
|
||||
- `COPILOT_PROVIDER_MAX_PROMPT_TOKENS=...`
|
||||
- `COPILOT_PROVIDER_MAX_OUTPUT_TOKENS=...`
|
||||
|
||||
---
|
||||
|
||||
## NOTE: Secret in `.envrc` hinterlegen
|
||||
|
||||
Das Secret für den verwendeten LLM-Provider soll **nicht** direkt in die Skripte geschrieben oder committed werden.
|
||||
|
||||
Lege den API-Key stattdessen in einer lokalen `.envrc` ab, z. B.:
|
||||
|
||||
```bash
|
||||
export SCW_SECRET_KEY="YOUR_PROVIDER_SECRET_HERE"
|
||||
```
|
||||
|
||||
oder allgemeiner:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="YOUR_PROVIDER_SECRET_HERE"
|
||||
```
|
||||
|
||||
Danach die Umgebung neu laden, z. B. mit:
|
||||
|
||||
```bash
|
||||
direnv allow
|
||||
```
|
||||
|
||||
oder die Datei manuell sourcen, falls du kein `direnv` verwendest.
|
||||
|
||||
**Wichtig:**
|
||||
- `.envrc` nicht committen
|
||||
- Secrets nicht im Repository speichern
|
||||
- Provider-Zugangsdaten nur lokal halten
|
||||
|
||||
---
|
||||
|
||||
## Empfohlene Reihenfolge
|
||||
|
||||
### 1. Legacy-Projekt erzeugen
|
||||
|
||||
```bash
|
||||
./scripts/create-qt5-legacy-benchmark.sh
|
||||
```
|
||||
|
||||
### 2. Optional: Copilot mit Scaleway konfigurieren
|
||||
|
||||
```bash
|
||||
source ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
Danach kannst du prüfen, ob die Variablen gesetzt sind:
|
||||
|
||||
```bash
|
||||
echo "$COPILOT_PROVIDER_BASE_URL"
|
||||
echo "$COPILOT_MODEL"
|
||||
```
|
||||
|
||||
### 3. Migration mit Entire + Copilot starten
|
||||
|
||||
Standard mit Default-Modell:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark
|
||||
```
|
||||
|
||||
Standard mit explizitem Modell:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark qwen3.5-397b-a17b
|
||||
```
|
||||
|
||||
Mit optionalem Rewind/Resume:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh qt5-before/qt5-legacy-migration-benchmark qwen3.5-397b-a17b
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Copilot-Prompt für die Migration
|
||||
|
||||
Die Migrationsskripte verwenden sinngemäß einen Prompt in dieser Richtung:
|
||||
|
||||
```text
|
||||
Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23.
|
||||
|
||||
Requirements:
|
||||
- Read the legacy source project from qt5-before/qt5-legacy-migration-benchmark
|
||||
- Write the migrated project into qt6-after/qt6-migrated-benchmark
|
||||
- Do not modify the legacy source tree in place
|
||||
- Update CMake from Qt5 to Qt6
|
||||
- Keep Core, Gui, and Widgets explicitly linked
|
||||
- Replace QRegExp with QRegularExpression
|
||||
- Replace QDesktopWidget / QApplication::desktop() usage with QScreen-based Qt6 APIs
|
||||
- Remove legacy QTextCodec usage and use modern Qt6-safe text handling
|
||||
- Modernize old SIGNAL/SLOT syntax to function-pointer connect syntax
|
||||
- Replace legacy null pointer literals with nullptr
|
||||
- Preserve the .ui and .qrc integration
|
||||
- Preserve application behavior
|
||||
- Upgrade the project from C++11 to C++23
|
||||
- For every code change inside a method, add at least three tests covering the changed behavior
|
||||
- Place all generated tests in qt6-after/qt6-migrated-benchmark/tests
|
||||
- Update CMake so the migrated application and all tests build successfully with CTest
|
||||
- Keep the test file naming compatible with repository policy checks
|
||||
- Briefly explain each migration change
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entire-Hinweise
|
||||
|
||||
Entire erfasst bei unterstützten Agenten u. a.:
|
||||
|
||||
- Gesprächsverlauf
|
||||
- Dateiveränderungen
|
||||
- Zeitstempel
|
||||
- Checkpoints bei Commits
|
||||
- Session-Kontext auf dem Branch `entire/checkpoints/v1`
|
||||
|
||||
Typischer Entire-Grundablauf:
|
||||
|
||||
```bash
|
||||
entire agent add copilot-cli
|
||||
copilot --model "qwen3.5-397b-a17b"
|
||||
git add .
|
||||
git commit -m "..."
|
||||
entire checkpoint list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hinweise zu `use-scaleway-copilot-debug.sh`
|
||||
|
||||
Das Skript sollte **gesourct** werden, nicht als eigener Prozess laufen, damit exportierte Variablen in deiner aktuellen Shell erhalten bleiben:
|
||||
|
||||
```bash
|
||||
source ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
oder kurz:
|
||||
|
||||
```bash
|
||||
. ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
Außerdem:
|
||||
|
||||
- keine echten Secrets committen
|
||||
- API-Keys nicht im Repo speichern
|
||||
- sensible Werte aus `.envrc` oder bereits gesetzten Umgebungsvariablen übernehmen
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
export SCW_SECRET_KEY="..."
|
||||
source ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Empfohlene Erweiterungen
|
||||
|
||||
Optional kannst du später noch ergänzen:
|
||||
|
||||
- `build-qt6-after.sh`
|
||||
- `run-ctest-qt6-after.sh`
|
||||
- `compare-before-after.sh`
|
||||
- `capture-copilot-env.sh`
|
||||
|
||||
---
|
||||
|
||||
## Kurzfassung
|
||||
|
||||
Der `scripts/`-Ordner enthält alles, um:
|
||||
|
||||
1. ein Legacy-Qt5-Projekt zu erzeugen,
|
||||
2. Copilot CLI optional mit einem externen Modell zu konfigurieren,
|
||||
3. Secrets lokal über `.envrc` bereitzustellen,
|
||||
4. eine Qt6/C++23-Migration mit Entire zu dokumentieren,
|
||||
5. und den Migrationslauf reproduzierbar zu machen.
|
||||
@@ -1,97 +0,0 @@
|
||||
# Copilot CLI mit Scaleway verwenden
|
||||
|
||||
## 1) `.envrc` anlegen
|
||||
|
||||
Lege in deinem Projekt eine Datei `.envrc` an:
|
||||
|
||||
```bash name=.envrc
|
||||
export STRIX_LLM="qwen3.5-397b-a17b"
|
||||
export SCW_SECRET_KEY="REPLACE_WITH_YOURS"
|
||||
|
||||
export COPILOT_PROVIDER_TYPE="openai"
|
||||
export COPILOT_PROVIDER_API_KEY="${SCW_SECRET_KEY}"
|
||||
export COPILOT_PROVIDER_BASE_URL="https://api.scaleway.ai/v1"
|
||||
export COPILOT_MODEL="${STRIX_LLM}"
|
||||
|
||||
export COPILOT_PROVIDER_MAX_PROMPT_TOKENS="32768"
|
||||
export COPILOT_PROVIDER_MAX_OUTPUT_TOKENS="8192"
|
||||
```
|
||||
|
||||
## 2) `direnv` erlauben
|
||||
|
||||
Falls `direnv` schon installiert ist:
|
||||
|
||||
```bash name=direnv-allow.sh
|
||||
direnv allow
|
||||
```
|
||||
|
||||
Wenn noch nicht in der Shell aktiviert, ergänze deine Shell-Config.
|
||||
|
||||
### Bash
|
||||
```bash name=bash-direnv-hook.sh
|
||||
eval "$(direnv hook bash)"
|
||||
```
|
||||
|
||||
### Zsh
|
||||
```bash name=zsh-direnv-hook.sh
|
||||
eval "$(direnv hook zsh)"
|
||||
```
|
||||
|
||||
Danach Shell neu laden:
|
||||
|
||||
```bash name=reload-shell-config.sh
|
||||
source ~/.bashrc
|
||||
# oder
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
## 3) Startskript anlegen
|
||||
|
||||
```bash name=use-scaleway-copilot.sh
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v copilot >/dev/null 2>&1; then
|
||||
echo "copilot CLI nicht gefunden."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${COPILOT_PROVIDER_TYPE:?COPILOT_PROVIDER_TYPE fehlt}"
|
||||
: "${COPILOT_PROVIDER_API_KEY:?COPILOT_PROVIDER_API_KEY fehlt}"
|
||||
: "${COPILOT_PROVIDER_BASE_URL:?COPILOT_PROVIDER_BASE_URL fehlt}"
|
||||
: "${COPILOT_MODEL:?COPILOT_MODEL fehlt}"
|
||||
|
||||
echo "Starte Copilot CLI mit:"
|
||||
echo " Provider: ${COPILOT_PROVIDER_TYPE}"
|
||||
echo " Base URL: ${COPILOT_PROVIDER_BASE_URL}"
|
||||
echo " Modell: ${COPILOT_MODEL}"
|
||||
|
||||
exec copilot "$@"
|
||||
```
|
||||
|
||||
Dann ausführbar machen:
|
||||
|
||||
```bash name=chmod-script.sh
|
||||
chmod +x use-scaleway-copilot.sh
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash name=start-script.sh
|
||||
./use-scaleway-copilot.sh
|
||||
```
|
||||
|
||||
Oder mit Argumenten:
|
||||
|
||||
```bash name=start-script-with-args.sh
|
||||
./use-scaleway-copilot.sh --model="qwen3.5-397b-a17b"
|
||||
```
|
||||
|
||||
## 4) `.gitignore` ergänzen
|
||||
|
||||
Falls noch nicht vorhanden:
|
||||
|
||||
```gitignore name=.gitignore
|
||||
.env
|
||||
.envrc
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
build-rti-full
|
||||
build-rti-off
|
||||
build-rti-off-notests
|
||||
build-*
|
||||
CMakeFiles
|
||||
fix_qt6_gui_deps2.py
|
||||
fix_qt6_gui_deps.py
|
||||
fix_rti_conditionals.py
|
||||
fix_target_checks.py
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../external
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignTrailingComments: true
|
||||
AlignAfterOpenBracket: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Allman
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeComma
|
||||
ColumnLimit: 170
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
IncludeCategories:
|
||||
- Regex: '^".*"$'
|
||||
Priority: 1
|
||||
- Regex: '^<.*>$'
|
||||
Priority: 2
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
InsertNewlineAtEOF: true
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: All
|
||||
PenaltyBreakBeforeFirstCallParameter: 1000
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
PointerAlignment: Right
|
||||
PointerBindsToType: false
|
||||
SortIncludes: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpacesInAngles: false
|
||||
SpacesInParentheses: false
|
||||
Standard: c++20
|
||||
UseTab: Never
|
||||
TabWidth: 2
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
CompactNamespaces: false
|
||||
DerivePointerAlignment: false
|
||||
ReflowComments: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
FixNamespaceComments: false
|
||||
@@ -0,0 +1,4 @@
|
||||
# export MIGRATE_PIPELINE_ROOT="/home/torsten/sandbox/qt6-migration-pipeline"
|
||||
export MIGRATE_PIPELINE_ROOT="/home/torsten/sandbox/qt6-migration-pipeline"
|
||||
export LANG=C.UTF-8
|
||||
export LC_ALL=C.UTF-8
|
||||
@@ -0,0 +1,18 @@
|
||||
build-rti-full
|
||||
build-rti-off
|
||||
build-rti-off-notests
|
||||
build-*
|
||||
CMakeFiles
|
||||
fix_qt6_gui_deps2.py
|
||||
fix_qt6_gui_deps.py
|
||||
fix_rti_conditionals.py
|
||||
fix_target_checks.py
|
||||
Lts/Tools/ArAssetsV2/build
|
||||
Lts/Tools/ArAssetsV3/build
|
||||
Lts/Tools/ArAssetsV3_Qatar/build
|
||||
build/
|
||||
build2/
|
||||
.envrc
|
||||
build4
|
||||
build4_*
|
||||
deb_build_tmp
|
||||
+1115
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
||||
# Minimal CTestCustom.ctest placeholder
|
||||
# Created to satisfy top-level CMake configure step
|
||||
@@ -0,0 +1,48 @@
|
||||
# Prebuilt build image for the Release 2 branch
|
||||
# (Ubuntu 26.04 + Qt6 + all dependencies for the legatus release build).
|
||||
#
|
||||
# Build with (the APT package list mirrors the in-container install list of
|
||||
# new_docker_build_release2.sh):
|
||||
# docker build --build-arg APT_PACKAGES="<space-separated packages>" -t <image> .
|
||||
|
||||
FROM ubuntu:26.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG APT_PACKAGES
|
||||
ARG QT_VERSION=6.10.2
|
||||
ARG QT_ARCH=linux_gcc_64
|
||||
ARG QT_BASE_DIR=/opt/Qt
|
||||
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y -qq \
|
||||
${APT_PACKAGES} \
|
||||
libqt6svg6-dev \
|
||||
libgtest-dev \
|
||||
libgmock-dev \
|
||||
python3-pip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Qt6 packages via aqtinstall
|
||||
RUN pip3 install --break-system-packages --quiet aqtinstall && \
|
||||
aqt install-qt linux desktop ${QT_VERSION} ${QT_ARCH} \
|
||||
-m qtmultimedia qtserialport qtimageformats qtwebsockets qtwebengine \
|
||||
qtpdf qtwebview qt5compat qtwebchannel qtpositioning qtlocation qtsensors \
|
||||
--outputdir ${QT_BASE_DIR} && \
|
||||
rm -rf /root/.cache/Aqt
|
||||
|
||||
# Install KDReports from source (apt package not available in Ubuntu 22.04/26.04)
|
||||
# Try apt install first (will fail), then build from KDAB GitHub mirror
|
||||
RUN apt-get install -y -qq libkdreports-dev 2>/dev/null || \
|
||||
(git clone --depth 1 https://github.com/KDAB/KDReports.git /tmp/KDReports && \
|
||||
cd /tmp/KDReports && \
|
||||
mkdir build && cd build && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_PREFIX_PATH=${QT_BASE_DIR} .. && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
cd / && rm -rf /tmp/KDReports)
|
||||
|
||||
# Legacy sources include <KDReports/...>
|
||||
RUN ln -sfn KDReports-qt6 /usr/local/include/KDReports
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -0,0 +1,58 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(LtsPlaceholder)
|
||||
SET(DOXYGEN_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen_Lts)
|
||||
if (EXISTS "${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in")
|
||||
ADD_DOXYGEN_TARGET(Lts ${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in ${CMAKE_BINARY_DIR}/DoxyfileLts
|
||||
${CMAKE_SOURCE_DIR}/doxygen/rheinmetall_defence_logo.jpg
|
||||
${DOXYGEN_OUT_DIR})
|
||||
else()
|
||||
message(WARNING "Doxygen config ${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in not found; skipping ADD_DOXYGEN_TARGET(Lts)")
|
||||
endif()
|
||||
|
||||
# Add only existing subdirectories to allow partial checkouts / incremental migration
|
||||
# Release 2 scope: Tools, DDS, Common, Services (minimal), Proxies (TrainingAreaBase only), Plugins (Styles only for LtsWidgets dependency)
|
||||
# Excluded: Application, Viewer, Projects, most Plugins (not in Release 2)
|
||||
set(LTS_SUBDIRS Tools DDS Common Services Proxies Plugins)
|
||||
foreach(dir IN LISTS LTS_SUBDIRS)
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}")
|
||||
# Use guarded add so top-level MIGRATE_MODULE can restrict which Lts subdirs are included
|
||||
NGL_ADD_SUBDIR_GUARDED(${dir})
|
||||
else()
|
||||
message(STATUS "Skipping missing Lts subdirectory: ${dir}")
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
|
||||
IF (BUILD_TESTING)
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Tests")
|
||||
ADD_SUBDIRECTORY(Tests)
|
||||
else()
|
||||
message(STATUS "Skipping missing Tests directory under Lts (BUILD_TESTING is ON)")
|
||||
endif()
|
||||
ENDIF()
|
||||
|
||||
OPTION(BUILD_DOCS "Build documentation as html files from Lts/Documentation" OFF)
|
||||
IF (BUILD_DOCS)
|
||||
ADD_SUBDIRECTORY(Documentation)
|
||||
ENDIF()
|
||||
|
||||
# Ensure codegen macros are available before calling CODEGEN_* helpers
|
||||
INCLUDE(MacroCodeGenIdl)
|
||||
|
||||
# Add stub headers for non-RTI/non-FilterManagement builds
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/AudioVideoManagement/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/NGLLogger/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/LtsSession/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/TopicEditorBase/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/OperatorManagement/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/LtsScriptTimeline/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/LtsUtilsGuiDds/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/Services/PresentationManagement/)
|
||||
|
||||
CODEGEN_WRITE_SERVICEFILE_INFO()
|
||||
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
# Release 2 Common modules (S set: 15 modules)
|
||||
# Mock library for unit tests (CoordTrans mocks) — defines the MockCoordTrans target
|
||||
ADD_SUBDIRECTORY(CommonUnitTestsPlaceholder)
|
||||
ADD_SUBDIRECTORY(CodeGen)
|
||||
ADD_SUBDIRECTORY(LtsAppDescriptor)
|
||||
ADD_SUBDIRECTORY(LtsCommandLineParser)
|
||||
ADD_SUBDIRECTORY(LtsCoordTrans)
|
||||
ADD_SUBDIRECTORY(LtsCoordinate)
|
||||
# LtsElevation excluded from Release 2a (depends on AssetManagement, needed only by Release 2b services)
|
||||
# ADD_SUBDIRECTORY(LtsElevation)
|
||||
ADD_SUBDIRECTORY(LtsLicence)
|
||||
ADD_SUBDIRECTORY(LtsNotification)
|
||||
ADD_SUBDIRECTORY(LtsPluginManager)
|
||||
ADD_SUBDIRECTORY(LtsSession)
|
||||
ADD_SUBDIRECTORY(LtsSerialPortQuerry)
|
||||
ADD_SUBDIRECTORY(LtsUtils)
|
||||
ADD_SUBDIRECTORY(LtsWidgets)
|
||||
ADD_SUBDIRECTORY(ProjectConfig)
|
||||
# LtsMultiMonitor and PluginInterfaces required by LtsSession for Release 2a
|
||||
ADD_SUBDIRECTORY(LtsMultiMonitor)
|
||||
ADD_SUBDIRECTORY(PluginInterfaces)
|
||||
# Excluded from Release 2: LtsAppManager, LtsLogin, LtsPrinting, LtsProjectionWorkstation, LtsScriptTimeline, LtsTimeline, LtsVirtualKeyboard
|
||||
@@ -0,0 +1,177 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
# Make ${Qt6_Widgets} optional
|
||||
if(QT6_GUI_AVAILABLE AND TARGET Qt6::Widgets)
|
||||
set(Qt6_Widgets ${Qt6_Widgets})
|
||||
else()
|
||||
set(Qt6_Widgets "")
|
||||
endif()
|
||||
|
||||
|
||||
FIND_PACKAGE(RTI)
|
||||
|
||||
# KTextTemplate is Qt6-compatible replacement for Grantlee5
|
||||
IF(BUILD_GRANTLEE)
|
||||
FIND_PACKAGE(KF6TextTemplate REQUIRED)
|
||||
ENDIF()
|
||||
|
||||
NGL_ADD_LIBRARY2(ALL codegen_base SHARED
|
||||
codegen_export.h
|
||||
SqlGenerator.cpp
|
||||
SqlGenerator.h
|
||||
StructBuilder.cpp
|
||||
StructBuilder.h
|
||||
XmlReader.cpp
|
||||
XmlReader.h
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(codegen_base
|
||||
PRIVATE
|
||||
Qt6::Xml
|
||||
NGLLogger
|
||||
ngltools
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(codegen_base
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
# If RTI target is available, propagate its include dirs/compile defs
|
||||
if (TARGET RTI::nddscpp2)
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(codegen_base
|
||||
PUBLIC
|
||||
$<TARGET_PROPERTY:RTI::nddscpp2,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
NGL_TARGET_COMPILE_DEFINITIONS(codegen_base
|
||||
PUBLIC
|
||||
$<TARGET_PROPERTY:RTI::nddscpp2,INTERFACE_COMPILE_DEFINITIONS>
|
||||
RTI_DDS_AVAILABLE
|
||||
)
|
||||
message(STATUS "RTI found; building codegen_base with RTI support")
|
||||
else()
|
||||
message(STATUS "RTI not found; building codegen_base without RTI support")
|
||||
endif()
|
||||
NGL_INSTALL_TARGET(codegen_base)
|
||||
|
||||
# KTextTemplate is Qt6-compatible replacement for Grantlee5
|
||||
IF(BUILD_GRANTLEE AND TARGET KF6::TextTemplate)
|
||||
NGL_ADD_LIBRARY(ALL codegen_grantlee STATIC
|
||||
CppTopicGeneratorGrantlee.cpp
|
||||
CppTopicGeneratorGrantlee.h
|
||||
DaoGeneratorGrantlee.cpp
|
||||
DaoGeneratorGrantlee.h
|
||||
DatabaseGeneratorGrantlee.cpp
|
||||
DatabaseGeneratorGrantlee.h
|
||||
GeneratorGrantleeBase.cpp
|
||||
GeneratorGrantleeBase.h
|
||||
GrantleeWrappers.cpp
|
||||
GrantleeWrappers.h
|
||||
|
||||
svc.idl.grantlee
|
||||
svc_enums.idl.grantlee
|
||||
|
||||
svc_translation.cpp.grantlee
|
||||
svc_translation.h.grantlee
|
||||
|
||||
svc_dao.cpp.grantlee
|
||||
svc_dao.h.grantlee
|
||||
|
||||
svc_db.h.grantlee
|
||||
svc_db_impl.h.grantlee
|
||||
svc_db_impl.cpp.grantlee
|
||||
|
||||
svc_dtc.h.grantlee
|
||||
svc_dtc_impl.cpp.grantlee
|
||||
svc_dtc_impl.h.grantlee
|
||||
|
||||
svc_dts.h.grantlee
|
||||
svc_dts_impl.cpp.grantlee
|
||||
svc_dts_impl.h.grantlee
|
||||
|
||||
svc_ftc.h.grantlee
|
||||
svc_ftc_impl.cpp.grantlee
|
||||
svc_ftc_impl.h.grantlee
|
||||
|
||||
svc_fts.h.grantlee
|
||||
svc_fts_impl.cpp.grantlee
|
||||
svc_fts_impl.h.grantlee
|
||||
|
||||
svc_json.cpp.grantlee
|
||||
svc_json.h.grantlee
|
||||
|
||||
svc_topic_type_plugin.cpp.grantlee
|
||||
svc_topic_type_plugin.h.grantlee
|
||||
|
||||
svc_record_replay_plugin.cpp.grantlee
|
||||
svc_record_replay_plugin.h.grantlee
|
||||
|
||||
codegen_simple.py
|
||||
)
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(codegen_grantlee
|
||||
PRIVATE
|
||||
NGLLogger
|
||||
ProjectConfig
|
||||
PUBLIC
|
||||
codegen_base
|
||||
KF6::TextTemplate
|
||||
)
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(codegen_grantlee
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
${CMAKE_BINARY_DIR}/Z/src/NGLLogger
|
||||
)
|
||||
|
||||
# Smoke-test: parse all .grantlee templates in this directory to detect parse/render regressions
|
||||
find_package(Qt6 COMPONENTS Core QUIET)
|
||||
if (Qt6_FOUND)
|
||||
set(QTCORE_TARGET Qt6::Core)
|
||||
else()
|
||||
find_package(Qt6 COMPONENTS Core REQUIRED)
|
||||
set(QTCORE_TARGET Qt6::Core)
|
||||
endif()
|
||||
|
||||
add_executable(grantlee_parser_smoke tests/GrantleeParserSmoke.cpp)
|
||||
target_link_libraries(grantlee_parser_smoke PRIVATE KF6::TextTemplate ${QTCORE_TARGET})
|
||||
add_test(NAME GrantleeParserSmoke COMMAND grantlee_parser_smoke ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
NGL_ADD_EXECUTABLE(ALL ngl_codegen
|
||||
main_grantlee.cpp
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(ngl_codegen
|
||||
codegen_grantlee
|
||||
LtsCommandLineParser
|
||||
nglcoreapplication
|
||||
Qt6::Core
|
||||
KF6::TextTemplate
|
||||
)
|
||||
|
||||
NGL_WRAP_UI(codegen_gui_uics
|
||||
main_grantlee_gui.ui
|
||||
)
|
||||
NGL_ADD_EXECUTABLE(WS AAR SV NO_TRANSLATION codegen_gui
|
||||
${codegen_gui_uics}
|
||||
main_grantlee_gui.cpp
|
||||
main_grantlee_gui.ui
|
||||
)
|
||||
if(QT6_GUI_AVAILABLE)
|
||||
NGL_TARGET_LINK_LIBRARIES(codegen_gui
|
||||
codegen_grantlee
|
||||
LtsCommandLineParser
|
||||
nglapplication
|
||||
${Qt6_Widgets}
|
||||
KF6::TextTemplate
|
||||
)
|
||||
endif()
|
||||
ELSE()
|
||||
message(STATUS "BUILD_GRANTLEE disabled or Grantlee not found; skipping codegen_grantlee, ngl_codegen, and codegen_gui targets")
|
||||
ENDIF()
|
||||
|
||||
#
|
||||
# Unit tests
|
||||
#
|
||||
ADD_SUBDIRECTORY(unit-test)
|
||||
@@ -0,0 +1,330 @@
|
||||
|
||||
|
||||
#include "CppTopicGeneratorGrantlee.h"
|
||||
|
||||
#include "StructBuilder.h"
|
||||
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <ktexttemplate/context.h>
|
||||
|
||||
#ifdef RTI_HEADER_AVAILABLE
|
||||
#include <dds/core/xtypes/EnumType.hpp>
|
||||
#endif
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
CppTopicGeneratorGrantlee::CppTopicGeneratorGrantlee() : GeneratorGrantleeBase("CodeGen.CppTopicGeneratorGrantlee") {}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::init(const XmlReader* reader, StructBuilder* structBuilder)
|
||||
{
|
||||
m_reader = reader;
|
||||
if (!GeneratorGrantleeBase::initInternal(reader, structBuilder))
|
||||
return false;
|
||||
|
||||
addCustomFunctions(reader->getCustomFunctions());
|
||||
|
||||
return initDataTopics() && initFunctionTopics() && initIdlStructs();
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::initDataTopics()
|
||||
{
|
||||
DataTopicsWrapper* dataTopicsWrapper = new DataTopicsWrapper(QString(), this);
|
||||
const auto dataTopics = m_reader->getIdlStructInformation();
|
||||
QVector<IdlData> keyColumns;
|
||||
QVector<IdlData> trColumns;
|
||||
for (const CodeGen::TopicTypeInformation& tti : dataTopics)
|
||||
{
|
||||
if (!tti.isDataObject)
|
||||
continue;
|
||||
|
||||
keyColumns.clear();
|
||||
trColumns.clear();
|
||||
const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(tti.name);
|
||||
if (!ctti.isValid())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "could not find informations for struct " << tti.name;
|
||||
continue;
|
||||
}
|
||||
DataTopicWrapper* dtw = dataTopicsWrapper->addDataTopic(ctti);
|
||||
getSpecialColumns(ctti, keyColumns, trColumns);
|
||||
if (keyColumns.size() == 1)
|
||||
dtw->setKeyColumnData(keyColumns.first());
|
||||
dtw->setTrColumns(trColumns);
|
||||
if (ctti.recordTopic)
|
||||
{
|
||||
const CompleteTableInformation cti(m_structBuilder->getRecordingTopicTable(ctti.name));
|
||||
if (!cti.isEmpty())
|
||||
dtw->setRecordTableInformation(cti);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctti.isDataObject)
|
||||
m_structBuilder->buildJSonStructureInformationFromIdlData(ctti);
|
||||
}
|
||||
}
|
||||
m_ctx->insert(QStringLiteral("dataTopics"), dataTopicsWrapper);
|
||||
|
||||
QSet<QString> exportedSubTopics;
|
||||
DataTopicsWrapper* subTopicsWrapper = new DataTopicsWrapper(QString(), this);
|
||||
const QVector<QSharedPointer<DataTypeInfo>> serializeTopics = m_structBuilder->getSerializeFromToJsonTopics();
|
||||
for (const QSharedPointer<DataTypeInfo>& dti : serializeTopics)
|
||||
{
|
||||
if (exportedSubTopics.contains(dti->idlType()))
|
||||
continue;
|
||||
if (dti->isStructure())
|
||||
{
|
||||
const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(dti->idlType());
|
||||
if (!ctti.isValid())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "could not find informations for struct " << dti->idlType();
|
||||
continue;
|
||||
}
|
||||
const CompleteTableInformation cti(m_structBuilder->getRecordingTopicTable(ctti.name));
|
||||
if (cti.isEmpty())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "could not find informations for struct " << dti->idlType();
|
||||
continue;
|
||||
}
|
||||
DEBUG_LOG(m_logger) << "Adding substruct topic " << ctti.name;
|
||||
DataTopicWrapper* dtw = subTopicsWrapper->addDataTopic(ctti);
|
||||
dtw->setRecordTableInformation(cti);
|
||||
exportedSubTopics += dti->idlType();
|
||||
}
|
||||
}
|
||||
m_ctx->insert(QStringLiteral("serializeTopics"), subTopicsWrapper);
|
||||
|
||||
QVector<MemberInformationListWrapper*> jsonTopics;
|
||||
const QMap<QString, QVector<MemberInformation>> jsonExport = m_structBuilder->jsonStructureInformation();
|
||||
for (auto it = jsonExport.begin(); it != jsonExport.end(); ++it)
|
||||
{
|
||||
MemberInformationListWrapper* mil = new MemberInformationListWrapper(it.key(), it.value(), this);
|
||||
jsonTopics += mil;
|
||||
}
|
||||
m_ctx->insert(QStringLiteral("jsonTopics"), QVariant::fromValue(jsonTopics));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::getSpecialColumns(const CompleteTopicTypeInformation& tti, QVector<IdlData>& keyColumns,
|
||||
QVector<IdlData>& trColumns) const
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Get special columns for " << tti.name << " - base is " << tti.baseValueType;
|
||||
if (!tti.isValid())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "tti is not valid ...";
|
||||
return false;
|
||||
}
|
||||
for (const IdlData& ci : tti.members)
|
||||
{
|
||||
if (ci.isKey)
|
||||
keyColumns += ci;
|
||||
if (ci.isTranslatable)
|
||||
trColumns += ci;
|
||||
}
|
||||
if (tti.baseValueType.isEmpty())
|
||||
return true;
|
||||
return getSpecialColumns(topicTypeInformationByDerivedTopic(tti), keyColumns, trColumns);
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::initFunctionTopics()
|
||||
{
|
||||
|
||||
const QVector<FunctionTopic> functionTopics = m_reader->getFunctionTopics();
|
||||
FunctionTopicsWrapper* functionTopicsWrapper(new FunctionTopicsWrapper(QStringLiteral("functionTopics"), this));
|
||||
for (const FunctionTopic& ft : functionTopics)
|
||||
{
|
||||
if (!handleStruct(ft, functionTopicsWrapper))
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ctx->insert(QStringLiteral("functionTopics"), functionTopicsWrapper);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::handleStruct(const FunctionTopic& ft, FunctionTopicsWrapper* functionTopicsWrapper)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "handling FunctionTopic: " << ft.name;
|
||||
|
||||
const CompleteTopicTypeInformation siRequest =
|
||||
m_structBuilder->topicTypeInformationByStructType(ft.requestTopicType);
|
||||
if (!siRequest.isValid())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not get topic informations about request topic " << ft.requestTopicName;
|
||||
return false;
|
||||
}
|
||||
|
||||
const CompleteTopicTypeInformation siReply = m_structBuilder->topicTypeInformationByStructType(ft.replyTopicType);
|
||||
if (!siReply.isValid())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not get topic informations about reply topic " << ft.replyTopicType;
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionTopicWrapper* ftw = functionTopicsWrapper->addFunctionTopic(ft, siRequest, siReply);
|
||||
const QByteArray hash =
|
||||
QCryptographicHash::hash(ft.requestTopicName.toUtf8(), QCryptographicHash::Sha3_512).toHex().left(16);
|
||||
ftw->setReplyHandlerHash(QStringLiteral("0x") + QString::fromLatin1(hash) + QStringLiteral("LL"));
|
||||
if (!addRequesterRequestParams(ft.name, siRequest, ftw) || !addRequesterReplyParams(ft.name, siReply, ftw))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::addRequesterRequestParams(const QString& request_name,
|
||||
const CompleteTopicTypeInformation& request,
|
||||
FunctionTopicWrapper* ftw)
|
||||
{
|
||||
if (request.hasBaseValue())
|
||||
{
|
||||
const CompleteTopicTypeInformation siBase =
|
||||
m_structBuilder->topicTypeInformationByStructType(request.baseValueType);
|
||||
if (!siBase.isValid())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not get topic informations about base request topic "
|
||||
<< request.baseValueType;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addRequesterRequestParams(request_name, siBase, ftw))
|
||||
return false;
|
||||
}
|
||||
ftw->addRequestParams(request.members);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::addRequesterReplyParams(const QString& request_name,
|
||||
const CompleteTopicTypeInformation& reply,
|
||||
FunctionTopicWrapper* ftw)
|
||||
{
|
||||
if (reply.hasBaseValue())
|
||||
{
|
||||
const CompleteTopicTypeInformation siBase =
|
||||
m_structBuilder->topicTypeInformationByStructType(reply.baseValueType);
|
||||
if (!siBase.isValid())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not get topic informations about base reply topic " << reply.baseValueType;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addRequesterReplyParams(request_name, siBase, ftw))
|
||||
return false;
|
||||
}
|
||||
ftw->addReplyParams(reply.members);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::handleIdlStruct(const CompleteTopicTypeInformation& csi, QSet<QString>& idlStructNames,
|
||||
QVector<IdlStructWrapper*>& idlStructs)
|
||||
{
|
||||
if (!csi.isValid())
|
||||
return true;
|
||||
|
||||
if (idlStructNames.contains(csi.name))
|
||||
return true;
|
||||
|
||||
if (csi.hasBaseValue())
|
||||
{
|
||||
const CompleteTopicTypeInformation ctti =
|
||||
m_structBuilder->topicTypeInformationByStructType(csi.baseValueType, false);
|
||||
if (ctti.isValid())
|
||||
{
|
||||
#ifdef RTI_HEADER_AVAILABLE
|
||||
if (ctti.extensibility == dds::core::xtypes::ExtensibilityKind_def::FINAL)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Base struct (" << ctti.name
|
||||
<< ") has final extensibility - this is not allowed";
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// RTI not available - skip extensibility check (assume EXTENSIBLE)
|
||||
Q_UNUSED(ctti.extensibility);
|
||||
#endif
|
||||
if (!idlStructNames.contains(csi.baseValueType))
|
||||
{
|
||||
if (!handleIdlStruct(ctti, idlStructNames, idlStructs))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int idx = csi.baseValueType.indexOf(QLatin1String("::"));
|
||||
if (idx < 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "idlStruct '" << csi.baseValueType << "' not found in idl-file";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const IdlData& idlData : csi.members)
|
||||
{
|
||||
QString idlType;
|
||||
DEBUG_LOG(m_logger) << "member: " << idlData.type << " - " << idlData.name << " "
|
||||
<< idlData.dataTypeInfo->isStructure() << " " << idlData.dataTypeInfo->isSequence();
|
||||
if (idlData.dataTypeInfo->isStructure())
|
||||
idlType = idlData.dataTypeInfo->idlType();
|
||||
else if (idlData.dataTypeInfo->isSequence())
|
||||
{
|
||||
if (idlData.dataTypeInfo->innerDataType()->isStructure())
|
||||
idlType = idlData.dataTypeInfo->innerDataType()->idlType();
|
||||
}
|
||||
if (idlType.isEmpty())
|
||||
continue;
|
||||
if (!handleIdlStruct(m_structBuilder->topicTypeInformationByStructType(idlType, false), idlStructNames,
|
||||
idlStructs))
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_LOG(m_logger) << "Current structName: " << csi.name;
|
||||
idlStructs += new IdlStructWrapper(csi, this);
|
||||
|
||||
idlStructNames.insert(csi.name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CppTopicGeneratorGrantlee::initIdlStructs()
|
||||
{
|
||||
|
||||
const QVector<CompleteTopicTypeInformation> csiData = m_structBuilder->topicTypeInformations();
|
||||
|
||||
QSet<QString> idlStructNames;
|
||||
QVector<IdlStructWrapper*> idlStructs;
|
||||
idlStructs.reserve(csiData.size());
|
||||
for (const CompleteTopicTypeInformation& csi : csiData)
|
||||
{
|
||||
if (!handleIdlStruct(csi, idlStructNames, idlStructs))
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ctx->insert(QStringLiteral("idl_structs"), QVariant::fromValue(idlStructs));
|
||||
|
||||
const QVector<EnumInformation> enumerations = m_structBuilder->enumInformations();
|
||||
QVector<IdlEnumWrapper*> idlEnumerations;
|
||||
idlEnumerations.reserve(enumerations.size());
|
||||
for (const EnumInformation& ei : enumerations)
|
||||
idlEnumerations += new IdlEnumWrapper(ei, this);
|
||||
m_ctx->insert(QStringLiteral("idl_enums"), QVariant::fromValue(idlEnumerations));
|
||||
|
||||
exportUsedServices(m_reader);
|
||||
return true;
|
||||
}
|
||||
|
||||
CompleteTopicTypeInformation
|
||||
CppTopicGeneratorGrantlee::topicTypeInformationByDerivedTopic(const CompleteTopicTypeInformation& tti) const
|
||||
{
|
||||
if (tti.baseValueType.isEmpty())
|
||||
return CompleteTopicTypeInformation();
|
||||
QString baseValueType = tti.baseValueType;
|
||||
|
||||
if (tti.name.contains(QLatin1String("::")) && !tti.baseValueType.contains(QLatin1String("::")))
|
||||
{
|
||||
const QStringList sl = tti.name.split(QStringLiteral("::"));
|
||||
baseValueType = sl.at(0) + QLatin1String("::") + tti.baseValueType;
|
||||
}
|
||||
DEBUG_LOG(m_logger) << "topicTypeInformationByStructName(" << baseValueType << ")";
|
||||
return m_structBuilder->topicTypeInformationByStructType(baseValueType);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include "GeneratorGrantleeBase.h"
|
||||
#include "GrantleeWrappers.h"
|
||||
#include "StructBuilder.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
class CppTopicGeneratorGrantlee final : public GeneratorGrantleeBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CppTopicGeneratorGrantlee();
|
||||
|
||||
bool init(const XmlReader* reader, StructBuilder* structBuilder) override;
|
||||
|
||||
private:
|
||||
bool initDataTopics();
|
||||
bool getSpecialColumns(const CompleteTopicTypeInformation& tti, QVector<IdlData>& keyColumns,
|
||||
QVector<IdlData>& trColumns) const;
|
||||
|
||||
bool initFunctionTopics();
|
||||
bool handleStruct(const FunctionTopic& ft, FunctionTopicsWrapper* functionTopicsWrapper);
|
||||
bool addRequesterRequestParams(const QString& request_name, const CompleteTopicTypeInformation& request,
|
||||
FunctionTopicWrapper* ftw);
|
||||
bool addRequesterReplyParams(const QString& request_name, const CompleteTopicTypeInformation& reply,
|
||||
FunctionTopicWrapper* ftw);
|
||||
|
||||
bool handleIdlStruct(const CompleteTopicTypeInformation& csi, QSet<QString>& idlStructNames,
|
||||
QVector<IdlStructWrapper*>& idlStructs);
|
||||
bool initIdlStructs();
|
||||
|
||||
CompleteTopicTypeInformation topicTypeInformationByDerivedTopic(const CompleteTopicTypeInformation& tti) const;
|
||||
|
||||
const XmlReader* m_reader = nullptr;
|
||||
};
|
||||
} // namespace CodeGen
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Generate cpp structs from xml
|
||||
* $Id: DaoGeneratorGrantlee.cpp 50446 2020-03-18 10:29:57Z ehrlicher $
|
||||
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/DaoGeneratorGrantlee.cpp $
|
||||
* $Author: ehrlicher $
|
||||
*/
|
||||
|
||||
#include "DaoGeneratorGrantlee.h"
|
||||
#include "StructBuilder.h"
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
#include <grantlee/context.h>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
DaoGeneratorGrantlee::DaoGeneratorGrantlee()
|
||||
: GeneratorGrantleeBase("CodeGen.DaoGeneratorGrantlee")
|
||||
{}
|
||||
|
||||
bool DaoGeneratorGrantlee::init(const XmlReader *reader,
|
||||
StructBuilder *structBuilder)
|
||||
{
|
||||
if (!GeneratorGrantleeBase::initInternal(reader, structBuilder))
|
||||
return false;
|
||||
|
||||
// Is Service exercise-independent?
|
||||
if (reader->isServiceExerciseIndependent())
|
||||
m_ctx->insert(QStringLiteral("isExerciseIndependent"), QLatin1String("1"));
|
||||
|
||||
bool bHasDbNotifications = false;
|
||||
QVector<DbStructWrapper *> vlServiceStructs;
|
||||
QVector<DbStructWrapper *> vlStaticDataStructs;
|
||||
const QVector<CompleteStructInformation> csiData = structBuilder->structInformations();
|
||||
vlServiceStructs.reserve(csiData.size());
|
||||
vlStaticDataStructs.reserve(csiData.size());
|
||||
for (const CompleteStructInformation &csi : csiData)
|
||||
{
|
||||
DbStructWrapper *dbStructWrapper(new DbStructWrapper(csi, this));
|
||||
|
||||
if (csi.hasStaticContent)
|
||||
vlStaticDataStructs += dbStructWrapper;
|
||||
else
|
||||
vlServiceStructs += dbStructWrapper;
|
||||
if (!csi.dbNotifications.isEmpty())
|
||||
bHasDbNotifications = true;
|
||||
}
|
||||
|
||||
if (structBuilder->needPoint3d())
|
||||
m_ctx->insert(QStringLiteral("need_point3d"), QLatin1String("1"));
|
||||
if (structBuilder->needPgArrayInclude())
|
||||
m_ctx->insert(QStringLiteral("need_pgarray"), QLatin1String("1"));
|
||||
m_ctx->insert(QStringLiteral("cpp_structs"), QVariant::fromValue(vlServiceStructs));
|
||||
m_ctx->insert(QStringLiteral("data_structs"), QVariant::fromValue(vlStaticDataStructs));
|
||||
if (bHasDbNotifications)
|
||||
m_ctx->insert(QStringLiteral("has_db_notifications"), QLatin1String("1"));
|
||||
|
||||
m_ctx->insert(QStringLiteral("library_target"), QStringLiteral("COMMON"));
|
||||
|
||||
exportUsedServices(reader);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Generate cpp structs from xml
|
||||
* $Id: DaoGeneratorGrantlee.h 50446 2020-03-18 10:29:57Z ehrlicher $
|
||||
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/DaoGeneratorGrantlee.h $
|
||||
* $Author: ehrlicher $
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GeneratorGrantleeBase.h"
|
||||
#include "StructBuilder.h"
|
||||
#include "GrantleeWrappers.h"
|
||||
#include "codegen_export.h"
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
struct CompleteStructInformation;
|
||||
class StructBuilder;
|
||||
class XmlReader;
|
||||
|
||||
|
||||
class DaoGeneratorGrantlee final : public GeneratorGrantleeBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DaoGeneratorGrantlee();
|
||||
|
||||
bool init(const XmlReader *reader, StructBuilder *structBuilder) override;
|
||||
};
|
||||
} // namespace CodeGen
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
#include "DatabaseGeneratorGrantlee.h"
|
||||
|
||||
#include "StructBuilder.h"
|
||||
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
#include <ktexttemplate/context.h>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
DatabaseGeneratorGrantlee::DatabaseGeneratorGrantlee(bool targetIsServer)
|
||||
: GeneratorGrantleeBase("CodeGen.CppStructGeneratorGrantlee"), m_targetIsServer(targetIsServer)
|
||||
{
|
||||
}
|
||||
|
||||
bool DatabaseGeneratorGrantlee::init(const XmlReader* reader, StructBuilder* structBuilder)
|
||||
{
|
||||
if (!GeneratorGrantleeBase::initInternal(reader, structBuilder))
|
||||
return false;
|
||||
|
||||
if (reader->isServiceExerciseIndependent())
|
||||
m_ctx->insert(QStringLiteral("isExerciseIndependent"), QLatin1String("1"));
|
||||
|
||||
bool bHasDbNotifications = false;
|
||||
QVector<DbStructWrapper*> vlServiceStructs;
|
||||
QVector<DbStructWrapper*> vlStaticDataStructs;
|
||||
const QVector<CompleteStructInformation> csiData = structBuilder->structInformations();
|
||||
vlServiceStructs.reserve(csiData.size());
|
||||
vlStaticDataStructs.reserve(csiData.size());
|
||||
for (const CompleteStructInformation& csi : csiData)
|
||||
{
|
||||
DbStructWrapper* dbStructWrapper(new DbStructWrapper(csi, this));
|
||||
|
||||
if (csi.hasStaticContent)
|
||||
vlStaticDataStructs += dbStructWrapper;
|
||||
else
|
||||
vlServiceStructs += dbStructWrapper;
|
||||
if (!csi.dbNotifications.isEmpty())
|
||||
bHasDbNotifications = true;
|
||||
}
|
||||
addCustomFunctions(reader->getCustomFunctions());
|
||||
|
||||
if (structBuilder->needPoint3d())
|
||||
m_ctx->insert(QStringLiteral("need_point3d"), QLatin1String("1"));
|
||||
if (structBuilder->needPgArrayInclude())
|
||||
m_ctx->insert(QStringLiteral("need_pgarray"), QLatin1String("1"));
|
||||
m_ctx->insert(QStringLiteral("cpp_structs"), QVariant::fromValue(vlServiceStructs));
|
||||
m_ctx->insert(QStringLiteral("data_structs"), QVariant::fromValue(vlStaticDataStructs));
|
||||
if (bHasDbNotifications)
|
||||
m_ctx->insert(QStringLiteral("has_db_notifications"), QLatin1String("1"));
|
||||
|
||||
m_ctx->insert(QStringLiteral("library_target"),
|
||||
m_targetIsServer ? QStringLiteral("SERVER") : QStringLiteral("COMMON"));
|
||||
|
||||
exportUsedServices(reader);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include "GeneratorGrantleeBase.h"
|
||||
#include "GrantleeWrappers.h"
|
||||
#include "StructBuilder.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
struct CompleteStructInformation;
|
||||
class StructBuilder;
|
||||
class XmlReader;
|
||||
|
||||
class DatabaseGeneratorGrantlee final : public GeneratorGrantleeBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DatabaseGeneratorGrantlee(bool targetIsServer);
|
||||
|
||||
bool init(const XmlReader* reader, StructBuilder* structBuilder) override;
|
||||
|
||||
private:
|
||||
bool m_targetIsServer;
|
||||
};
|
||||
} // namespace CodeGen
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
|
||||
#include "GeneratorGrantleeBase.h"
|
||||
|
||||
#include "GrantleeWrappers.h"
|
||||
#include "XmlReader.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
GeneratorGrantleeBase::GeneratorGrantleeBase(const char* loggerName)
|
||||
: m_engine(new KTextTemplate::Engine)
|
||||
, m_ctx(new KTextTemplate::Context)
|
||||
, m_structBuilder(nullptr)
|
||||
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName))
|
||||
{
|
||||
// Add KTextTemplate plugin path for loading default tags/filters
|
||||
// KTextTemplate plugins are in /usr/lib/x86_64-linux-gnu/qt6/plugins/kf6/ktexttemplate
|
||||
// QT_PLUGIN_PATH should point to the parent directory containing the category folders
|
||||
m_engine->addPluginPath(QStringLiteral("/usr/lib/x86_64-linux-gnu/qt6/plugins"));
|
||||
}
|
||||
|
||||
GeneratorGrantleeBase::~GeneratorGrantleeBase()
|
||||
{
|
||||
delete m_engine;
|
||||
delete m_ctx;
|
||||
}
|
||||
|
||||
bool GeneratorGrantleeBase::initInternal(const XmlReader* reader, StructBuilder* structBuilder)
|
||||
{
|
||||
m_structBuilder = structBuilder;
|
||||
|
||||
if (reader)
|
||||
{
|
||||
m_ctx->insert(QStringLiteral("namespace"), reader->getServiceNamespace());
|
||||
m_ctx->insert(QStringLiteral("namespace_abbr"), reader->getServiceAbbr().toLower());
|
||||
m_ctx->insert(QStringLiteral("namespace_abbr_upper"), reader->getServiceAbbr().toUpper());
|
||||
if (reader->getServiceNamespace() == QLatin1String("Core"))
|
||||
m_ctx->insert(QStringLiteral("include_prefix"),
|
||||
QStringLiteral("DDS/%1").arg(reader->getServiceNamespace()));
|
||||
else
|
||||
m_ctx->insert(QStringLiteral("include_prefix"), reader->getServiceNamespace());
|
||||
m_ctx->insert(QStringLiteral("custom_record_replay"), reader->hasCustomRecordReplayPlugin());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeneratorGrantleeBase::setTemplate(const QString& templateName, const QString& tmpl)
|
||||
{
|
||||
m_template = m_engine->newTemplate(tmpl, QStringLiteral("myTemplate"));
|
||||
if (m_template->error())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error parsing template '" << templateName << "': " << m_template->errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString GeneratorGrantleeBase::render() const
|
||||
{
|
||||
QString out = m_template->render(m_ctx);
|
||||
const QRegularExpression rx(QLatin1String(";\\n([ ]+\\n)*"));
|
||||
out = out.replace(rx, QStringLiteral(";\n"));
|
||||
return out;
|
||||
}
|
||||
|
||||
QString GeneratorGrantleeBase::errorString() const
|
||||
{
|
||||
return m_template->errorString();
|
||||
}
|
||||
|
||||
bool GeneratorGrantleeBase::generateData(const QString& templateName, const QString& outDirectory)
|
||||
{
|
||||
QString content;
|
||||
if (!readFile(templateName, content))
|
||||
return false;
|
||||
if (!setTemplate(templateName, content))
|
||||
return false;
|
||||
|
||||
content = render();
|
||||
|
||||
QString outFilename;
|
||||
if (!getFilenameFromData(content, outFilename))
|
||||
return false;
|
||||
|
||||
QFile f(outDirectory + QLatin1Char('/') + outFilename);
|
||||
if (!f.open(QIODevice::WriteOnly))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: " << f.errorString() << "("
|
||||
<< f.error() << ")";
|
||||
return false;
|
||||
}
|
||||
const QByteArray outData = content.toUtf8();
|
||||
f.write(outData);
|
||||
f.close();
|
||||
|
||||
DEBUG_LOG(m_logger) << "Written data to " << f.fileName();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeneratorGrantleeBase::readFile(const QString& filename, QString& data) const
|
||||
{
|
||||
QFile f(filename);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error opening file " << filename << " - error: " << f.errorString();
|
||||
return false;
|
||||
}
|
||||
data = QString::fromUtf8(f.readAll());
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeneratorGrantleeBase::getFilenameFromData(const QString& dataToParse, QString& filename) const
|
||||
{
|
||||
|
||||
const QString filenameMarker(QStringLiteral("// filename:"));
|
||||
int idx = dataToParse.indexOf(filenameMarker);
|
||||
if (idx < 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Could not find marker '" << filenameMarker << "' in data!";
|
||||
return false;
|
||||
}
|
||||
idx += filenameMarker.length() + 1;
|
||||
int endIdx = dataToParse.indexOf(QLatin1Char('\n'), idx);
|
||||
if (endIdx < 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Could not find end marker '\\n' in data!";
|
||||
return false;
|
||||
}
|
||||
filename = dataToParse.mid(idx, endIdx - idx).trimmed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GeneratorGrantleeBase::addCustomFunctions(const QVector<CustomServiceFunctionInformation>& customFunctions)
|
||||
{
|
||||
|
||||
QVariantList vlCustomDbFunctions;
|
||||
QVariantList vlCustomDtcFunctions;
|
||||
vlCustomDbFunctions.reserve(customFunctions.size());
|
||||
vlCustomDtcFunctions.reserve(customFunctions.size());
|
||||
for (const CustomServiceFunctionInformation& csfi : customFunctions)
|
||||
{
|
||||
const QString signature(csfi.returnValue + QLatin1Char(' ') + csfi.methodName + QLatin1Char('(') +
|
||||
csfi.signature + QLatin1Char(')') +
|
||||
(csfi.isConst ? QStringLiteral(" const") : QString()));
|
||||
CustomFunctionWrapper* customFunctionWrapper(new CustomFunctionWrapper(signature, this));
|
||||
switch (csfi.type)
|
||||
{
|
||||
case CustomServiceFunctionInformation::Type::Db:
|
||||
vlCustomDbFunctions += QVariant::fromValue(customFunctionWrapper);
|
||||
break;
|
||||
case CustomServiceFunctionInformation::Type::Dtc:
|
||||
vlCustomDtcFunctions += QVariant::fromValue(customFunctionWrapper);
|
||||
break;
|
||||
case CustomServiceFunctionInformation::Type::DbNotify:
|
||||
|
||||
WARNING_LOG(m_logger) << "Internal error - Trying to handle DbNotify in addCustomFunctions(): "
|
||||
<< csfi.methodName;
|
||||
delete customFunctionWrapper;
|
||||
break;
|
||||
}
|
||||
DEBUG_LOG(m_logger) << "Added custom db function " << csfi.methodName;
|
||||
}
|
||||
m_ctx->insert(QStringLiteral("custom_dtc_procs"), vlCustomDtcFunctions);
|
||||
m_ctx->insert(QStringLiteral("custom_db_procs"), vlCustomDbFunctions);
|
||||
}
|
||||
|
||||
void GeneratorGrantleeBase::exportUsedServices(const XmlReader* reader)
|
||||
{
|
||||
IdlServicesWrapper* idlServicesWrapper(new IdlServicesWrapper(QString(), this));
|
||||
const QMap<QString, bool> usedServices = reader->usedServices();
|
||||
for (auto it = usedServices.begin(); it != usedServices.end(); ++it)
|
||||
{
|
||||
|
||||
if (!it.value())
|
||||
continue;
|
||||
const QString& svc = it.key();
|
||||
const QString fullPath = reader->idlFileForService(svc);
|
||||
const int idx = fullPath.lastIndexOf(QLatin1Char('.'));
|
||||
if (idx < 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Could not parse file path: " << fullPath;
|
||||
continue;
|
||||
}
|
||||
const QString idlFile = fullPath.left(idx);
|
||||
if (idlFile.isEmpty())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not find idl file for service " << svc;
|
||||
continue;
|
||||
}
|
||||
idlServicesWrapper->addService(svc, idlFile);
|
||||
DEBUG_LOG(m_logger) << "Added service " << svc;
|
||||
}
|
||||
m_ctx->insert(QStringLiteral("ext_services"), idlServicesWrapper);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
|
||||
#ifndef GENERATORGRANTLEEBASE_H
|
||||
#define GENERATORGRANTLEEBASE_H
|
||||
|
||||
#include "XmlReader.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include <ktexttemplate/template.h>
|
||||
#include <ktexttemplate/context.h>
|
||||
#include <ktexttemplate/engine.h>
|
||||
|
||||
namespace NGL
|
||||
{
|
||||
class INGLLogger;
|
||||
}
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
class StructBuilder;
|
||||
|
||||
class GeneratorGrantleeBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual ~GeneratorGrantleeBase();
|
||||
virtual bool init(const XmlReader* reader, StructBuilder* structBuilder) = 0;
|
||||
bool setTemplate(const QString& templateName, const QString& tmpl);
|
||||
QString render() const;
|
||||
QString errorString() const;
|
||||
bool generateData(const QString& templateName, const QString& outDirectory);
|
||||
|
||||
protected:
|
||||
explicit GeneratorGrantleeBase(const char* loggerName);
|
||||
|
||||
bool initInternal(const XmlReader* reader, StructBuilder* structBuilder);
|
||||
bool readFile(const QString& filename, QString& data) const;
|
||||
bool getFilenameFromData(const QString& dataToParse, QString& filename) const;
|
||||
void addCustomFunctions(const QVector<CustomServiceFunctionInformation>& customFunctions);
|
||||
void exportUsedServices(const XmlReader* reader);
|
||||
|
||||
protected:
|
||||
KTextTemplate::Engine* m_engine;
|
||||
KTextTemplate::Context* m_ctx;
|
||||
KTextTemplate::Template m_template;
|
||||
StructBuilder* m_structBuilder;
|
||||
NGL::INGLLogger& m_logger;
|
||||
};
|
||||
} // namespace CodeGen
|
||||
|
||||
#endif // GENERATORGRANTLEEBASE_H
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef GRANTLEEPARSERSMOKE_H
|
||||
#define GRANTLEEPARSERSMOKE_H
|
||||
|
||||
/**
|
||||
* @brief GrantleeParserSmoke - Minimal header for test compilation
|
||||
*
|
||||
* This header exists solely to allow test_GrantleeParserSmoke.cpp to compile.
|
||||
* The actual Grantlee parser smoke test is implemented in
|
||||
* Lts/Common/CodeGen/tests/GrantleeParserSmoke.cpp as a standalone executable.
|
||||
*
|
||||
* This file was accidentally deleted and needs to be restored for BUILD_TESTING.
|
||||
*/
|
||||
|
||||
namespace GrantleeParserSmoke
|
||||
{
|
||||
// Placeholder namespace for test compilation
|
||||
// Actual implementation is in Lts/Common/CodeGen/tests/GrantleeParserSmoke.cpp
|
||||
}
|
||||
|
||||
#endif // GRANTLEEPARSERSMOKE_H
|
||||
@@ -0,0 +1,203 @@
|
||||
|
||||
|
||||
#include "GrantleeWrappers.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <utility>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
#ifdef RTI_HEADER_AVAILABLE
|
||||
QString IdlStructWrapper::extensibility2String(dds::core::xtypes::ExtensibilityKind extensiblity)
|
||||
{
|
||||
switch (extensiblity.underlying())
|
||||
{
|
||||
case dds::core::xtypes::ExtensibilityKind_def::type::FINAL:
|
||||
return QStringLiteral("FINAL_EXTENSIBILITY");
|
||||
case dds::core::xtypes::ExtensibilityKind_def::type::MUTABLE:
|
||||
return QStringLiteral("MUTABLE_EXTENSIBILITY");
|
||||
case dds::core::xtypes::ExtensibilityKind_def::type::EXTENSIBLE:
|
||||
return QStringLiteral("EXTENSIBLE_EXTENSIBILITY");
|
||||
}
|
||||
Q_ASSERT_X(false, "IdlStructWrapper::extensibility2String", "Unknown extensiblity kind");
|
||||
return QString();
|
||||
}
|
||||
#else
|
||||
QString IdlStructWrapper::extensibility2String(int /*extensiblity*/)
|
||||
{
|
||||
// RTI not available - return placeholder
|
||||
return QStringLiteral("EXTENSIBLE_EXTENSIBILITY");
|
||||
}
|
||||
#endif
|
||||
|
||||
QString DataTopicWrapper::typeFq() const
|
||||
{
|
||||
const QString& t = type();
|
||||
const QString& s = serviceName();
|
||||
if (s.isEmpty())
|
||||
return t;
|
||||
if (t.startsWith(s))
|
||||
return t;
|
||||
return s + QStringLiteral("::") + t;
|
||||
}
|
||||
|
||||
QString MemberInformationWrapper::ddsToQVariant() const
|
||||
{
|
||||
// rtiddsgen 7.7 generates plain public members - access them directly.
|
||||
return QStringLiteral("toQVariant(data.") + cppMemberName() + QStringLiteral(")");
|
||||
}
|
||||
|
||||
QString MemberInformationWrapper::qVariantToDds() const
|
||||
{
|
||||
const QString ddsType =
|
||||
isOptional() ? (QStringLiteral("dds::core::optional<") + cppDdsType() + QLatin1Char('>')) : (cppDdsType());
|
||||
|
||||
return QStringLiteral("data.") + cppMemberName() + QStringLiteral(" = fromQVariant<") + ddsType +
|
||||
QStringLiteral(">(q.value(i++))");
|
||||
}
|
||||
|
||||
QString MemberInformationWrapper::ddsToJsonElement() const
|
||||
{
|
||||
|
||||
if (cppMemberName().isEmpty())
|
||||
return QStringLiteral("obj[QStringLiteral(\"") + cppType() + QStringLiteral("\")] = element;");
|
||||
|
||||
// rtiddsgen 7.7 generates plain public members for every kind of
|
||||
// member (struct or not), so no getter call is emitted.
|
||||
const QString memberAccess = cppMemberName();
|
||||
if (!isOptional())
|
||||
return QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") + cppToJsonPre() +
|
||||
QStringLiteral("element.") + memberAccess + cppToJsonPost();
|
||||
|
||||
const QString optionalAccess = cppMemberName() + QStringLiteral(".has_value()");
|
||||
const QString getValue = cppMemberName();
|
||||
return QStringLiteral("if (element.") + optionalAccess + QStringLiteral(") ") +
|
||||
QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") + cppToJsonPre() +
|
||||
QStringLiteral("*element.") + getValue + cppToJsonPost();
|
||||
}
|
||||
|
||||
QString MemberInformationWrapper::jsonElementToDds() const
|
||||
{
|
||||
|
||||
if (cppMemberName().isEmpty())
|
||||
return QStringLiteral("element = ") + cppFromQvPre2() + QStringLiteral("objElement.value(QStringLiteral(\"") +
|
||||
cppType() + QStringLiteral("\"))") + cppFromJsonPost();
|
||||
|
||||
QString ret(QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"") + cppMemberName() +
|
||||
QStringLiteral("\"))); "));
|
||||
if (isOptional())
|
||||
ret += QStringLiteral("if (!val.isNull()) ");
|
||||
|
||||
// rtiddsgen 7.7 generates plain public members - assign instead of
|
||||
// calling a setter.
|
||||
ret += QStringLiteral("element.") + cppMemberName() + QStringLiteral(" = ") + cppFromJsonPre2() +
|
||||
QStringLiteral("val") + cppFromJsonPost();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString MemberInformationWrapper::dbToJsonElement() const
|
||||
{
|
||||
|
||||
if (cppMemberName().isEmpty())
|
||||
return QStringLiteral("obj[QStringLiteral(\"") + cppType() + QStringLiteral("\")] = element;");
|
||||
|
||||
if (isOptional() || cppType() == QStringLiteral("QDateTime"))
|
||||
{
|
||||
QString ret = QStringLiteral("if (") +
|
||||
((cppType() == QStringLiteral("QDateTime")) ? QString() : QStringLiteral("!")) +
|
||||
QStringLiteral("element.") + cppMemberName() +
|
||||
((cppType() == QStringLiteral("QDateTime")) ? QStringLiteral(".isValid()) ")
|
||||
: QStringLiteral(".isNull()) ")) +
|
||||
QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ");
|
||||
if (isOptional())
|
||||
return ret += cppFromQvPre2() + QStringLiteral("element.") + cppMemberName() + cppFromQvPost();
|
||||
}
|
||||
|
||||
return QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") + cppToJsonPre() +
|
||||
QStringLiteral("element.") + cppMemberName() + cppToJsonPost();
|
||||
}
|
||||
|
||||
QString MemberInformationWrapper::jsonElementToDb() const
|
||||
{
|
||||
|
||||
if (cppMemberName().isEmpty())
|
||||
return QStringLiteral("element = ") + cppFromQvPre2() + QStringLiteral("objElement.value(QStringLiteral(\"") +
|
||||
cppType() + QStringLiteral("\"))") + cppFromJsonPost();
|
||||
|
||||
QString ret(QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"") + cppMemberName() +
|
||||
QStringLiteral("\"))); "));
|
||||
if (isOptional())
|
||||
ret += QStringLiteral("if (!val.isUndefined()) ");
|
||||
|
||||
ret += QStringLiteral("element.") + cppMemberName() + QStringLiteral(" = ") + cppFromJsonPre2() +
|
||||
QStringLiteral("val") + cppFromJsonPost();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVariantList DbStructWrapper::insertStructures()
|
||||
{
|
||||
|
||||
QStringList tableNames;
|
||||
QMap<QString, QVector<MemberInformationWrapper*>> structures;
|
||||
QMap<QString, MemberInformationWrapper*> primaryKeys;
|
||||
QMap<QString, MemberInformationWrapper*> fkComplexTableKeys;
|
||||
|
||||
tableNames.reserve(5);
|
||||
for (const QVariant& v : std::as_const(m_member))
|
||||
{
|
||||
MemberInformationWrapper* mi = v.value<MemberInformationWrapper*>();
|
||||
const QString& tableName = mi->info().db.table;
|
||||
if (mi->info().db.usedForInsert)
|
||||
{
|
||||
auto& tnStructure = structures[tableName];
|
||||
tnStructure.append(mi);
|
||||
if (!tableNames.contains(tableName))
|
||||
tableNames.push_back(tableName);
|
||||
}
|
||||
if (mi->info().fk)
|
||||
fkComplexTableKeys[mi->info().fk->destination.tableName] = mi;
|
||||
if (mi->info().db.isPrimaryKey)
|
||||
{
|
||||
if (!primaryKeys.contains(tableName))
|
||||
primaryKeys[tableName] = mi;
|
||||
}
|
||||
if (mi->info().db.isAssignmentForPrimaryTablePk)
|
||||
fkComplexTableKeys[mi->info().db.table] = mi;
|
||||
}
|
||||
|
||||
QVariantList vl;
|
||||
vl.reserve(tableNames.size());
|
||||
for (int i = 0; i < tableNames.size(); ++i)
|
||||
{
|
||||
const QString& tn = tableNames.at(i);
|
||||
MemberInformationWrapper* serialMember = fkComplexTableKeys.value(tn);
|
||||
MemberInformationWrapper* baseId = nullptr;
|
||||
MemberInformationWrapper* primaryKey = primaryKeys.value(tn);
|
||||
if (i == 0 && tableNames.size() > 1)
|
||||
baseId = primaryKeys.value(tn);
|
||||
|
||||
if (tableNames.size() == 1)
|
||||
{
|
||||
baseId = nullptr;
|
||||
|
||||
if (!primaryKey)
|
||||
{
|
||||
NGL::INGLLogger& logger = NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("DbStructWrapper");
|
||||
WARNING_LOG(logger) << "Table " << tn << " has no primary key!";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (primaryKey->dbHasSequence())
|
||||
serialMember = primaryKey;
|
||||
}
|
||||
}
|
||||
|
||||
const QVector<MemberInformationWrapper*> members = structures.value(tn);
|
||||
TableWrapper* tw = new TableWrapper(tn, members, serialMember, baseId, primaryKey, this);
|
||||
vl += QVariant::fromValue(tw);
|
||||
}
|
||||
|
||||
return vl;
|
||||
}
|
||||
@@ -0,0 +1,932 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StructBuilder.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVariant>
|
||||
#include <utility>
|
||||
|
||||
#ifdef RTI_HEADER_AVAILABLE
|
||||
#include <dds/core/xtypes/EnumType.hpp>
|
||||
#endif
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
class NameWrapper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name)
|
||||
Q_PROPERTY(QString nameCc READ nameCc)
|
||||
Q_PROPERTY(QString nameFlc READ nameFlc)
|
||||
public:
|
||||
NameWrapper(const QString& name, QObject* parent) : QObject(parent), m_name(name) {}
|
||||
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString nameCc() const
|
||||
{
|
||||
return StructBuilder::camelCaseString(name(), true);
|
||||
}
|
||||
|
||||
QString nameFlc() const
|
||||
{
|
||||
return StructBuilder::firstCharLowercase(nameCc());
|
||||
}
|
||||
|
||||
private:
|
||||
const QString m_name;
|
||||
};
|
||||
|
||||
class VariantListWrapper : public NameWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantList member READ member)
|
||||
public:
|
||||
using NameWrapper::NameWrapper;
|
||||
|
||||
~VariantListWrapper()
|
||||
{
|
||||
for (const QVariant& v : std::as_const(m_member))
|
||||
{
|
||||
auto ptr = v.value<QObject*>();
|
||||
delete ptr;
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList member() const
|
||||
{
|
||||
return m_member;
|
||||
}
|
||||
|
||||
protected:
|
||||
QVariantList m_member;
|
||||
};
|
||||
|
||||
class MemberInformationWrapper final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString cppMemberName READ cppMemberName)
|
||||
Q_PROPERTY(QString cppMemberNameCc READ cppMemberNameCc)
|
||||
Q_PROPERTY(QString cppStructName READ cppStructName)
|
||||
Q_PROPERTY(QString cppType READ cppType)
|
||||
Q_PROPERTY(QString cppToQvPre READ cppToQvPre)
|
||||
Q_PROPERTY(QString cppToQvPost READ cppToQvPost)
|
||||
Q_PROPERTY(QString cppFromQvPre1 READ cppFromQvPre1)
|
||||
Q_PROPERTY(QString cppFromQvPre2 READ cppFromQvPre2)
|
||||
Q_PROPERTY(QString cppFromQvPost READ cppFromQvPost)
|
||||
Q_PROPERTY(QString cppDefault READ cppDefault)
|
||||
Q_PROPERTY(QString dbTableName READ dbTableName)
|
||||
Q_PROPERTY(QString dbColumnName READ dbColumnName)
|
||||
Q_PROPERTY(QString dbColumnNameView READ dbColumnNameView)
|
||||
Q_PROPERTY(QString ddsToQVariant READ ddsToQVariant)
|
||||
Q_PROPERTY(QString qVariantToDds READ qVariantToDds)
|
||||
Q_PROPERTY(QString ddsToJsonElement READ ddsToJsonElement)
|
||||
Q_PROPERTY(QString jsonElementToDds READ jsonElementToDds)
|
||||
Q_PROPERTY(QString dbToJsonElement READ dbToJsonElement)
|
||||
Q_PROPERTY(QString jsonElementToDb READ jsonElementToDb)
|
||||
Q_PROPERTY(bool dbHasSequence READ dbHasSequence)
|
||||
Q_PROPERTY(bool isOptional READ isOptional)
|
||||
Q_PROPERTY(bool isCppStruct READ isCppStruct)
|
||||
public:
|
||||
MemberInformationWrapper(const MemberInformation& mi, QObject* parent) : QObject(parent), m_mi(mi) {}
|
||||
|
||||
QString cppMemberName() const
|
||||
{
|
||||
return m_mi.cpp.memberName;
|
||||
}
|
||||
|
||||
QString cppMemberNameCc() const
|
||||
{
|
||||
return StructBuilder::camelCaseString(m_mi.cpp.memberName, true);
|
||||
}
|
||||
|
||||
QString cppStructName() const
|
||||
{
|
||||
return m_mi.cpp.structName;
|
||||
}
|
||||
|
||||
QString cppType() const
|
||||
{
|
||||
return m_mi.cpp.type;
|
||||
}
|
||||
|
||||
QString cppToQvPre() const
|
||||
{
|
||||
return m_mi.cpp.toVariantPre;
|
||||
}
|
||||
|
||||
QString cppToQvPost() const
|
||||
{
|
||||
return m_mi.cpp.toVariantPost;
|
||||
}
|
||||
|
||||
QString cppFromQvPre1() const
|
||||
{
|
||||
return m_mi.cpp.fromVariantPre1;
|
||||
}
|
||||
|
||||
QString cppFromQvPre2() const
|
||||
{
|
||||
return m_mi.cpp.fromVariantPre2;
|
||||
}
|
||||
|
||||
QString cppFromQvPost() const
|
||||
{
|
||||
return m_mi.cpp.fromVariantPost;
|
||||
}
|
||||
|
||||
QString cppDefault() const
|
||||
{
|
||||
return m_mi.cpp.defaultValue;
|
||||
}
|
||||
|
||||
QString dbTableName() const
|
||||
{
|
||||
return m_mi.db.table;
|
||||
}
|
||||
|
||||
QString dbColumnName() const
|
||||
{
|
||||
return m_mi.db.columnName;
|
||||
}
|
||||
|
||||
QString dbColumnNameView() const
|
||||
{
|
||||
return m_mi.db.getColumnNameView();
|
||||
}
|
||||
|
||||
QString cppDdsType() const
|
||||
{
|
||||
return m_mi.cpp.ddsType.isEmpty() ? cppType() : m_mi.cpp.ddsType;
|
||||
}
|
||||
|
||||
QString ddsToQVariant() const;
|
||||
QString qVariantToDds() const;
|
||||
QString ddsToJsonElement() const;
|
||||
QString jsonElementToDds() const;
|
||||
QString dbToJsonElement() const;
|
||||
QString jsonElementToDb() const;
|
||||
|
||||
bool dbHasSequence() const
|
||||
{
|
||||
return m_mi.db.hasSequence;
|
||||
}
|
||||
|
||||
bool isOptional() const
|
||||
{
|
||||
return m_mi.cpp.isOptional;
|
||||
}
|
||||
|
||||
bool isCppStruct() const
|
||||
{
|
||||
return m_mi.cpp.usedForCppStruct;
|
||||
}
|
||||
|
||||
QString cppToJsonPre() const
|
||||
{
|
||||
return m_mi.cpp.toJsonPre.isNull() ? m_mi.cpp.toVariantPre : m_mi.cpp.toJsonPre;
|
||||
}
|
||||
|
||||
QString cppToJsonPost() const
|
||||
{
|
||||
return m_mi.cpp.toJsonPost.isNull() ? m_mi.cpp.toVariantPost : m_mi.cpp.toJsonPost;
|
||||
}
|
||||
|
||||
QString cppFromJsonPre2() const
|
||||
{
|
||||
return m_mi.cpp.fromJsonPre2.isNull() ? m_mi.cpp.fromVariantPre2 : m_mi.cpp.fromJsonPre2;
|
||||
}
|
||||
|
||||
QString cppFromJsonPost() const
|
||||
{
|
||||
return m_mi.cpp.fromJsonPost.isNull() ? m_mi.cpp.fromVariantPost : m_mi.cpp.fromJsonPost;
|
||||
}
|
||||
|
||||
MemberInformation info() const
|
||||
{
|
||||
return m_mi;
|
||||
}
|
||||
|
||||
private:
|
||||
const MemberInformation m_mi;
|
||||
};
|
||||
|
||||
class MemberInformationListWrapper : public VariantListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantList cppMember READ cppMember)
|
||||
Q_PROPERTY(QVariantList jsonMember READ jsonMember)
|
||||
Q_PROPERTY(QVariantList fkMember READ fkMember)
|
||||
Q_PROPERTY(QVariantList selectRemoveMember READ selectRemoveMember)
|
||||
Q_PROPERTY(QVariantList viewMember READ viewMember)
|
||||
Q_PROPERTY(MemberInformationWrapper* ddsKey READ ddsKey)
|
||||
Q_PROPERTY(MemberInformationWrapper* primaryKey READ primaryKey)
|
||||
public:
|
||||
explicit MemberInformationListWrapper(const QString& name, const QVector<MemberInformation>& member,
|
||||
QObject* parent)
|
||||
: VariantListWrapper(name, parent)
|
||||
{
|
||||
for (const MemberInformation& mi : member)
|
||||
m_member += QVariant::fromValue(new MemberInformationWrapper(mi, this));
|
||||
}
|
||||
|
||||
QVariantList memberByFilter(const std::function<bool(const MemberInformation& mi)>& acceptFilter) const
|
||||
{
|
||||
QVariantList vl;
|
||||
vl.reserve(m_member.size());
|
||||
for (const QVariant& v : m_member)
|
||||
{
|
||||
MemberInformationWrapper* mi = v.value<MemberInformationWrapper*>();
|
||||
if (mi && acceptFilter(mi->info()))
|
||||
vl += v;
|
||||
}
|
||||
return vl;
|
||||
}
|
||||
|
||||
QVariantList cppMember() const
|
||||
{
|
||||
return memberByFilter([](const MemberInformation& mi) { return mi.cpp.usedForCppStruct; });
|
||||
}
|
||||
|
||||
QVariantList jsonMember() const
|
||||
{
|
||||
return memberByFilter([](const MemberInformation& mi) { return mi.cpp.usedForJson; });
|
||||
}
|
||||
|
||||
QVariantList fkMember() const
|
||||
{
|
||||
return memberByFilter([](const MemberInformation& mi) { return mi.db.isForeignKey; });
|
||||
}
|
||||
|
||||
QVariantList selectRemoveMember() const
|
||||
{
|
||||
return memberByFilter([](const MemberInformation& mi) { return mi.db.generateSelectRemove; });
|
||||
}
|
||||
|
||||
QVariantList viewMember() const
|
||||
{
|
||||
return memberByFilter([](const MemberInformation& mi) { return mi.db.usedForView; });
|
||||
}
|
||||
|
||||
MemberInformationWrapper* ddsKey() const
|
||||
{
|
||||
for (const QVariant& v : m_member)
|
||||
{
|
||||
MemberInformationWrapper* mi = v.value<MemberInformationWrapper*>();
|
||||
if (mi->info().isDdsKey)
|
||||
return mi;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MemberInformationWrapper* primaryKey() const
|
||||
{
|
||||
for (const QVariant& v : m_member)
|
||||
{
|
||||
MemberInformationWrapper* mi = v.value<MemberInformationWrapper*>();
|
||||
if (mi->info().db.isPrimaryKey)
|
||||
return mi;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class TableWrapper final : public NameWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVector<MemberInformationWrapper*> member READ member)
|
||||
|
||||
Q_PROPERTY(MemberInformationWrapper* serialMember READ serialMember)
|
||||
|
||||
Q_PROPERTY(MemberInformationWrapper* baseId READ baseId)
|
||||
|
||||
Q_PROPERTY(MemberInformationWrapper* primaryKey READ primaryKey)
|
||||
public:
|
||||
explicit TableWrapper(const QString& name, const QVector<MemberInformationWrapper*>& member,
|
||||
MemberInformationWrapper* serialMember, MemberInformationWrapper* baseId,
|
||||
MemberInformationWrapper* primaryKey, QObject* parent)
|
||||
: NameWrapper(name, parent), m_member(member), m_serialMember(serialMember), m_baseId(baseId),
|
||||
m_primaryKey(primaryKey)
|
||||
{
|
||||
}
|
||||
|
||||
QVector<MemberInformationWrapper*> member() const
|
||||
{
|
||||
return m_member;
|
||||
}
|
||||
|
||||
MemberInformationWrapper* serialMember() const
|
||||
{
|
||||
return m_serialMember;
|
||||
}
|
||||
|
||||
MemberInformationWrapper* baseId() const
|
||||
{
|
||||
return m_baseId;
|
||||
}
|
||||
|
||||
MemberInformationWrapper* primaryKey() const
|
||||
{
|
||||
return m_primaryKey;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<MemberInformationWrapper*> m_member;
|
||||
MemberInformationWrapper* m_serialMember;
|
||||
MemberInformationWrapper* m_baseId;
|
||||
MemberInformationWrapper* m_primaryKey;
|
||||
};
|
||||
|
||||
class DbStructWrapper final : public MemberInformationListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantList insertStructures READ insertStructures)
|
||||
Q_PROPERTY(QString viewTableName READ viewTableName)
|
||||
Q_PROPERTY(bool deleteWithoutPkAllowed READ deleteWithoutPkAllowed)
|
||||
Q_PROPERTY(MemberInformationWrapper* primaryKey READ primaryKey)
|
||||
Q_PROPERTY(QVariantList dbNotifications READ dbNotifications)
|
||||
|
||||
public:
|
||||
explicit DbStructWrapper(const CompleteStructInformation& csi, QObject* parent)
|
||||
: MemberInformationListWrapper(csi.structName, csi.member, parent), m_csi(csi)
|
||||
{
|
||||
m_dbNotifications.reserve(m_csi.dbNotifications.size());
|
||||
for (const auto& notification : std::as_const(m_csi.dbNotifications))
|
||||
m_dbNotifications += QVariant(notification.methodName);
|
||||
}
|
||||
|
||||
MemberInformationWrapper* primaryKey() const
|
||||
{
|
||||
for (const QVariant& v : m_member)
|
||||
{
|
||||
MemberInformationWrapper* mi = v.value<MemberInformationWrapper*>();
|
||||
if (mi->info().db.isPrimaryKey && mi->info().db.table == primaryTableName())
|
||||
return mi;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QVariantList insertStructures();
|
||||
|
||||
bool deleteWithoutPkAllowed() const
|
||||
{
|
||||
return m_csi.tables.size() == 1;
|
||||
}
|
||||
|
||||
QVariantList dbNotifications() const
|
||||
{
|
||||
return m_dbNotifications;
|
||||
}
|
||||
|
||||
QString primaryTableName() const
|
||||
{
|
||||
return m_csi.primaryTable->name;
|
||||
}
|
||||
|
||||
QString viewTableName() const
|
||||
{
|
||||
return (m_csi.tables.size() == 1) ? primaryTableName() : m_csi.viewName;
|
||||
}
|
||||
|
||||
private:
|
||||
const CompleteStructInformation m_csi;
|
||||
QVariantList m_dbNotifications;
|
||||
};
|
||||
|
||||
class CustomFunctionWrapper final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString signature READ signature)
|
||||
public:
|
||||
explicit CustomFunctionWrapper(const QString& signature, QObject* parent) : QObject(parent), m_signature(signature)
|
||||
{
|
||||
}
|
||||
|
||||
QString signature() const
|
||||
{
|
||||
return m_signature;
|
||||
}
|
||||
|
||||
private:
|
||||
const QString m_signature;
|
||||
};
|
||||
|
||||
class IdlEnumKeyValueWrapper final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString key READ key)
|
||||
Q_PROPERTY(int value READ value)
|
||||
public:
|
||||
explicit IdlEnumKeyValueWrapper(const EnumKeyValuePair& ekvp, QObject* parent) : QObject(parent), m_ekvp(ekvp) {}
|
||||
|
||||
QString key() const
|
||||
{
|
||||
return m_ekvp.key;
|
||||
}
|
||||
|
||||
int value() const
|
||||
{
|
||||
return m_ekvp.value;
|
||||
}
|
||||
|
||||
private:
|
||||
const EnumKeyValuePair m_ekvp;
|
||||
};
|
||||
|
||||
class IdlDataWrapper final : public NameWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString type READ type)
|
||||
Q_PROPERTY(QString comment READ comment)
|
||||
Q_PROPERTY(QString isKey READ isKey)
|
||||
Q_PROPERTY(QString isOptional READ isOptional)
|
||||
Q_PROPERTY(QString cppType READ cppType)
|
||||
Q_PROPERTY(QString cppTypeCr READ cppTypeCr)
|
||||
Q_PROPERTY(QString nameGetter READ nameGetter)
|
||||
public:
|
||||
IdlDataWrapper(const IdlData& data, QObject* parent) : NameWrapper(data.name, parent), m_data(data) {}
|
||||
|
||||
QString type() const
|
||||
{
|
||||
return m_data.dataTypeInfo->idlType();
|
||||
}
|
||||
|
||||
QString comment() const
|
||||
{
|
||||
return m_data.comment;
|
||||
}
|
||||
|
||||
QString isKey() const
|
||||
{
|
||||
return m_data.isKey ? QStringLiteral(" \t //@key") : QString();
|
||||
}
|
||||
|
||||
QString isOptional() const
|
||||
{
|
||||
return m_data.isOptional ? QStringLiteral(" \t //@Optional") : QString();
|
||||
}
|
||||
|
||||
QString cppType() const
|
||||
{
|
||||
return m_data.dataTypeInfo->cppType();
|
||||
}
|
||||
|
||||
QString cppTypeCr() const
|
||||
{
|
||||
return m_data.dataTypeInfo->cppTypeConstRef();
|
||||
}
|
||||
|
||||
QString nameGetter() const
|
||||
{
|
||||
// rtiddsgen 7.7 generates public data members (no getters); optional
|
||||
// members are omg::types::optional<T> and are unwrapped via .get().
|
||||
return m_data.name + (m_data.isOptional ? QStringLiteral(".get()") : QString());
|
||||
}
|
||||
|
||||
private:
|
||||
const IdlData m_data;
|
||||
};
|
||||
|
||||
class IdlEnumWrapper final : public VariantListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IdlEnumWrapper(const EnumInformation& ei, QObject* parent) : VariantListWrapper(ei.enumName, parent)
|
||||
{
|
||||
m_member.reserve(ei.values.size());
|
||||
for (const auto& val : ei.values)
|
||||
m_member += QVariant::fromValue(new IdlEnumKeyValueWrapper(val, this));
|
||||
}
|
||||
};
|
||||
|
||||
class IdlStructWrapper final : public VariantListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString comment READ comment)
|
||||
Q_PROPERTY(QString baseValueType READ baseValueType)
|
||||
Q_PROPERTY(QString extensibility READ extensibility)
|
||||
public:
|
||||
explicit IdlStructWrapper(const CompleteTopicTypeInformation& ctti, QObject* parent)
|
||||
: VariantListWrapper(ctti.name, parent), m_ctti(ctti)
|
||||
{
|
||||
m_member.reserve(m_ctti.members.size());
|
||||
for (const auto& val : std::as_const(m_ctti.members))
|
||||
m_member += QVariant::fromValue(new IdlDataWrapper(val, this));
|
||||
}
|
||||
|
||||
QString comment() const
|
||||
{
|
||||
return m_ctti.comment;
|
||||
}
|
||||
|
||||
QString baseValueType() const
|
||||
{
|
||||
return m_ctti.baseValueType;
|
||||
}
|
||||
|
||||
QString extensibility() const
|
||||
{
|
||||
return extensibility2String(m_ctti.extensibility);
|
||||
}
|
||||
|
||||
private:
|
||||
const CompleteTopicTypeInformation m_ctti;
|
||||
#ifdef RTI_HEADER_AVAILABLE
|
||||
static QString extensibility2String(dds::core::xtypes::ExtensibilityKind extensiblity);
|
||||
#else
|
||||
static QString extensibility2String(int extensiblity);
|
||||
#endif
|
||||
};
|
||||
|
||||
class IdlServiceWrapper final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString svcName READ svcName)
|
||||
Q_PROPERTY(QString baseFilePath READ baseFilePath)
|
||||
public:
|
||||
IdlServiceWrapper(const QString& svcName, const QString& baseFilePath)
|
||||
: m_svcName(svcName), m_baseFilePath(baseFilePath)
|
||||
{
|
||||
}
|
||||
|
||||
QString svcName() const
|
||||
{
|
||||
return m_svcName;
|
||||
}
|
||||
|
||||
QString baseFilePath() const
|
||||
{
|
||||
return m_baseFilePath;
|
||||
}
|
||||
|
||||
private:
|
||||
const QString m_svcName;
|
||||
const QString m_baseFilePath;
|
||||
};
|
||||
|
||||
class FunctionTopicWrapper final : public NameWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString onReply READ onReply)
|
||||
Q_PROPERTY(QString onRequest READ onRequest)
|
||||
Q_PROPERTY(QString onError READ onError)
|
||||
Q_PROPERTY(bool isCoreFt READ isCoreFt)
|
||||
Q_PROPERTY(bool routeInReplay READ routeInReplay)
|
||||
|
||||
Q_PROPERTY(QString reply READ reply)
|
||||
Q_PROPERTY(QString replyFlc READ replyFlc)
|
||||
Q_PROPERTY(QString replyHandler READ replyHandler)
|
||||
Q_PROPERTY(QString replyHandlerFlc READ replyHandlerFlc)
|
||||
Q_PROPERTY(QString replyType READ replyType)
|
||||
Q_PROPERTY(QString replyTypeFro READ replyTypeFro)
|
||||
Q_PROPERTY(QVariantList replyParams READ replyParams)
|
||||
Q_PROPERTY(QString replyHandlerHash READ replyHandlerHash WRITE setReplyHandlerHash)
|
||||
|
||||
Q_PROPERTY(QString request READ request)
|
||||
Q_PROPERTY(QString requestFlc READ requestFlc)
|
||||
Q_PROPERTY(QString requestHandler READ requestHandler)
|
||||
Q_PROPERTY(QString requestHandlerFlc READ requestHandlerFlc)
|
||||
Q_PROPERTY(QString requestType READ requestType)
|
||||
Q_PROPERTY(QVariantList requestParams READ requestParams)
|
||||
|
||||
public:
|
||||
FunctionTopicWrapper(const FunctionTopic& ft, const CompleteTopicTypeInformation& siRequest,
|
||||
const CompleteTopicTypeInformation& siReply, QObject* parent)
|
||||
: NameWrapper(ft.name, parent), m_ft(ft), m_request(siRequest), m_reply(siReply)
|
||||
{
|
||||
}
|
||||
|
||||
QString onReply() const
|
||||
{
|
||||
return QLatin1String("on") + m_ft.replyTopicName;
|
||||
}
|
||||
|
||||
QString onRequest() const
|
||||
{
|
||||
return QLatin1String("on") + m_ft.requestTopicName;
|
||||
}
|
||||
|
||||
QString onError() const
|
||||
{
|
||||
return QLatin1String("on") + m_ft.name + QLatin1String("Error");
|
||||
}
|
||||
|
||||
bool isCoreFt() const
|
||||
{
|
||||
return (m_ft.replyTopicType == QStringLiteral("Core::FunctionReply") ||
|
||||
m_ft.replyTopicType == QStringLiteral("FunctionReply"));
|
||||
}
|
||||
|
||||
bool routeInReplay() const
|
||||
{
|
||||
return m_ft.routeInReplay;
|
||||
}
|
||||
|
||||
QString reply() const
|
||||
{
|
||||
return m_ft.replyTopicName;
|
||||
}
|
||||
|
||||
QString replyFlc() const
|
||||
{
|
||||
return StructBuilder::firstCharLowercase(reply());
|
||||
}
|
||||
|
||||
QString replyHandler() const
|
||||
{
|
||||
return m_ft.replyTopicName + QLatin1String("Handler");
|
||||
}
|
||||
|
||||
QString replyHandlerFlc() const
|
||||
{
|
||||
return StructBuilder::firstCharLowercase(replyHandler());
|
||||
}
|
||||
|
||||
QString replyType() const
|
||||
{
|
||||
return m_reply.name;
|
||||
}
|
||||
|
||||
QString replyTypeFro() const
|
||||
{
|
||||
return isCoreFt() ? QStringLiteral("Core::FunctionReplyObject") : m_reply.name;
|
||||
}
|
||||
|
||||
QString request() const
|
||||
{
|
||||
return m_ft.requestTopicName;
|
||||
}
|
||||
|
||||
QString requestFlc() const
|
||||
{
|
||||
return StructBuilder::firstCharLowercase(request());
|
||||
}
|
||||
|
||||
QString requestHandler() const
|
||||
{
|
||||
return m_ft.requestTopicName + QLatin1String("Handler");
|
||||
}
|
||||
|
||||
QString requestHandlerFlc() const
|
||||
{
|
||||
return StructBuilder::firstCharLowercase(requestHandler());
|
||||
}
|
||||
|
||||
QString requestType() const
|
||||
{
|
||||
return m_request.name;
|
||||
}
|
||||
|
||||
void addRequestParams(const QVector<IdlData>& params)
|
||||
{
|
||||
for (const auto& param : params)
|
||||
m_requestParams.push_back(QVariant::fromValue(new IdlDataWrapper(param, this)));
|
||||
}
|
||||
|
||||
QVariantList requestParams() const
|
||||
{
|
||||
return m_requestParams;
|
||||
}
|
||||
|
||||
void addReplyParams(const QVector<IdlData>& params)
|
||||
{
|
||||
for (const auto& param : params)
|
||||
m_replyParams.push_back(QVariant::fromValue(new IdlDataWrapper(param, this)));
|
||||
}
|
||||
|
||||
QVariantList replyParams() const
|
||||
{
|
||||
return m_replyParams;
|
||||
}
|
||||
|
||||
QString replyHandlerHash() const
|
||||
{
|
||||
return m_replyHandlerHash;
|
||||
}
|
||||
|
||||
void setReplyHandlerHash(const QString& hash)
|
||||
{
|
||||
m_replyHandlerHash = hash;
|
||||
}
|
||||
|
||||
private:
|
||||
const FunctionTopic m_ft;
|
||||
const CompleteTopicTypeInformation m_request;
|
||||
const CompleteTopicTypeInformation m_reply;
|
||||
QVariantList m_requestParams;
|
||||
QVariantList m_replyParams;
|
||||
QString m_replyHandlerHash;
|
||||
};
|
||||
|
||||
class FunctionTopicsWrapper final : public VariantListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_PROPERTY(QVariantList topicsToRouteInReplay READ topicsToRouteInReplay)
|
||||
|
||||
using VariantListWrapper::VariantListWrapper;
|
||||
|
||||
FunctionTopicWrapper* addFunctionTopic(const FunctionTopic& ft, const CompleteTopicTypeInformation& siRequest,
|
||||
const CompleteTopicTypeInformation& siReply)
|
||||
{
|
||||
FunctionTopicWrapper* ret = new FunctionTopicWrapper(ft, siRequest, siReply, this);
|
||||
m_member += QVariant::fromValue(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVariantList topicsToRouteInReplay() const
|
||||
{
|
||||
QVariantList vl;
|
||||
vl.reserve(m_member.size());
|
||||
for (const QVariant& v : m_member)
|
||||
{
|
||||
FunctionTopicWrapper* ft = v.value<FunctionTopicWrapper*>();
|
||||
if (ft->routeInReplay())
|
||||
vl += v;
|
||||
}
|
||||
return vl;
|
||||
}
|
||||
};
|
||||
|
||||
class DataTopicWrapper final : public NameWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString serviceName READ serviceName)
|
||||
Q_PROPERTY(QString type READ type)
|
||||
Q_PROPERTY(QString typeFq READ typeFq)
|
||||
Q_PROPERTY(QVariant key READ key)
|
||||
Q_PROPERTY(bool dtcImpl READ dtcImpl)
|
||||
Q_PROPERTY(bool dtsImpl READ dtsImpl)
|
||||
Q_PROPERTY(bool recordTopic READ recordTopic)
|
||||
Q_PROPERTY(QVariant recTable READ recTable)
|
||||
Q_PROPERTY(QVariantList trColumns READ trColumns)
|
||||
Q_PROPERTY(bool hasTrColumns READ hasTrColumns)
|
||||
Q_PROPERTY(bool routeToClient READ routeToClient)
|
||||
Q_PROPERTY(bool routeToServer READ routeToServer)
|
||||
Q_PROPERTY(bool routeInReplay READ routeInReplay)
|
||||
Q_PROPERTY(bool autosubscribe READ isAutoSubscribe)
|
||||
|
||||
public:
|
||||
DataTopicWrapper(const CompleteTopicTypeInformation& ctti, QObject* parent)
|
||||
: NameWrapper(ctti.name, parent), m_ctti(ctti), m_keyData(nullptr), m_recordData(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void setKeyColumnData(const IdlData& data)
|
||||
{
|
||||
delete m_keyData;
|
||||
m_keyData = new IdlDataWrapper(data, this);
|
||||
}
|
||||
|
||||
void setTrColumns(const QVector<IdlData>& params)
|
||||
{
|
||||
for (const auto& param : params)
|
||||
m_trColumns.push_back(QVariant::fromValue(new IdlDataWrapper(param, this)));
|
||||
}
|
||||
|
||||
QVariantList trColumns() const
|
||||
{
|
||||
return m_trColumns;
|
||||
}
|
||||
|
||||
bool hasTrColumns() const
|
||||
{
|
||||
return !m_trColumns.empty();
|
||||
}
|
||||
|
||||
void setRecordTableInformation(const CompleteTableInformation& cti)
|
||||
{
|
||||
delete m_recordData;
|
||||
m_recordData = new MemberInformationListWrapper(cti.name, cti.columns, this);
|
||||
m_ctti.recordTopic = true;
|
||||
}
|
||||
|
||||
QString serviceName() const
|
||||
{
|
||||
return m_ctti.serviceName;
|
||||
}
|
||||
|
||||
QString type() const
|
||||
{
|
||||
return m_ctti.name;
|
||||
}
|
||||
|
||||
QString typeFq() const;
|
||||
|
||||
QVariant key() const
|
||||
{
|
||||
return m_keyData ? QVariant::fromValue(m_keyData) : QVariant();
|
||||
}
|
||||
|
||||
bool dtsImpl() const
|
||||
{
|
||||
return m_ctti.dtsAutogen;
|
||||
}
|
||||
|
||||
bool dtcImpl() const
|
||||
{
|
||||
return m_ctti.dtcAutogen;
|
||||
}
|
||||
|
||||
bool recordTopic() const
|
||||
{
|
||||
return m_ctti.recordTopic && m_recordData;
|
||||
}
|
||||
|
||||
bool routeToClient() const
|
||||
{
|
||||
return m_ctti.routeToClient;
|
||||
}
|
||||
|
||||
bool routeToServer() const
|
||||
{
|
||||
return m_ctti.routeToServer;
|
||||
}
|
||||
|
||||
bool routeInReplay() const
|
||||
{
|
||||
return m_ctti.routeInReplay;
|
||||
}
|
||||
|
||||
bool isAutoSubscribe() const
|
||||
{
|
||||
return m_ctti.autosubscribe;
|
||||
}
|
||||
|
||||
QVariant recTable() const
|
||||
{
|
||||
return recordTopic() ? QVariant::fromValue(m_recordData) : QVariant();
|
||||
}
|
||||
|
||||
private:
|
||||
CompleteTopicTypeInformation m_ctti;
|
||||
IdlDataWrapper* m_keyData;
|
||||
MemberInformationListWrapper* m_recordData;
|
||||
QVariantList m_trColumns;
|
||||
};
|
||||
|
||||
class DataTopicsWrapper final : public VariantListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_PROPERTY(QVariantList topicsToRouteInReplay READ topicsToRouteInReplay)
|
||||
Q_PROPERTY(QVariantList topicsToRouteToClient READ topicsToRouteToClient)
|
||||
Q_PROPERTY(QVariantList topicsToRouteToServer READ topicsToRouteToServer)
|
||||
|
||||
using VariantListWrapper::VariantListWrapper;
|
||||
|
||||
DataTopicWrapper* addDataTopic(const CompleteTopicTypeInformation& ctti)
|
||||
{
|
||||
DataTopicWrapper* ret = new DataTopicWrapper(ctti, this);
|
||||
m_member += QVariant::fromValue(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVariantList memberByFilter(const std::function<bool(const DataTopicWrapper* dtw)>& acceptFilter) const
|
||||
{
|
||||
QVariantList vl;
|
||||
vl.reserve(m_member.size());
|
||||
for (const QVariant& v : m_member)
|
||||
{
|
||||
DataTopicWrapper* dtw = v.value<DataTopicWrapper*>();
|
||||
if (dtw && acceptFilter(dtw))
|
||||
vl += v;
|
||||
}
|
||||
return vl;
|
||||
}
|
||||
|
||||
QVariantList topicsToRouteInReplay() const
|
||||
{
|
||||
return memberByFilter([](const DataTopicWrapper* dtw) { return dtw->routeInReplay(); });
|
||||
}
|
||||
|
||||
QVariantList topicsToRouteToClient() const
|
||||
{
|
||||
return memberByFilter([](const DataTopicWrapper* dtw) { return dtw->routeToClient(); });
|
||||
}
|
||||
|
||||
QVariantList topicsToRouteToServer() const
|
||||
{
|
||||
return memberByFilter([](const DataTopicWrapper* dtw) { return dtw->routeToServer(); });
|
||||
}
|
||||
};
|
||||
|
||||
class IdlServicesWrapper final : public VariantListWrapper
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using VariantListWrapper::VariantListWrapper;
|
||||
|
||||
void addService(const QString& svcName, const QString& baseFilePath)
|
||||
{
|
||||
m_member += QVariant::fromValue(new IdlServiceWrapper(svcName, baseFilePath));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
@@ -0,0 +1,37 @@
|
||||
#
|
||||
# Hinweise für den CodeGen
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
Erstellt: Mai 2022, Norbert Thoden
|
||||
|
||||
Der Aufruf des CodeGen kann z.B. so aussehen:
|
||||
|
||||
codegen -s /tmp/ppm_serviceORG.xml -i /shd/CTC/TOOLS/TDS/TDS-build/service_idl_files.txt -a dds_topics -o /tmp /shd/CTC/TOOLS/TDS/TDS-source/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee
|
||||
Edit Juni 2023, Niklas Schmidt
|
||||
|
||||
Ebenso ist es nicht möglich einen Struct aus einem anderen Service(Service B) zu verwenden,
|
||||
der in sich widerum einen anderen Struct aus dem anderen Service (Service B) beinhaltet.
|
||||
|
||||
Bsp:
|
||||
|
||||
Service A = Struct1{ int id,
|
||||
Struct2 test
|
||||
}
|
||||
|
||||
Service B = Struct2{
|
||||
int id2,
|
||||
Struct3 test
|
||||
}
|
||||
|
||||
Service B = Struct3{
|
||||
std::string test
|
||||
}
|
||||
|
||||
Mögliche Diagnose:
|
||||
|
||||
Wenn Struct3 aus Service A aufgerufen wird, ist wahrscheinlich nicht mehr gekennzeichnet dass der Struct3 aus Service B kommt. Dieser kann somit nicht
|
||||
aufgelöst werden
|
||||
@@ -0,0 +1,892 @@
|
||||
|
||||
|
||||
#include "SqlGenerator.h"
|
||||
|
||||
#include "StructBuilder.h"
|
||||
#include "XmlReader.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QTextStream>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
static QString getExerciseSchemaName(int exerciseId)
|
||||
{
|
||||
return QStringLiteral("ex_") + QString::number(exerciseId);
|
||||
}
|
||||
|
||||
SqlGenerator::SqlGenerator(XmlReader* xmlReader, StructBuilder* structBuilder)
|
||||
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator")),
|
||||
m_xmlReader(xmlReader), m_structBuilder(structBuilder), m_createdByXmlFile(false), m_isValid(true),
|
||||
m_exerciseId(0)
|
||||
{
|
||||
}
|
||||
|
||||
SqlGenerator::SqlGenerator(const QString& xmlFile)
|
||||
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator")),
|
||||
m_xmlReader(new XmlReader), m_structBuilder(new StructBuilder(m_xmlReader)), m_createdByXmlFile(true),
|
||||
m_isValid(false), m_exerciseId(0)
|
||||
{
|
||||
m_isValid = m_xmlReader->readServiceFile(xmlFile, QString()) && m_structBuilder->buildStructures();
|
||||
}
|
||||
|
||||
SqlGenerator::~SqlGenerator()
|
||||
{
|
||||
if (m_createdByXmlFile)
|
||||
{
|
||||
delete m_xmlReader;
|
||||
delete m_structBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
bool SqlGenerator::generateData(int exerciseId)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Generating SQL data for service " << getServiceAbbr() << " in exercise " << exerciseId
|
||||
<< ", this: " << this;
|
||||
|
||||
m_exerciseId = exerciseId;
|
||||
m_sqlGlobal = SqlStatements();
|
||||
m_sqlExercise = SqlStatements();
|
||||
m_sqlItemsGlobal = SqlItems();
|
||||
m_sqlItemsExercise = SqlItems();
|
||||
m_referencedSevices.clear();
|
||||
|
||||
if (!handleTables())
|
||||
return false;
|
||||
|
||||
if (!handleViews())
|
||||
return false;
|
||||
|
||||
if (!handleEnumerations())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqlGenerator::createStatementsForTable(const QString& tableName, const QString& tableNameOut)
|
||||
{
|
||||
const QMap<QString, CompleteTableInformation> tables = m_structBuilder->getDbTables();
|
||||
for (const CompleteTableInformation& cti : tables)
|
||||
{
|
||||
if (cti.name != tableName)
|
||||
continue;
|
||||
CompleteTableInformation ctiOut = cti;
|
||||
ctiOut.name = tableNameOut;
|
||||
return handleTable(ctiOut);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SqlGenerator::handleTables()
|
||||
{
|
||||
const QMap<QString, CompleteTableInformation> tables = m_structBuilder->getDbTables();
|
||||
for (const CompleteTableInformation& cti : tables)
|
||||
{
|
||||
if (!handleTable(cti))
|
||||
return false;
|
||||
}
|
||||
|
||||
const QMap<QString, CompleteTableInformation> recTables = m_structBuilder->getRecordingTopicTables();
|
||||
MemberInformation miColumnIsValid;
|
||||
miColumnIsValid.db.columnName = QStringLiteral("dds_is_alive");
|
||||
miColumnIsValid.db.datatype = QStringLiteral("boolean");
|
||||
miColumnIsValid.db.defaultValue = QStringLiteral("true");
|
||||
MemberInformation miInsertionTime;
|
||||
miInsertionTime.cpp.type = QStringLiteral("timestamp");
|
||||
miInsertionTime.db.columnName = QStringLiteral("insert_time");
|
||||
miInsertionTime.db.defaultValue = QStringLiteral("now()");
|
||||
miInsertionTime.db.usedForInsert = false;
|
||||
m_structBuilder->fixupQVariantConversions(miInsertionTime);
|
||||
ConstraintInformation ci;
|
||||
ci.name = QStringLiteral("IDX_sample_time");
|
||||
ci.affectedColumns = QStringList({QStringLiteral("sample_time")});
|
||||
ci.type = ConstraintInformation::Type::index;
|
||||
for (const CompleteTableInformation& cti : recTables)
|
||||
{
|
||||
CompleteTableInformation ctiTmp(cti);
|
||||
ctiTmp.columns.push_back(miColumnIsValid);
|
||||
ctiTmp.columns.push_back(miInsertionTime);
|
||||
ctiTmp.constraints.push_back(ci);
|
||||
if (!handleTable(ctiTmp))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqlGenerator::handleViews()
|
||||
{
|
||||
const QMap<QString, ViewInformation> views = m_structBuilder->getDbViews();
|
||||
for (const ViewInformation& vi : views)
|
||||
{
|
||||
if (!handleView(vi))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqlGenerator::handleView(const ViewInformation& vi)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Handle view " << vi.name;
|
||||
|
||||
const QString parent = vi.baseTable;
|
||||
|
||||
DEBUG_LOG(m_logger) << "Parent = " << parent;
|
||||
|
||||
if (!parent.isEmpty())
|
||||
{
|
||||
const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(parent);
|
||||
const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n"));
|
||||
const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;"));
|
||||
|
||||
DEBUG_LOG(m_logger) << "csi.primaryTable = " << csi.primaryTable.data()->name;
|
||||
|
||||
if (!csi.primaryTable.isNull())
|
||||
{
|
||||
QStringList fromTables;
|
||||
fromTables += vi.baseSchema + QLatin1Char('.') + vi.baseTable;
|
||||
|
||||
QStringList whereConditions;
|
||||
if (vi.filter.contains(QLatin1String("exercise_id")))
|
||||
whereConditions += QLatin1String("exercise_id=") + QString::number(m_exerciseId);
|
||||
|
||||
QStringList fqColumnNames;
|
||||
|
||||
for (const MemberInformation& mi : csi.member)
|
||||
{
|
||||
if (mi.db.usedForView)
|
||||
fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") +
|
||||
mi.db.getColumnNameView();
|
||||
}
|
||||
|
||||
DEBUG_LOG(m_logger) << "fqColumnNames = " << fqColumnNames.join(QStringLiteral(", "));
|
||||
|
||||
if (!whereConditions.isEmpty())
|
||||
addToCreateViews(createViewStmt.arg(csi.viewName, fqColumnNames.join(QStringLiteral(", ")),
|
||||
fromTables.join(QStringLiteral(", ")),
|
||||
whereConditions.join(QStringLiteral(" AND\n"))),
|
||||
vi.exerciseDependency);
|
||||
addToDropViews(dropViewStmt.arg(csi.viewName), vi.exerciseDependency);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqlGenerator::handleTable(const CompleteTableInformation& cti)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Handle table " << cti.name;
|
||||
|
||||
if (cti.columns.isEmpty())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Table " << cti.name
|
||||
<< " has no DatabaseServiceStruct - not generating anything for this!";
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString dropTableStmt(QStringLiteral("DROP TABLE IF EXISTS %1 CASCADE;"));
|
||||
const QString createTableStmt(QStringLiteral("CREATE TABLE IF NOT EXISTS %1 (%2\n);"));
|
||||
const QString commentTableStmt(QStringLiteral("COMMENT ON TABLE %1 IS '%2';"));
|
||||
|
||||
QStringList tableComments;
|
||||
addToDropTables(dropTableStmt.arg(cti.name), cti.exerciseDependency);
|
||||
|
||||
if ((cti.exerciseDependency & Global) == Global)
|
||||
{
|
||||
addToCreateTables(createTableStmt.arg(
|
||||
cti.name, getTableColumnCreateStatements(cti.name, cti.columns, &tableComments, Global)),
|
||||
Global);
|
||||
}
|
||||
if ((cti.exerciseDependency & Exercise) == Exercise)
|
||||
{
|
||||
addToCreateTables(createTableStmt.arg(cti.name, getTableColumnCreateStatements(cti.name, cti.columns,
|
||||
&tableComments, Exercise)),
|
||||
Exercise);
|
||||
}
|
||||
addToTables(cti.name, cti.exerciseDependency);
|
||||
|
||||
if (!cti.comment.isEmpty())
|
||||
addToCreateTables(commentTableStmt.arg(cti.name, fixupComment(cti.comment)), cti.exerciseDependency);
|
||||
addToCreateTables(tableComments, cti.exerciseDependency);
|
||||
|
||||
const QString parent = m_xmlReader->getStruct2BaseClassRelations().value(cti.name);
|
||||
const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(cti.name);
|
||||
if (!parent.isEmpty())
|
||||
{
|
||||
const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n"));
|
||||
const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;"));
|
||||
if (!csi.primaryTable.isNull())
|
||||
{
|
||||
QStringList fqColumnNames;
|
||||
QStringList fromTables;
|
||||
QStringList whereConditions;
|
||||
fromTables += cti.name;
|
||||
|
||||
for (const MemberInformation& mi : csi.member)
|
||||
{
|
||||
if (mi.db.usedForView)
|
||||
fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") +
|
||||
mi.db.getColumnNameView();
|
||||
if (mi.cpp.isReadOnly && mi.db.isForeignKey)
|
||||
{
|
||||
|
||||
for (const QSharedPointer<TableInformation>& tiPtr : csi.tables)
|
||||
{
|
||||
if (tiPtr->name == mi.db.table)
|
||||
{
|
||||
const QSharedPointer<ForeignKeyInformation> fki =
|
||||
tiPtr->fkInformationForColumn(mi.db.table, mi.db.columnName);
|
||||
if (!fki.isNull())
|
||||
{
|
||||
|
||||
if (!fromTables.contains(fki->destination.tableName))
|
||||
{
|
||||
whereConditions += fki->destination.tableName + QLatin1Char('.') +
|
||||
fki->destination.columnName + QLatin1String(" = ") +
|
||||
fki->source.tableName + QLatin1Char('.') +
|
||||
fki->source.columnName;
|
||||
fromTables += fki->destination.tableName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!whereConditions.isEmpty())
|
||||
{
|
||||
addToCreateViews(createViewStmt.arg(csi.viewName, fqColumnNames.join(QStringLiteral(", ")),
|
||||
fromTables.join(QStringLiteral(", ")),
|
||||
whereConditions.join(QStringLiteral(" AND\n"))),
|
||||
cti.exerciseDependency);
|
||||
}
|
||||
addToDropViews(dropViewStmt.arg(csi.viewName), cti.exerciseDependency);
|
||||
}
|
||||
}
|
||||
|
||||
if (!generateDbNotifications(cti.name, csi.dbNotifications, cti.exerciseDependency))
|
||||
return false;
|
||||
|
||||
generateForeignKeys(cti.foreignKeys, cti.exerciseDependency);
|
||||
|
||||
generateConstraints(cti.name, cti.constraints, cti.exerciseDependency);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString SqlGenerator::getTableColumnCreateStatements(const QString& tableName,
|
||||
const QVector<MemberInformation>& columns,
|
||||
QStringList* tableComments, ExerciseDependency ed)
|
||||
{
|
||||
Q_ASSERT(ed == Global || ed == Exercise);
|
||||
|
||||
const QString commentColumnStmt(QStringLiteral("COMMENT ON COLUMN %1 IS '%2';"));
|
||||
|
||||
int maxLength = 1;
|
||||
for (const MemberInformation& mi : columns)
|
||||
maxLength = std::max(maxLength, static_cast<int>(mi.db.columnName.length()));
|
||||
maxLength += 1;
|
||||
|
||||
QStringList retCols;
|
||||
retCols.reserve(columns.size() + 1);
|
||||
retCols += QString();
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
const MemberInformation& mi = columns.at(i);
|
||||
const FullQualifiedColumnInformation& ci = mi.db;
|
||||
const QString spaces(maxLength - ci.columnName.length(), QLatin1Char(' '));
|
||||
QString column = ci.columnName + spaces + ci.datatype;
|
||||
if (ci.hasSequence)
|
||||
{
|
||||
|
||||
const QString seqDescr = generateSequence(tableName, ci, ed);
|
||||
QString schema;
|
||||
if (ed == Global)
|
||||
schema = m_xmlReader->getServiceAbbr();
|
||||
else
|
||||
schema = getExerciseSchemaName(m_exerciseId);
|
||||
|
||||
column += QLatin1String(" DEFAULT nextval(( '") + schema + QLatin1Char('.') + seqDescr +
|
||||
QLatin1String("'::text)::regclass)");
|
||||
}
|
||||
if (!ci.canBeNull)
|
||||
column += QLatin1String(" NOT NULL");
|
||||
if (!ci.hasSequence && !ci.defaultValue.isEmpty())
|
||||
column += QLatin1String(" DEFAULT ") + ci.defaultValue;
|
||||
if (i < columns.size() - 1)
|
||||
column += QLatin1Char(',');
|
||||
if (!ci.comment.isEmpty())
|
||||
{
|
||||
const QString comment(fixupComment(ci.comment));
|
||||
const QString fqn = ci.table + QLatin1Char('.') + ci.columnName;
|
||||
*tableComments += commentColumnStmt.arg(fqn, comment);
|
||||
column += QLatin1String(" -- ") + comment;
|
||||
}
|
||||
|
||||
retCols += column;
|
||||
|
||||
if (ci.isPrimaryKey)
|
||||
generatePrimaryKey(tableName, ci, ed);
|
||||
}
|
||||
return retCols.join(QStringLiteral("\n "));
|
||||
}
|
||||
|
||||
QString SqlGenerator::generateSequence(const QString& tableName, const FullQualifiedColumnInformation& ci,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
const QString sequenceName = tableName + QLatin1Char('_') + ci.columnName + QLatin1String("_seq");
|
||||
|
||||
const QString createSequenceStmt(
|
||||
QLatin1String("CREATE SEQUENCE IF NOT EXISTS %1 INCREMENT %2 START %3 MINVALUE %4;"));
|
||||
|
||||
const int minValue = ci.sequenceStart < 1 ? ci.sequenceStart : 1;
|
||||
|
||||
addToCreateSequences(
|
||||
createSequenceStmt.arg(sequenceName).arg(ci.sequenceIncrement).arg(ci.sequenceStart).arg(minValue), ed);
|
||||
addToSequences(sequenceName, ed);
|
||||
|
||||
return sequenceName;
|
||||
}
|
||||
|
||||
void SqlGenerator::generatePrimaryKey(const QString& tableName, const FullQualifiedColumnInformation& ci,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
QString pkName = QLatin1String("PK_") + tableName + QLatin1Char('_') + ci.columnName;
|
||||
|
||||
if (pkName.size() > 63)
|
||||
pkName.resize(63);
|
||||
|
||||
addToPks(pkName, ed);
|
||||
|
||||
const QString pkConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 ADD CONSTRAINT %2 PRIMARY KEY (%3);"));
|
||||
addToCreatePks(pkConstraint.arg(tableName, pkName, ci.columnName), ed);
|
||||
|
||||
const QString pkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
|
||||
addToDropConstraints(pkDropConstraint.arg(tableName, pkName), ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::generateForeignKeys(const QVector<QSharedPointer<ForeignKeyInformation>>& foreignKeys,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
for (const QSharedPointer<ForeignKeyInformation>& fk : foreignKeys)
|
||||
generateForeignKey(fk, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::generateForeignKey(const QSharedPointer<ForeignKeyInformation>& foreignKey, ExerciseDependency ed)
|
||||
{
|
||||
QString fkName = QLatin1String("FK_") + foreignKey->source.tableName + QLatin1Char('_') +
|
||||
foreignKey->source.columnName + QLatin1Char('_') + foreignKey->destination.tableName +
|
||||
QLatin1Char('_') + foreignKey->destination.columnName;
|
||||
|
||||
if (fkName.size() > 63)
|
||||
fkName = foreignKey->name;
|
||||
|
||||
addToFks(fkName, ed);
|
||||
|
||||
QString fkConstraint(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 FOREIGN KEY (%3) REFERENCES %4 (%5)"));
|
||||
switch (foreignKey->deleteAction)
|
||||
{
|
||||
case ForeignKeyInformation::OnDeleteAction::NoAction:
|
||||
fkConstraint += QStringLiteral(" ON DELETE NO ACTION");
|
||||
break;
|
||||
case ForeignKeyInformation::OnDeleteAction::Cascade:
|
||||
fkConstraint += QStringLiteral(" ON DELETE CASCADE ON UPDATE CASCADE");
|
||||
break;
|
||||
case ForeignKeyInformation::OnDeleteAction::Restrict:
|
||||
fkConstraint += QStringLiteral(" ON DELETE RESTRICT");
|
||||
break;
|
||||
case ForeignKeyInformation::OnDeleteAction::SetNull:
|
||||
fkConstraint += QStringLiteral(" ON DELETE SET NULL");
|
||||
break;
|
||||
case ForeignKeyInformation::OnDeleteAction::SetDefault:
|
||||
fkConstraint += QStringLiteral(" ON DELETE SET DEFAULT");
|
||||
break;
|
||||
}
|
||||
|
||||
fkConstraint += QLatin1Char(';');
|
||||
|
||||
QString destination;
|
||||
if (!foreignKey->destination.schema.isEmpty())
|
||||
{
|
||||
const QString& extSchema = foreignKey->destination.schema;
|
||||
destination = extSchema + QLatin1Char('.') + foreignKey->destination.tableName;
|
||||
}
|
||||
else
|
||||
destination = foreignKey->destination.tableName;
|
||||
|
||||
const QString& extSvcAbbr = foreignKey->destination.serviceAbbr;
|
||||
|
||||
if (!extSvcAbbr.isEmpty() && !m_referencedSevices.contains(extSvcAbbr))
|
||||
m_referencedSevices += extSvcAbbr;
|
||||
|
||||
addToCreateFks(fkConstraint.arg(foreignKey->source.tableName, fkName, foreignKey->source.columnName, destination,
|
||||
foreignKey->destination.columnName),
|
||||
ed);
|
||||
|
||||
const QString fkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
|
||||
addToDropConstraints(fkDropConstraint.arg(foreignKey->source.tableName, fkName), ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::generateConstraints(const QString& tableName, const QVector<ConstraintInformation>& constraints,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
for (const ConstraintInformation& constraint : constraints)
|
||||
generateConstraint(tableName, constraint, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::generateConstraint(const QString& tableName, const ConstraintInformation& constraint,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
const QString dropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
|
||||
switch (constraint.type)
|
||||
{
|
||||
case ConstraintInformation::Type::index: {
|
||||
QString constraintName = QLatin1String("IDX_") + tableName + QLatin1Char('_') +
|
||||
constraint.affectedColumns.join(QStringLiteral("_"));
|
||||
|
||||
if (constraintName.size() > 63)
|
||||
constraintName.resize(63);
|
||||
|
||||
const QString index(QStringLiteral("CREATE INDEX %1 ON %2 (%3);"));
|
||||
|
||||
addToCreateConstraints(
|
||||
index.arg(constraintName, tableName, constraint.affectedColumns.join(QStringLiteral(", "))), ed);
|
||||
addToConstraints(constraintName, ed);
|
||||
|
||||
addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed);
|
||||
|
||||
break;
|
||||
}
|
||||
case ConstraintInformation::Type::unique: {
|
||||
QString constraintName = QLatin1String("UQ_") + tableName + QLatin1Char('_') +
|
||||
constraint.affectedColumns.join(QStringLiteral("_"));
|
||||
|
||||
if (constraintName.size() > 63)
|
||||
constraintName.resize(63);
|
||||
const QString unique(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 UNIQUE (%3);"));
|
||||
|
||||
addToCreateConstraints(
|
||||
unique.arg(tableName, constraintName, constraint.affectedColumns.join(QStringLiteral(", "))), ed);
|
||||
addToConstraints(constraintName, ed);
|
||||
|
||||
addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed);
|
||||
|
||||
break;
|
||||
}
|
||||
case ConstraintInformation::Type::unknown:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool SqlGenerator::handleEnumerations()
|
||||
{
|
||||
const QVector<EnumInformation> enumerations = m_xmlReader->getEnumerations();
|
||||
for (const EnumInformation& ei : enumerations)
|
||||
{
|
||||
if (!handleEnumeration(ei))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqlGenerator::handleEnumeration(const EnumInformation& ei)
|
||||
{
|
||||
const QStringList sql = handleEnumeration(ei, QString());
|
||||
if (m_sqlItemsGlobal.tables.contains(ei.tableName))
|
||||
m_sqlGlobal.enumerationValues += sql;
|
||||
if (m_sqlItemsExercise.tables.contains(ei.tableName))
|
||||
m_sqlExercise.enumerationValues += sql;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList SqlGenerator::handleEnumeration(const EnumInformation& ei, const QString& dbSchema) const
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Handle enumeration " << ei.enumName;
|
||||
|
||||
if (ei.tableName.isEmpty())
|
||||
{
|
||||
INFO_LOG(m_logger) << "No table for enumeration " << ei.enumName << " - not creating inserts for this enum";
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
const QString insertEnum(QStringLiteral("INSERT INTO %1 (%2) VALUES (%3);"));
|
||||
const QString columnNames(QStringLiteral("id, name"));
|
||||
const QString tablename = dbSchema.isEmpty() ? ei.tableName : dbSchema + QLatin1Char('.') + ei.tableName;
|
||||
|
||||
QStringList enumerationValues;
|
||||
for (const EnumKeyValuePair& evp : ei.values)
|
||||
{
|
||||
const QString values(QString::number(evp.value) + QLatin1String(", '") + fixupComment(evp.key) +
|
||||
QLatin1Char('\''));
|
||||
enumerationValues += insertEnum.arg(tablename, columnNames, values);
|
||||
}
|
||||
|
||||
return enumerationValues;
|
||||
}
|
||||
|
||||
bool SqlGenerator::generateDbNotifications(const QString& tableName,
|
||||
const QVector<DbNotifcationInformation>& notifications,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
for (const DbNotifcationInformation& notification : notifications)
|
||||
{
|
||||
if (notification.paramNames.isEmpty() || notification.paramNames.size() > 1)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Can not handle more than one parameter";
|
||||
continue;
|
||||
}
|
||||
generateDbNotification(notification, ed);
|
||||
if (!generateDbNotificationTrigger(tableName, notification, ed))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SqlGenerator::generateDbNotification(const DbNotifcationInformation& notification, ExerciseDependency ed)
|
||||
{
|
||||
const QString func(QStringLiteral("CREATE OR REPLACE FUNCTION public.%1()\n"
|
||||
"RETURNS TRIGGER AS $$\n"
|
||||
"DECLARE\n"
|
||||
" toNotify RECORD;\n"
|
||||
" toReturn RECORD;\n"
|
||||
"BEGIN\n"
|
||||
" IF (TG_OP = 'INSERT') THEN\n"
|
||||
" toNotify = NEW;\n"
|
||||
" toReturn = NEW;\n"
|
||||
" ELSIF (TG_OP = 'UPDATE') THEN\n"
|
||||
" toNotify = OLD;\n"
|
||||
" toReturn = NEW;\n"
|
||||
" ELSIF (TG_OP = 'DELETE') THEN\n"
|
||||
" toNotify = OLD;\n"
|
||||
" toReturn = OLD;\n"
|
||||
" END IF;\n"
|
||||
" \n"
|
||||
" PERFORM pg_notify('%2', CAST(toNotify.%3 AS text));\n"
|
||||
" RETURN toReturn;\n"
|
||||
"END;\n"
|
||||
"$$ LANGUAGE plpgsql;"));
|
||||
|
||||
const QString triggerFuncName(getNotificationFunctionName(notification));
|
||||
addToDbFunctions(func.arg(triggerFuncName, notification.methodName, notification.paramNames.first()), ed);
|
||||
}
|
||||
|
||||
bool SqlGenerator::generateDbNotificationTrigger(const QString& tableName, const DbNotifcationInformation& notification,
|
||||
ExerciseDependency ed)
|
||||
{
|
||||
|
||||
const QString drop(QStringLiteral("DROP TRIGGER IF EXISTS %1 ON %2;"));
|
||||
const QString func(QStringLiteral("CREATE TRIGGER %1\n") +
|
||||
QStringLiteral("%2 ON %3 FOR EACH ROW EXECUTE PROCEDURE public.%4();"));
|
||||
|
||||
for (DbNotifcationInformation::NotificationType notifyType : notification.notificationType)
|
||||
{
|
||||
QString when;
|
||||
QString triggerNameShortcut;
|
||||
switch (notifyType)
|
||||
{
|
||||
case DbNotifcationInformation::NotificationType::BeforeInsert:
|
||||
when = QStringLiteral("BEFORE INSERT");
|
||||
triggerNameShortcut = QStringLiteral("bi");
|
||||
break;
|
||||
case DbNotifcationInformation::NotificationType::AfterInsert:
|
||||
when = QStringLiteral("AFTER INSERT");
|
||||
triggerNameShortcut = QStringLiteral("ai");
|
||||
break;
|
||||
case DbNotifcationInformation::NotificationType::BeforeUpdate:
|
||||
when = QStringLiteral("BEFORE UPDATE");
|
||||
triggerNameShortcut = QStringLiteral("bu");
|
||||
break;
|
||||
case DbNotifcationInformation::NotificationType::AfterUpdate:
|
||||
when = QStringLiteral("AFTER UPDATE");
|
||||
triggerNameShortcut = QStringLiteral("au");
|
||||
break;
|
||||
case DbNotifcationInformation::NotificationType::BeforeDelete:
|
||||
when = QStringLiteral("BEFORE DELETE");
|
||||
triggerNameShortcut = QStringLiteral("bd");
|
||||
break;
|
||||
case DbNotifcationInformation::NotificationType::AfterDelete:
|
||||
when = QStringLiteral("AFTER DELETE");
|
||||
triggerNameShortcut = QStringLiteral("ad");
|
||||
break;
|
||||
}
|
||||
const QString triggerName(QLatin1String("trg_") + triggerNameShortcut + QLatin1Char('_') +
|
||||
notification.methodName);
|
||||
const QString triggerFuncName(getNotificationFunctionName(notification));
|
||||
addToDbFunctionTrigger(drop.arg(triggerName, tableName), ed);
|
||||
addToDbFunctionTrigger(func.arg(triggerName, when, tableName, triggerFuncName), ed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString SqlGenerator::getNotificationFunctionName(const DbNotifcationInformation& notification) const
|
||||
{
|
||||
return getServiceAbbr() + QLatin1Char('_') + notification.methodName;
|
||||
}
|
||||
|
||||
QString SqlGenerator::fixupComment(const QString& comment)
|
||||
{
|
||||
|
||||
QString ret = comment;
|
||||
ret = ret.remove(QLatin1Char('\''));
|
||||
ret = ret.replace(QLatin1Char(';'), QLatin1Char(','));
|
||||
return ret;
|
||||
}
|
||||
|
||||
QStringList SqlGenerator::getDbSettings(const QString& dbSchema) const
|
||||
{
|
||||
QStringList data;
|
||||
|
||||
QString dbUser(QStringLiteral("ets_"));
|
||||
if (m_exerciseId > 0)
|
||||
dbUser += QStringLiteral("ex_00");
|
||||
else
|
||||
dbUser += m_xmlReader->getServiceAbbr().toLower();
|
||||
|
||||
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
||||
QLatin1String(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + dbUser +
|
||||
QLatin1Char(';'));
|
||||
|
||||
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
||||
QLatin1String(" GRANT SELECT, REFERENCES ON TABLES TO ets_ro;"));
|
||||
|
||||
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
||||
QLatin1String(" GRANT EXECUTE ON FUNCTIONS TO ") + dbUser + QLatin1Char(';'));
|
||||
|
||||
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
|
||||
QLatin1String(" GRANT ALL ON SEQUENCES TO ") + dbUser + QLatin1Char(';'));
|
||||
|
||||
data.append(QLatin1String("SET SEARCH_PATH TO ") + dbSchema + QLatin1String(", PUBLIC;"));
|
||||
|
||||
QStringList deps;
|
||||
deps.reserve(m_referencedSevices.size());
|
||||
for (const QString& svc : m_referencedSevices)
|
||||
deps += QStringLiteral("-- DEPENDS: ") + svc;
|
||||
|
||||
return deps + data;
|
||||
}
|
||||
|
||||
QStringList SqlGenerator::getAllSqlStmts(const QString& dbSchema, ExerciseDependency ed) const
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Creating statements for service " << getServiceAbbr() << " in exercise " << m_exerciseId
|
||||
<< ", this: " << this;
|
||||
|
||||
QStringList data;
|
||||
|
||||
const SqlStatements& sql = getStatements(ed);
|
||||
if (sql.isEmpty())
|
||||
return data;
|
||||
|
||||
if (!dbSchema.isEmpty())
|
||||
data << getDbSettings(dbSchema);
|
||||
|
||||
data.append(QStringLiteral("\n-- Drop tables/views/sequences/constraints"));
|
||||
data.append(sql.dropViews);
|
||||
data.append(sql.dropConstraints);
|
||||
data.append(sql.dropTables);
|
||||
data.append(sql.dropSequences);
|
||||
|
||||
data.append(QStringLiteral("\n-- Create tables"));
|
||||
data.append(sql.createTables);
|
||||
|
||||
data.append(QStringLiteral("\n-- Create sequence(s)"));
|
||||
data.append(sql.createSequences);
|
||||
|
||||
data.append(QStringLiteral("\n-- Create primary keys"));
|
||||
data.append(sql.createPrimaryKeys);
|
||||
|
||||
data.append(QStringLiteral("\n-- Create foreign keys"));
|
||||
data.append(sql.createForeignKeys);
|
||||
|
||||
data.append(QStringLiteral("\n-- Create views"));
|
||||
data.append(sql.createViews);
|
||||
|
||||
data.append(
|
||||
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
||||
data.append(QStringLiteral("\n-- Inserts for static data i.e. enums\n"));
|
||||
data.append(sql.enumerationValues);
|
||||
|
||||
data.append(
|
||||
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
||||
data.append(QStringLiteral("\n-- Notify functions for callbacks (signals in db interface)\n"));
|
||||
data.append(sql.dbFunctions);
|
||||
|
||||
data.append(
|
||||
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
||||
data.append(QStringLiteral("\n-- Notify function trigger for callbacks (signals in db interface)\n"));
|
||||
data.append(sql.dbFunctionTrigger);
|
||||
|
||||
data.append(
|
||||
QStringLiteral("\n-- --------------------------------------------------------------------------------"));
|
||||
data.append(QStringLiteral("\n-- Indexes \n"));
|
||||
data.append(sql.createConstraints);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
QStringList SqlGenerator::getEnumerationInsertValues(const QString& table, const QString& dbSchema) const
|
||||
{
|
||||
const QVector<EnumInformation> enumerations = m_xmlReader->getEnumerations();
|
||||
|
||||
for (const EnumInformation& ei : enumerations)
|
||||
{
|
||||
if (ei.tableName == table)
|
||||
return handleEnumeration(ei, dbSchema);
|
||||
}
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
bool SqlGenerator::writeSqlToFile(const QString& dbSchema, const QString& sqlFile, ExerciseDependency ed) const
|
||||
{
|
||||
return writeFile(sqlFile, getAllSqlStmts(dbSchema, ed).join(QLatin1Char('\n')));
|
||||
}
|
||||
|
||||
bool SqlGenerator::writeSql(const QString& filePath, ExerciseDependency ed)
|
||||
{
|
||||
Q_ASSERT(ed == Global || ed == Exercise);
|
||||
|
||||
QString sqlFile = filePath + getServiceAbbr();
|
||||
if (ed == ExerciseDependency::Exercise)
|
||||
sqlFile += QLatin1Char('_') + getExerciseSchemaName(m_exerciseId);
|
||||
sqlFile += QLatin1String(".sql");
|
||||
|
||||
const SqlStatements& sql = getStatements(ed);
|
||||
if (sql.isEmpty())
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Sql file is emtpy: " << sqlFile;
|
||||
return writeFile(sqlFile, QString());
|
||||
}
|
||||
|
||||
const QString dbSchema =
|
||||
(ed == ExerciseDependency::Exercise ? getExerciseSchemaName(m_exerciseId) : getServiceAbbr());
|
||||
|
||||
INFO_LOG(m_logger) << "Creating sql-stmt for schema (" << dbSchema << ")";
|
||||
return writeSqlToFile(dbSchema, sqlFile, ed);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void addInternal(QStringList& global, QStringList& exercise, const T& stmt, ExerciseDependency ed)
|
||||
{
|
||||
switch (ed)
|
||||
{
|
||||
case ExerciseDependency::Global:
|
||||
global += stmt;
|
||||
break;
|
||||
case ExerciseDependency::Exercise:
|
||||
exercise += stmt;
|
||||
break;
|
||||
case ExerciseDependency::GlobalAndExercise:
|
||||
global += stmt;
|
||||
exercise += stmt;
|
||||
break;
|
||||
case ExerciseDependency::Undefined:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SqlGenerator::addToDropSequences(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.dropSequences, m_sqlExercise.dropSequences, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToDropTables(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.dropTables, m_sqlExercise.dropTables, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToDropViews(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.dropViews, m_sqlExercise.dropViews, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreateConstraints(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createConstraints, m_sqlExercise.createConstraints, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToDropConstraints(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.dropConstraints, m_sqlExercise.dropConstraints, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreateFks(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createForeignKeys, m_sqlExercise.createForeignKeys, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreatePks(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createPrimaryKeys, m_sqlExercise.createPrimaryKeys, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreateSequences(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createSequences, m_sqlExercise.createSequences, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreateTables(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreateTables(const QStringList& stmts, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmts, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToCreateViews(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.createViews, m_sqlExercise.createViews, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToDbFunctions(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.dbFunctions, m_sqlExercise.dbFunctions, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToDbFunctionTrigger(const QString& stmt, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlGlobal.dbFunctionTrigger, m_sqlExercise.dbFunctionTrigger, stmt, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToConstraints(const QString& name, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlItemsGlobal.constraints, m_sqlItemsExercise.constraints, name, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToFks(const QString& name, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlItemsGlobal.foreignKeys, m_sqlItemsExercise.foreignKeys, name, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToPks(const QString& name, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlItemsGlobal.primaryKeys, m_sqlItemsExercise.primaryKeys, name, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToSequences(const QString& name, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlItemsGlobal.sequences, m_sqlItemsExercise.sequences, name, ed);
|
||||
}
|
||||
|
||||
void SqlGenerator::addToTables(const QString& name, ExerciseDependency ed)
|
||||
{
|
||||
addInternal(m_sqlItemsGlobal.tables, m_sqlItemsExercise.tables, name, ed);
|
||||
}
|
||||
|
||||
bool SqlGenerator::writeFile(const QString& fn, const QString& content) const
|
||||
{
|
||||
QFile f(fn);
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: " << f.errorString();
|
||||
return false;
|
||||
}
|
||||
f.write(content.toUtf8());
|
||||
if (!content.isEmpty())
|
||||
f.write("\n");
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StructBuilder.h"
|
||||
#include "codegen_base_export.h"
|
||||
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace NGL
|
||||
{
|
||||
class INGLLogger;
|
||||
}
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
struct ColumnInformation;
|
||||
struct ConstraintInformation;
|
||||
struct EnumInformation;
|
||||
struct ForeignKeyInformation;
|
||||
struct TableInformation;
|
||||
class XmlReader;
|
||||
|
||||
class CODEGEN_BASE_EXPORT SqlGenerator final
|
||||
{
|
||||
public:
|
||||
struct SqlStatements
|
||||
{
|
||||
QStringList dropViews;
|
||||
QStringList dropTables;
|
||||
QStringList dropSequences;
|
||||
QStringList dropConstraints;
|
||||
|
||||
QStringList createTables;
|
||||
QStringList createSequences;
|
||||
QStringList createPrimaryKeys;
|
||||
QStringList createForeignKeys;
|
||||
QStringList createViews;
|
||||
QStringList createConstraints;
|
||||
|
||||
QStringList enumerationValues;
|
||||
QStringList dbFunctions;
|
||||
QStringList dbFunctionTrigger;
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return dropViews.isEmpty() && dropTables.isEmpty() && dropSequences.isEmpty() && dropConstraints.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct SqlItems
|
||||
{
|
||||
QStringList tables;
|
||||
QStringList sequences;
|
||||
QStringList primaryKeys;
|
||||
QStringList foreignKeys;
|
||||
QStringList constraints;
|
||||
};
|
||||
|
||||
public:
|
||||
Q_DISABLE_COPY(SqlGenerator)
|
||||
|
||||
explicit SqlGenerator(XmlReader* xmlReader, StructBuilder* structBuilder);
|
||||
explicit SqlGenerator(const QString& xmlFile);
|
||||
|
||||
~SqlGenerator();
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return m_isValid;
|
||||
}
|
||||
|
||||
bool generateData(int exerciseId);
|
||||
|
||||
bool createStatementsForTable(const QString& tableName, const QString& tableNameOut);
|
||||
|
||||
SqlStatements getStatements(ExerciseDependency ed) const
|
||||
{
|
||||
return (ed == ExerciseDependency::Exercise ? m_sqlExercise : m_sqlGlobal);
|
||||
}
|
||||
|
||||
SqlItems getSqlItems(ExerciseDependency ed) const
|
||||
{
|
||||
return (ed == ExerciseDependency::Exercise ? m_sqlItemsExercise : m_sqlItemsGlobal);
|
||||
}
|
||||
|
||||
QStringList getEnumerationInsertValues(const QString& table, const QString& dbSchema) const;
|
||||
|
||||
QStringList getReferencedServices() const
|
||||
{
|
||||
return m_referencedSevices;
|
||||
}
|
||||
|
||||
QStringList getDbSettings(const QString& dbSchema) const;
|
||||
|
||||
bool writeSql(const QString& filePath, ExerciseDependency ed);
|
||||
|
||||
bool writeSqlToFile(const QString& dbSchema, const QString& sqlFile, ExerciseDependency ed) const;
|
||||
|
||||
QString getServiceAbbr() const
|
||||
{
|
||||
return m_xmlReader->getServiceAbbr();
|
||||
}
|
||||
|
||||
bool isServiceExerciseIndependent() const
|
||||
{
|
||||
return m_xmlReader->isServiceExerciseIndependent();
|
||||
}
|
||||
|
||||
bool isServiceGlobal() const
|
||||
{
|
||||
return (m_xmlReader->defaultExerciseDependency() == CodeGen::ExerciseDependency::Global ||
|
||||
m_xmlReader->defaultExerciseDependency() == CodeGen::ExerciseDependency::GlobalAndExercise);
|
||||
}
|
||||
|
||||
CodeGen::ExerciseDependency getExerciseDependency() const
|
||||
{
|
||||
return m_xmlReader->defaultExerciseDependency();
|
||||
}
|
||||
|
||||
QStringList getGroupNames() const
|
||||
{
|
||||
return m_xmlReader->getGroupNames();
|
||||
}
|
||||
|
||||
QStringList getAllSqlStmts(const QString& dbSchema, ExerciseDependency ed) const;
|
||||
|
||||
protected:
|
||||
NGL::INGLLogger& m_logger;
|
||||
|
||||
private:
|
||||
bool handleTables();
|
||||
bool handleViews();
|
||||
bool handleTable(const CompleteTableInformation& cti);
|
||||
bool handleView(const ViewInformation& vi);
|
||||
QString getTableColumnCreateStatements(const QString& tableName, const QVector<MemberInformation>& columns,
|
||||
QStringList* tableComments, ExerciseDependency ed);
|
||||
QString generateSequence(const QString& tableName, const FullQualifiedColumnInformation& ci, ExerciseDependency ed);
|
||||
void generatePrimaryKey(const QString& tableName, const FullQualifiedColumnInformation& ci, ExerciseDependency ed);
|
||||
void generateForeignKeys(const QVector<QSharedPointer<ForeignKeyInformation>>& foreignKeys, ExerciseDependency ed);
|
||||
void generateForeignKey(const QSharedPointer<ForeignKeyInformation>& foreignKey, ExerciseDependency ed);
|
||||
void generateConstraints(const QString& tableName, const QVector<ConstraintInformation>& constraints,
|
||||
ExerciseDependency ed);
|
||||
void generateConstraint(const QString& tableName, const ConstraintInformation& constraint, ExerciseDependency ed);
|
||||
bool handleEnumerations();
|
||||
bool handleEnumeration(const EnumInformation& ei);
|
||||
QStringList handleEnumeration(const EnumInformation& ei, const QString& dbSchema) const;
|
||||
bool generateDbNotifications(const QString& tableName, const QVector<DbNotifcationInformation>& notifications,
|
||||
ExerciseDependency ed);
|
||||
void generateDbNotification(const DbNotifcationInformation& notification, ExerciseDependency ed);
|
||||
bool generateDbNotificationTrigger(const QString& tableName, const DbNotifcationInformation& notification,
|
||||
ExerciseDependency ed);
|
||||
QString getNotificationFunctionName(const DbNotifcationInformation& notification) const;
|
||||
static QString fixupComment(const QString& comment);
|
||||
|
||||
private:
|
||||
XmlReader* m_xmlReader;
|
||||
StructBuilder* m_structBuilder;
|
||||
bool m_createdByXmlFile;
|
||||
|
||||
void addToDropSequences(const QString& stmt, ExerciseDependency ed);
|
||||
void addToDropTables(const QString& stmt, ExerciseDependency ed);
|
||||
void addToDropViews(const QString& stmt, ExerciseDependency ed);
|
||||
void addToDropConstraints(const QString& stmt, ExerciseDependency ed);
|
||||
|
||||
void addToCreateConstraints(const QString& stmt, ExerciseDependency ed);
|
||||
void addToCreateFks(const QString& stmt, ExerciseDependency ed);
|
||||
void addToCreatePks(const QString& stmt, ExerciseDependency ed);
|
||||
void addToCreateSequences(const QString& stmt, ExerciseDependency ed);
|
||||
void addToCreateTables(const QString& stmt, ExerciseDependency ed);
|
||||
void addToCreateTables(const QStringList& stmts, ExerciseDependency ed);
|
||||
void addToCreateViews(const QString& stmt, ExerciseDependency ed);
|
||||
|
||||
void addToDbFunctions(const QString& stmt, ExerciseDependency ed);
|
||||
void addToDbFunctionTrigger(const QString& stmt, ExerciseDependency ed);
|
||||
|
||||
void addToConstraints(const QString& name, ExerciseDependency ed);
|
||||
void addToFks(const QString& name, ExerciseDependency ed);
|
||||
void addToPks(const QString& name, ExerciseDependency ed);
|
||||
void addToSequences(const QString& name, ExerciseDependency ed);
|
||||
void addToTables(const QString& name, ExerciseDependency ed);
|
||||
|
||||
bool writeFile(const QString& fn, const QString& content) const;
|
||||
|
||||
SqlStatements m_sqlGlobal;
|
||||
SqlStatements m_sqlExercise;
|
||||
|
||||
SqlItems m_sqlItemsGlobal;
|
||||
SqlItems m_sqlItemsExercise;
|
||||
|
||||
QStringList m_referencedSevices;
|
||||
|
||||
bool m_isValid;
|
||||
int m_exerciseId;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,385 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "XmlReader.h"
|
||||
#include "codegen_base_export.h"
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace NGL
|
||||
{
|
||||
class INGLLogger;
|
||||
}
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
class StructBuilder;
|
||||
|
||||
struct EaData
|
||||
{
|
||||
QString table;
|
||||
QString column;
|
||||
QString type;
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return table + QLatin1Char(';') + column + QLatin1Char(';') + type;
|
||||
}
|
||||
};
|
||||
|
||||
struct FullQualifiedColumnInformation : public ColumnInformation
|
||||
{
|
||||
QString table;
|
||||
bool usedForInsert = false;
|
||||
bool usedForView = false;
|
||||
QString columnNameView;
|
||||
|
||||
QString getColumnNameView() const
|
||||
{
|
||||
return columnNameView.isEmpty() ? columnName : columnNameView;
|
||||
}
|
||||
|
||||
FullQualifiedColumnInformation() = default;
|
||||
|
||||
FullQualifiedColumnInformation(const ColumnInformation& ci, const QString& tableName)
|
||||
: ColumnInformation(ci), table(tableName)
|
||||
{
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return table + QLatin1Char(';') + columnName + QLatin1Char(';') + datatype +
|
||||
|
||||
QLatin1Char(';') + (isPrimaryKey ? QLatin1String("true") : QLatin1String("false")) + QLatin1Char(';') +
|
||||
(isForeignKey ? QLatin1String("true") : QLatin1String("false")) + QLatin1Char(';') +
|
||||
(usedForInsert ? QLatin1String("true") : QLatin1String("false")) + QLatin1Char(';') +
|
||||
(usedForView ? QLatin1String("true") : QLatin1String("false"));
|
||||
}
|
||||
};
|
||||
|
||||
class DataTypeInfo
|
||||
{
|
||||
public:
|
||||
DataTypeInfo(const QString& name, bool isPod) : m_name(name), m_isPod(isPod) {}
|
||||
|
||||
virtual ~DataTypeInfo() = default;
|
||||
|
||||
virtual QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
virtual QString idlType() const = 0;
|
||||
virtual QString ddsType() const = 0;
|
||||
virtual QString sqlType() const = 0;
|
||||
virtual QString cppType() const = 0;
|
||||
|
||||
virtual QString cppTypeConstRef() const
|
||||
{
|
||||
return isPod() ? cppType() : QLatin1String("const ") + cppType() + QLatin1String(" &");
|
||||
}
|
||||
|
||||
virtual bool isPod() const
|
||||
{
|
||||
return m_isPod;
|
||||
}
|
||||
|
||||
virtual bool isSequence() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool isStructure() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual QVector<QSharedPointer<DataTypeInfo>> member() const
|
||||
{
|
||||
return QVector<QSharedPointer<DataTypeInfo>>();
|
||||
}
|
||||
|
||||
virtual QSharedPointer<DataTypeInfo> innerDataType() const
|
||||
{
|
||||
return QSharedPointer<DataTypeInfo>();
|
||||
}
|
||||
|
||||
static QSharedPointer<DataTypeInfo> fromIdlDataType(const QString& name, const QString& idlType,
|
||||
StructBuilder* structBuilder,
|
||||
const QString& svcName = QString());
|
||||
|
||||
protected:
|
||||
const QString m_name;
|
||||
const bool m_isPod;
|
||||
};
|
||||
|
||||
struct IdlData : public IdlColumnInformation
|
||||
{
|
||||
QSharedPointer<DataTypeInfo> dataTypeInfo;
|
||||
|
||||
IdlData() = default;
|
||||
|
||||
explicit IdlData(const IdlColumnInformation& ci) : IdlColumnInformation(ci) {}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return name + QLatin1Char(';') + type +
|
||||
|
||||
QLatin1Char(';') + (isKey ? QLatin1String("true") : QLatin1String("false")) + QLatin1Char(';') +
|
||||
(isOptional ? QLatin1String("true") : QLatin1String("false"));
|
||||
}
|
||||
};
|
||||
|
||||
struct CppData
|
||||
{
|
||||
QString structName;
|
||||
QString memberName;
|
||||
QString defaultValue;
|
||||
bool usedForCppStruct = false;
|
||||
bool usedForJson = false;
|
||||
bool isReadOnly = false;
|
||||
bool isOptional = false;
|
||||
QString type;
|
||||
QString ddsType;
|
||||
QString toVariantPre;
|
||||
QString toVariantPost;
|
||||
QString fromVariantPre1;
|
||||
QString fromVariantPre2;
|
||||
QString fromVariantPost;
|
||||
|
||||
QString toJsonPre;
|
||||
QString toJsonPost;
|
||||
QString fromJsonPre2;
|
||||
QString fromJsonPost;
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return structName + QLatin1Char(';') + memberName + QLatin1Char(';') +
|
||||
(usedForCppStruct ? QLatin1String("true") : QLatin1String("false")) + QLatin1Char(';') +
|
||||
(isReadOnly ? QLatin1String("true") : QLatin1String("false")) + QLatin1Char(';') + type +
|
||||
QLatin1Char(';') + toVariantPre + QLatin1Char(';') + toVariantPost + QLatin1Char(';') + fromVariantPre1 +
|
||||
QLatin1Char(';') + fromVariantPre2 + QLatin1Char(';') + fromVariantPost;
|
||||
}
|
||||
};
|
||||
|
||||
struct MemberInformation
|
||||
{
|
||||
EaData ea;
|
||||
FullQualifiedColumnInformation db;
|
||||
CppData cpp;
|
||||
bool isDdsKey = false;
|
||||
QSharedPointer<ForeignKeyInformation> fk;
|
||||
QSharedPointer<EnumInformation> enumeration;
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return ea.toString() + QLatin1Char(';') + db.toString() + QLatin1Char(';') + cpp.toString();
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return ea.column.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct CompleteStructInformation
|
||||
{
|
||||
QString structName;
|
||||
QString viewName;
|
||||
QSet<QSharedPointer<TableInformation>> tables;
|
||||
QSharedPointer<TableInformation> primaryTable;
|
||||
QVector<MemberInformation> member;
|
||||
QVector<DbNotifcationInformation> dbNotifications;
|
||||
bool hasStaticContent = false;
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return primaryTable.isNull();
|
||||
}
|
||||
};
|
||||
|
||||
struct CompleteTopicTypeInformation final : public TopicTypeInformationBase
|
||||
{
|
||||
QString serviceName;
|
||||
|
||||
QVector<IdlData> members;
|
||||
|
||||
CompleteTopicTypeInformation() = default;
|
||||
|
||||
explicit CompleteTopicTypeInformation(const TopicTypeInformationBase& s) : TopicTypeInformationBase(s) {}
|
||||
|
||||
bool hasBaseValue() const
|
||||
{
|
||||
return !baseValueType.isEmpty();
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return TopicTypeInformationBase::isValid() && !members.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct CompleteTableInformation : public TableInformation
|
||||
{
|
||||
QVector<MemberInformation> columns;
|
||||
|
||||
explicit CompleteTableInformation(const TableInformation& o) : TableInformation(o) {}
|
||||
|
||||
CompleteTableInformation() = default;
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return columns.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
class CODEGEN_BASE_EXPORT StructBuilder
|
||||
{
|
||||
struct ColumnInformationEx : public ColumnInformation
|
||||
{
|
||||
QSharedPointer<TableInformation> table;
|
||||
|
||||
ColumnInformationEx() = default;
|
||||
|
||||
ColumnInformationEx(const ColumnInformation& o, const QSharedPointer<TableInformation>& ti)
|
||||
: ColumnInformation(o), table(ti)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
struct ServiceTypeStruct
|
||||
{
|
||||
QString service;
|
||||
QString type;
|
||||
bool bIsSequence = false;
|
||||
|
||||
bool isExternalType() const
|
||||
{
|
||||
return !service.isEmpty();
|
||||
}
|
||||
|
||||
bool isSequence() const
|
||||
{
|
||||
return bIsSequence;
|
||||
}
|
||||
|
||||
QString fqType() const
|
||||
{
|
||||
return service + QStringLiteral("::") + type;
|
||||
}
|
||||
};
|
||||
|
||||
explicit StructBuilder(XmlReader* xmlReader);
|
||||
~StructBuilder() = default;
|
||||
|
||||
bool buildStructures();
|
||||
|
||||
QVector<CompleteStructInformation> structInformations() const
|
||||
{
|
||||
return m_structInfos;
|
||||
}
|
||||
|
||||
QVector<CompleteTopicTypeInformation> topicTypeInformations() const
|
||||
{
|
||||
return m_topicTypeInfos;
|
||||
}
|
||||
|
||||
CompleteStructInformation structInformationByTableName(const QString& tableName) const;
|
||||
|
||||
EnumInformation enumInformationByType(const QString& type, bool checkExternals = true, bool verbose = true);
|
||||
|
||||
CompleteTopicTypeInformation topicTypeInformationByStructType(const QString& type, bool checkExternals = true);
|
||||
CompleteTopicTypeInformation externalTopicTypeByStructType(const ServiceTypeStruct& sts);
|
||||
|
||||
QVector<EnumInformation> enumInformations() const
|
||||
{
|
||||
return m_enumInfos;
|
||||
}
|
||||
|
||||
QMap<QString, CompleteTableInformation> getDbTables() const
|
||||
{
|
||||
return m_sqlTableInformations;
|
||||
}
|
||||
|
||||
QMap<QString, ViewInformation> getDbViews() const
|
||||
{
|
||||
return m_sqlViewInformations;
|
||||
}
|
||||
|
||||
bool needPoint3d() const
|
||||
{
|
||||
return m_needPoint3d;
|
||||
}
|
||||
|
||||
bool needPgArrayInclude() const
|
||||
{
|
||||
return m_needPgArrayInclude;
|
||||
}
|
||||
|
||||
CompleteTableInformation completeTableInformation(const CompleteTopicTypeInformation& ctti);
|
||||
CompleteTableInformation getRecordingTopicTable(const QString& structName);
|
||||
QMap<QString, CompleteTableInformation> getRecordingTopicTables();
|
||||
QVector<QSharedPointer<DataTypeInfo>> getSerializeFromToJsonTopics() const;
|
||||
bool fixupQVariantConversions(MemberInformation& mi) const;
|
||||
|
||||
Q_REQUIRED_RESULT static QString firstCharLowercase(const QString& txt);
|
||||
Q_REQUIRED_RESULT static QString camelCaseString(const QString& in, bool bFirstUppercase = false);
|
||||
Q_REQUIRED_RESULT static QString camelCaseToUnderline(const QString& in);
|
||||
|
||||
bool buildJSonStructureInformationFromIdlData(const CompleteTopicTypeInformation& ctti);
|
||||
|
||||
QMap<QString, QVector<MemberInformation>> jsonStructureInformation() const
|
||||
{
|
||||
return m_jsonStructureInformation;
|
||||
}
|
||||
|
||||
private:
|
||||
bool handleEnum(const EnumInformation& ei);
|
||||
bool handleTable(const ServiceStructInformation& ssi);
|
||||
bool handleIdlStructInfo(const TopicTypeInformation& tti, QVector<CompleteTopicTypeInformation>& destination,
|
||||
const QString& svcName = QString());
|
||||
bool loadExternalTopicType(const ServiceTypeStruct& sts);
|
||||
|
||||
bool getColumnsFromTable(const QString& tableName, QVector<ColumnInformationEx>& columns,
|
||||
QVector<QString>& tableNames) const;
|
||||
|
||||
bool fixupCustomDbDatatypes(MemberInformation& memberInfo, QVector<MemberInformation>& members);
|
||||
bool fixupDatatypePoint2d(MemberInformation& memberInfo, QVector<MemberInformation>& members);
|
||||
bool fixupDatatypePoint3d(MemberInformation& memberInfo, QVector<MemberInformation>& members);
|
||||
bool fixupDatatypeIntegerArray(MemberInformation& memberInfo);
|
||||
|
||||
QVector<MemberInformation> buildDbTableInformationFromIdlData(const CompleteTopicTypeInformation& ctti,
|
||||
const QString& tableName,
|
||||
const QString& columnNamePrefix,
|
||||
const QString& cppStructure);
|
||||
void addToSerializeFromToJsonTopics(const QSharedPointer<DataTypeInfo>& toAdd);
|
||||
CompleteTableInformation getRecordingTopicTable(const CompleteTopicTypeInformation& ctti,
|
||||
const MemberInformation& miPrimaryKey);
|
||||
MemberInformation getRecordPrimaryKey() const;
|
||||
|
||||
ServiceTypeStruct extractServiceAndTypeFromTypename(const QString& type);
|
||||
|
||||
void addToSqlTableInformation(const MemberInformation& mi);
|
||||
|
||||
private:
|
||||
QVector<CompleteStructInformation> m_structInfos;
|
||||
QVector<CompleteTopicTypeInformation> m_topicTypeInfos;
|
||||
mutable QVector<CompleteTopicTypeInformation> m_externalTopicTypes;
|
||||
QVector<EnumInformation> m_enumInfos;
|
||||
QMap<QString, CompleteTableInformation> m_sqlTableInformations;
|
||||
QMap<QString, ViewInformation> m_sqlViewInformations;
|
||||
QSet<QString> m_sqlTableInformationsAlreadyAvailable;
|
||||
QVector<QSharedPointer<DataTypeInfo>> m_serializeFromToJsonTopics;
|
||||
|
||||
QMap<QString, QVector<MemberInformation>> m_jsonStructureInformation;
|
||||
|
||||
bool m_needPoint3d;
|
||||
bool m_needPgArrayInclude;
|
||||
|
||||
XmlReader* m_xmlReader;
|
||||
NGL::INGLLogger& m_logger;
|
||||
NGL::INGLLogger& m_loggerCSV;
|
||||
};
|
||||
} // namespace CodeGen
|
||||
@@ -0,0 +1,893 @@
|
||||
|
||||
|
||||
#include "XmlReader.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <QDirIterator>
|
||||
#include <QtXml/QDomDocument>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QXmlStreamReader>
|
||||
#include <ngltools/ngltools_filesystem.h>
|
||||
#include <utility>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
class XmlReaderException : public std::exception
|
||||
{
|
||||
public:
|
||||
explicit XmlReaderException(const QString& msg) noexcept : m_msg(msg.toUtf8()) {}
|
||||
|
||||
~XmlReaderException() = default;
|
||||
|
||||
virtual const char* what() const noexcept
|
||||
{
|
||||
return m_msg.data();
|
||||
}
|
||||
|
||||
private:
|
||||
const QByteArray m_msg;
|
||||
};
|
||||
|
||||
XmlReader::XmlReader() : m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.XmlReader")) {}
|
||||
|
||||
bool XmlReader::readServiceFile(const QString& svcFile, const QString& idlServiceInfoFile, const XmlReader* parent)
|
||||
{
|
||||
m_externalXmlInformations.clear();
|
||||
m_idlFiles.clear();
|
||||
m_xmlFiles.clear();
|
||||
m_usedServices.clear();
|
||||
m_fullServiceName2Abbreviation.clear();
|
||||
|
||||
m_svcNamespace.clear();
|
||||
m_svcAbbr.clear();
|
||||
m_svcProcNames.clear();
|
||||
m_customRecordReplay = false;
|
||||
m_exerciseDependency = ExerciseDependency::Exercise;
|
||||
m_dbClassTables.clear();
|
||||
m_struct2BaseClass.clear();
|
||||
m_structsToGenerate.clear();
|
||||
m_idlStructsToGenerate.clear();
|
||||
m_tables.clear();
|
||||
m_views.clear();
|
||||
m_enumerations.clear();
|
||||
m_functionTopics.clear();
|
||||
m_externalTopicTypes.clear();
|
||||
m_customFunctions.clear();
|
||||
m_prio = 0;
|
||||
m_svcDevMode = false;
|
||||
|
||||
if (parent)
|
||||
{
|
||||
m_idlFiles = parent->m_idlFiles;
|
||||
m_xmlFiles = parent->m_xmlFiles;
|
||||
m_fullServiceName2Abbreviation = parent->m_fullServiceName2Abbreviation;
|
||||
m_externalXmlInformations = parent->m_externalXmlInformations;
|
||||
}
|
||||
|
||||
loadIdlInfofile(idlServiceInfoFile);
|
||||
|
||||
QFile f(svcFile);
|
||||
if (!f.exists())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Service file '" << svcFile << "' not found!";
|
||||
return false;
|
||||
}
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
m_errorString =
|
||||
QLatin1String("Can not open service file '") + svcFile + QLatin1String("' for reading: ") + f.errorString();
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG(m_logger) << "SvcFile: " << svcFile;
|
||||
|
||||
QDomDocument doc;
|
||||
const auto parseResult = doc.setContent(&f);
|
||||
if (!parseResult)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing service file '") + svcFile + QLatin1String("' at line ") +
|
||||
QString::number(int(parseResult.errorLine)) + QLatin1Char(':') +
|
||||
QString::number(int(parseResult.errorColumn));
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
QDomElement domRoot = doc.documentElement();
|
||||
if (!domRoot.hasAttribute(QStringLiteral("namespace")))
|
||||
{
|
||||
m_errorString = QLatin1String("Xml file '") + svcFile +
|
||||
QLatin1String("' is not a valid service file: service name/namespace is missing");
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
m_svcNamespace = getAttribute(domRoot, QStringLiteral("namespace"));
|
||||
m_svcAbbr = getAttribute(domRoot, QStringLiteral("abbreviation")).toLower();
|
||||
m_svcProcNames = getAttribute(domRoot, QStringLiteral("procNames")).split(QLatin1Char(','));
|
||||
m_customRecordReplay = getAttributeBool(domRoot, QStringLiteral("customRecordReplay"), true, false);
|
||||
|
||||
QString devModeStr = getAttribute(domRoot, QStringLiteral("devMode"), true);
|
||||
m_svcDevMode = devModeStr == QStringLiteral("1") || devModeStr == QStringLiteral("true");
|
||||
INFO_LOG(m_logger) << "devMode: " << m_svcDevMode;
|
||||
|
||||
const QString groupNamesString = getAttribute(domRoot, QStringLiteral("groups"), true);
|
||||
if (!groupNamesString.isEmpty())
|
||||
{
|
||||
m_groupNames = groupNamesString.split(QLatin1Char(','), Qt::SkipEmptyParts);
|
||||
INFO_LOG(m_logger) << "groups: " << groupNamesString;
|
||||
}
|
||||
else
|
||||
m_groupNames.clear();
|
||||
|
||||
m_exerciseDependency = string2ExerciseDependency(getAttribute(domRoot, QStringLiteral("exerciseDependency"), true));
|
||||
|
||||
if (m_groupNames.contains(QStringLiteral("Core")) || m_groupNames.contains(QStringLiteral("Master")))
|
||||
{
|
||||
if (m_exerciseDependency != ExerciseDependency::Global &&
|
||||
m_exerciseDependency != ExerciseDependency::GlobalAndExercise)
|
||||
{
|
||||
WARNING_LOG(m_logger)
|
||||
<< "Service " << getServiceNamespace()
|
||||
<< " is not marked as exercise independent but should be started in group Core/Master";
|
||||
}
|
||||
}
|
||||
|
||||
INFO_LOG(m_logger) << "exerciseDependency: " << (int)m_exerciseDependency;
|
||||
if (domRoot.hasAttribute(QStringLiteral("prio")))
|
||||
{
|
||||
m_prio = getAttribute(domRoot, QStringLiteral("prio"), 0).toInt();
|
||||
}
|
||||
try
|
||||
{
|
||||
parseDBSserviceStructs(domRoot.firstChildElement(QStringLiteral("dbServiceStructs")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing dbServiceStructs: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseDBTables(domRoot.firstChildElement(QStringLiteral("tables")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing dbtables: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseDBViews(domRoot.firstChildElement(QStringLiteral("views")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing dbviews: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseEnumerations(domRoot.firstChildElement(QStringLiteral("enumerations")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing enumerations: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseCustomDtcFunctions(domRoot.firstChildElement(QStringLiteral("customDtcFunctions")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing custom dtc functions: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseTopicTypes(domRoot.firstChildElement(QStringLiteral("topicTypes")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing idl strucs: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseFunctionTopics(domRoot.firstChildElement(QStringLiteral("functionTopics")));
|
||||
}
|
||||
catch (const XmlReaderException& ex)
|
||||
{
|
||||
m_errorString = QLatin1String("Error parsing function topics: ") + QLatin1String(ex.what());
|
||||
ERROR_LOG(m_logger) << m_errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString XmlReader::getAttribute(const QDomElement& element, const QString& attributeName, bool bOptional)
|
||||
{
|
||||
if (!element.hasAttribute(attributeName))
|
||||
{
|
||||
if (bOptional)
|
||||
return QString();
|
||||
throw XmlReaderException(QLatin1String("Element ") + element.tagName() + QLatin1String(" has no attribute ") +
|
||||
attributeName);
|
||||
}
|
||||
return element.attribute(attributeName);
|
||||
}
|
||||
|
||||
bool XmlReader::getAttributeBool(const QDomElement& element, const QString& attributeName, bool bOptional,
|
||||
bool defaultValue)
|
||||
{
|
||||
QString str = getAttribute(element, attributeName, bOptional);
|
||||
if (str.isEmpty())
|
||||
return defaultValue;
|
||||
bool bOk;
|
||||
bool ret = (str.toInt(&bOk) != 0);
|
||||
if (!bOk)
|
||||
throw XmlReaderException(QLatin1String("Can not parse boolean value: ") + str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int XmlReader::getAttributeInt(const QDomElement& element, const QString& attributeName, bool bOptional,
|
||||
int defaultValue)
|
||||
{
|
||||
QString str = getAttribute(element, attributeName, bOptional);
|
||||
if (str.isEmpty())
|
||||
return defaultValue;
|
||||
bool bOk;
|
||||
int ret = str.toInt(&bOk);
|
||||
if (!bOk)
|
||||
{
|
||||
|
||||
ret = str.toUInt(&bOk, 16);
|
||||
if (!bOk)
|
||||
throw XmlReaderException(QLatin1String("Can not parse int value: ") + str);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void XmlReader::parseDBSserviceStructs(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domDbStruct = rootElement.firstChildElement(QStringLiteral("serviceStruct"));
|
||||
while (!domDbStruct.isNull())
|
||||
{
|
||||
ServiceStructInformation ssi;
|
||||
ssi.name = getAttribute(domDbStruct, QStringLiteral("name"));
|
||||
ssi.primaryTable = getAttribute(domDbStruct, QStringLiteral("table"));
|
||||
ssi.hasStaticContent = getAttributeBool(domDbStruct, QStringLiteral("static"), true, false);
|
||||
|
||||
QDomElement domFunc = domDbStruct.firstChildElement(QStringLiteral("function"));
|
||||
while (!domFunc.isNull())
|
||||
{
|
||||
m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Db);
|
||||
domFunc = domFunc.nextSiblingElement(domFunc.tagName());
|
||||
}
|
||||
|
||||
QDomElement domNotify = domDbStruct.firstChildElement(QStringLiteral("notification"));
|
||||
while (!domNotify.isNull())
|
||||
{
|
||||
ssi.dbNotifications.append(parseDbNotification(domNotify));
|
||||
domNotify = domNotify.nextSiblingElement(domNotify.tagName());
|
||||
}
|
||||
|
||||
m_structsToGenerate.push_back(ssi);
|
||||
|
||||
domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName());
|
||||
}
|
||||
std::sort(m_structsToGenerate.begin(), m_structsToGenerate.end());
|
||||
|
||||
domDbStruct = rootElement.firstChildElement(QStringLiteral("table"));
|
||||
while (!domDbStruct.isNull())
|
||||
{
|
||||
const QString table = getAttribute(domDbStruct, QStringLiteral("name"));
|
||||
const QString parentTable = getAttribute(domDbStruct, QStringLiteral("base"), true);
|
||||
m_struct2BaseClass[table] = parentTable;
|
||||
|
||||
domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName());
|
||||
}
|
||||
INFO_LOG(m_logger) << "Read " << m_structsToGenerate.size() << " database service struct(s)";
|
||||
}
|
||||
|
||||
void XmlReader::parseDBTables(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domDbTable = rootElement.firstChildElement(QStringLiteral("table"));
|
||||
while (!domDbTable.isNull())
|
||||
{
|
||||
QSharedPointer<TableInformation> ti(new TableInformation);
|
||||
ti->name = getAttribute(domDbTable, QStringLiteral("name"));
|
||||
ti->structName = QLatin1String("t_") + ti->name;
|
||||
ti->comment = getAttribute(domDbTable, QStringLiteral("comment"), true);
|
||||
ti->exerciseDependency =
|
||||
string2ExerciseDependency(getAttribute(domDbTable, QStringLiteral("exerciseDependency"), true));
|
||||
|
||||
QDomElement domColumn = domDbTable.firstChildElement(QStringLiteral("column"));
|
||||
while (!domColumn.isNull())
|
||||
{
|
||||
ti->plainColumns.append(parseTableColumn(domColumn, ti->name));
|
||||
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
|
||||
}
|
||||
|
||||
QDomElement domForeignKey = domDbTable.firstChildElement(QStringLiteral("foreignKey"));
|
||||
while (!domForeignKey.isNull())
|
||||
{
|
||||
ti->foreignKeys.append(parseForeignKey(domForeignKey));
|
||||
domForeignKey = domForeignKey.nextSiblingElement(domForeignKey.tagName());
|
||||
}
|
||||
QDomElement domConstraint = domDbTable.firstChildElement(QStringLiteral("constraint"));
|
||||
while (!domConstraint.isNull())
|
||||
{
|
||||
ti->constraints.append(parseConstraint(domConstraint));
|
||||
domConstraint = domConstraint.nextSiblingElement(domConstraint.tagName());
|
||||
}
|
||||
|
||||
m_tables.insert(ti->name, ti);
|
||||
|
||||
domDbTable = domDbTable.nextSiblingElement(domDbTable.tagName());
|
||||
}
|
||||
|
||||
INFO_LOG(m_logger) << "Read " << m_tables.size() << " database table(s)";
|
||||
}
|
||||
|
||||
void XmlReader::parseDBViews(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domDbView = rootElement.firstChildElement(QStringLiteral("view"));
|
||||
while (!domDbView.isNull())
|
||||
{
|
||||
QSharedPointer<ViewInformation> vi(new ViewInformation);
|
||||
vi->name = getAttribute(domDbView, QStringLiteral("name"));
|
||||
vi->structName = QLatin1String("v_") + vi->name;
|
||||
vi->baseSchema = m_svcAbbr;
|
||||
vi->baseTable = getAttribute(domDbView, QStringLiteral("base"));
|
||||
vi->comment = getAttribute(domDbView, QStringLiteral("comment"), true);
|
||||
vi->filter = getAttribute(domDbView, QStringLiteral("filter"));
|
||||
vi->exerciseDependency =
|
||||
string2ExerciseDependency(getAttribute(domDbView, QStringLiteral("exerciseDependency"), true));
|
||||
|
||||
DEBUG_LOG(m_logger) << "Base table = " << vi->baseTable;
|
||||
|
||||
m_views.insert(vi->name, vi);
|
||||
|
||||
domDbView = domDbView.nextSiblingElement(domDbView.tagName());
|
||||
}
|
||||
|
||||
INFO_LOG(m_logger) << "Read " << m_views.size() << " database view(s)";
|
||||
}
|
||||
|
||||
void XmlReader::parseEnumerations(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domEnum = rootElement.firstChildElement(QStringLiteral("enumeration"));
|
||||
while (!domEnum.isNull())
|
||||
{
|
||||
EnumInformation enumeration;
|
||||
enumeration.serviceName = getServiceNamespace();
|
||||
enumeration.enumName = getAttribute(domEnum, QStringLiteral("name"));
|
||||
enumeration.tableName = getAttribute(domEnum, QStringLiteral("table"), true);
|
||||
|
||||
QDomElement enumVal = domEnum.firstChildElement(QStringLiteral("attribute"));
|
||||
while (!enumVal.isNull())
|
||||
{
|
||||
EnumKeyValuePair evp;
|
||||
evp.key = getAttribute(enumVal, QStringLiteral("key"));
|
||||
evp.value = getAttributeInt(enumVal, QStringLiteral("value"));
|
||||
enumeration.values.append(evp);
|
||||
|
||||
enumVal = enumVal.nextSiblingElement(enumVal.tagName());
|
||||
}
|
||||
|
||||
m_enumerations.push_back(enumeration);
|
||||
|
||||
domEnum = domEnum.nextSiblingElement(domEnum.tagName());
|
||||
}
|
||||
INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " enumeration(s)";
|
||||
}
|
||||
|
||||
void XmlReader::parseCustomDtcFunctions(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domElement = rootElement.firstChildElement(QStringLiteral("customDtcFunction"));
|
||||
while (!domElement.isNull())
|
||||
{
|
||||
|
||||
QDomElement domFunc = domElement.firstChildElement(QStringLiteral("function"));
|
||||
while (!domFunc.isNull())
|
||||
{
|
||||
m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Dtc);
|
||||
domFunc = domFunc.nextSiblingElement(domFunc.tagName());
|
||||
}
|
||||
domElement = domElement.nextSiblingElement(domElement.tagName());
|
||||
}
|
||||
INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " customDtcFunction(s)";
|
||||
}
|
||||
|
||||
void XmlReader::parseTopicTypes(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domIdlStruct = rootElement.firstChildElement(QStringLiteral("topicType"));
|
||||
while (!domIdlStruct.isNull())
|
||||
{
|
||||
TopicTypeInformation isi;
|
||||
isi.name = getAttribute(domIdlStruct, QStringLiteral("name"));
|
||||
isi.baseValueType = getAttribute(domIdlStruct, QStringLiteral("baseValueType"), true);
|
||||
|
||||
const QStringList sl = isi.baseValueType.split(QStringLiteral("::"));
|
||||
if (sl.size() == 2)
|
||||
loadExternalTopicType(sl.at(0), sl.at(1));
|
||||
|
||||
const QString tmp = getAttribute(domIdlStruct, QStringLiteral("extensibility"), true);
|
||||
|
||||
#ifdef RTI_HEADER_AVAILABLE
|
||||
if (tmp.compare(QLatin1String("FINAL"), Qt::CaseInsensitive) == 0)
|
||||
isi.extensibility = dds::core::xtypes::ExtensibilityKind::FINAL;
|
||||
if (tmp.compare(QLatin1String("MUTABLE"), Qt::CaseInsensitive) == 0)
|
||||
isi.extensibility = dds::core::xtypes::ExtensibilityKind::MUTABLE;
|
||||
if (tmp.compare(QLatin1String("EXTENSIBLE"), Qt::CaseInsensitive) == 0)
|
||||
isi.extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE;
|
||||
#else
|
||||
// RTI not available - skip extensibility parsing
|
||||
Q_UNUSED(tmp)
|
||||
#endif
|
||||
|
||||
isi.comment = getAttribute(domIdlStruct, QStringLiteral("comment"), true);
|
||||
isi.isDataObject = getAttributeBool(domIdlStruct, QStringLiteral("isDataObject"), true, false);
|
||||
isi.dtsAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTS"), true, true);
|
||||
isi.dtcAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTC"), true, true);
|
||||
isi.recordTopic = getAttributeBool(domIdlStruct, QStringLiteral("recordTopic"), true, false);
|
||||
isi.routeToClient = getAttributeBool(domIdlStruct, QStringLiteral("routeToClient"), true, true);
|
||||
isi.routeToServer = getAttributeBool(domIdlStruct, QStringLiteral("routeToServer"), true, false);
|
||||
isi.routeInReplay = getAttributeBool(domIdlStruct, QStringLiteral("routeInReplay"), true, false);
|
||||
isi.autosubscribe = getAttributeBool(domIdlStruct, QStringLiteral("autosubscribe"), true, true);
|
||||
|
||||
QDomElement domColumn = domIdlStruct.firstChildElement(QStringLiteral("column"));
|
||||
while (!domColumn.isNull())
|
||||
{
|
||||
isi.columns.append(parseIdlStructColumn(domColumn));
|
||||
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
|
||||
}
|
||||
|
||||
m_idlStructsToGenerate.insert(isi.name, isi);
|
||||
|
||||
domIdlStruct = domIdlStruct.nextSiblingElement(domIdlStruct.tagName());
|
||||
}
|
||||
|
||||
INFO_LOG(m_logger) << "Read " << m_idlStructsToGenerate.size() << " idl struct(s)";
|
||||
}
|
||||
|
||||
void XmlReader::parseFunctionTopics(const QDomElement& rootElement)
|
||||
{
|
||||
if (rootElement.isNull())
|
||||
return;
|
||||
|
||||
QDomElement domFunctionTopic = rootElement.firstChildElement(QStringLiteral("functionTopic"));
|
||||
while (!domFunctionTopic.isNull())
|
||||
{
|
||||
FunctionTopic ft;
|
||||
ft.name = getAttribute(domFunctionTopic, QStringLiteral("name"));
|
||||
ft.requestTopicType = getAttribute(domFunctionTopic, QStringLiteral("requestTopicType"));
|
||||
ft.replyTopicType = getAttribute(domFunctionTopic, QStringLiteral("replyTopicType"));
|
||||
ft.requestTopicName = getAttribute(domFunctionTopic, QStringLiteral("requestTopicName"));
|
||||
ft.replyTopicName = getAttribute(domFunctionTopic, QStringLiteral("replyTopicName"));
|
||||
ft.routeInReplay = getAttributeBool(domFunctionTopic, QStringLiteral("routeInReplay"), true, false);
|
||||
|
||||
m_functionTopics.append(ft);
|
||||
|
||||
domFunctionTopic = domFunctionTopic.nextSiblingElement(domFunctionTopic.tagName());
|
||||
}
|
||||
|
||||
for (const FunctionTopic& ft : std::as_const(m_functionTopics))
|
||||
{
|
||||
const QStringList sl1 = ft.requestTopicType.split(QStringLiteral("::"));
|
||||
if (sl1.size() == 2)
|
||||
loadExternalTopicType(sl1.at(0), sl1.at(1));
|
||||
const QStringList sl2 = ft.replyTopicType.split(QStringLiteral("::"));
|
||||
if (sl2.size() == 2)
|
||||
loadExternalTopicType(sl2.at(0), sl2.at(1));
|
||||
}
|
||||
}
|
||||
|
||||
ColumnInformation XmlReader::parseTableColumn(const QDomElement& column, const QString& tableName) const
|
||||
{
|
||||
ColumnInformation ci;
|
||||
|
||||
ci.columnName = getAttribute(column, QStringLiteral("name"));
|
||||
ci.datatype = getAttribute(column, QStringLiteral("datatype"));
|
||||
ci.defaultValue = getAttribute(column, QStringLiteral("default"), true);
|
||||
ci.comment = getAttribute(column, QStringLiteral("comment"), true);
|
||||
ci.isPrimaryKey = getAttributeBool(column, QStringLiteral("isPrimaryKey"), true, false);
|
||||
ci.isForeignKey = getAttributeBool(column, QStringLiteral("isForeignKey"), true, false);
|
||||
ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false);
|
||||
const QDomElement seq = column.firstChildElement(QStringLiteral("sequence"));
|
||||
if (!seq.isNull())
|
||||
{
|
||||
ci.sequenceStart = getAttributeInt(seq, QStringLiteral("start"), true, -1);
|
||||
ci.sequenceIncrement = getAttributeInt(seq, QStringLiteral("increment"), true, -1);
|
||||
ci.hasSequence = ci.sequenceStart >= 0 && ci.sequenceIncrement > 0;
|
||||
}
|
||||
|
||||
if (ci.columnName.toLower() != ci.columnName)
|
||||
WARNING_LOG(m_logger) << "Column " << ci.columnName << " in Table " << tableName
|
||||
<< " has has upper-case chars which is not allowed!";
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
IdlColumnInformation XmlReader::parseIdlStructColumn(const QDomElement& column) const
|
||||
{
|
||||
IdlColumnInformation ci;
|
||||
|
||||
ci.name = getAttribute(column, QStringLiteral("name"));
|
||||
ci.type = getAttribute(column, QStringLiteral("idlType"));
|
||||
ci.comment = getAttribute(column, QStringLiteral("comment"), true);
|
||||
ci.isKey = getAttributeBool(column, QStringLiteral("isKey"), true, false);
|
||||
ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false);
|
||||
ci.isTranslatable = getAttributeBool(column, QStringLiteral("isTranslatable"), true, false);
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
QSharedPointer<ForeignKeyInformation> XmlReader::parseForeignKey(const QDomElement& foreignKey) const
|
||||
{
|
||||
QSharedPointer<ForeignKeyInformation> fk(new ForeignKeyInformation);
|
||||
|
||||
fk->name = getAttribute(foreignKey, QStringLiteral("name"));
|
||||
|
||||
const QString deleteAction = getAttribute(foreignKey, QStringLiteral("referential_integrity"), true);
|
||||
if (deleteAction == QLatin1String("DeleteNoAction"))
|
||||
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::NoAction;
|
||||
else if (deleteAction == QLatin1String("DeleteCascade"))
|
||||
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade;
|
||||
else if (deleteAction == QLatin1String("DeleteRestrict"))
|
||||
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Restrict;
|
||||
else if (deleteAction == QLatin1String("DeleteSetNull"))
|
||||
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetNull;
|
||||
else if (deleteAction == QLatin1String("DeleteSetDefault"))
|
||||
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetDefault;
|
||||
else
|
||||
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade;
|
||||
|
||||
fk->source = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("source")));
|
||||
fk->destination = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("destination")));
|
||||
|
||||
return fk;
|
||||
}
|
||||
|
||||
ForeignKeyInformation::Endpoint XmlReader::parseForeignKeyEndpoint(const QString& foreignKeyName,
|
||||
const QDomElement& endpoint) const
|
||||
{
|
||||
if (endpoint.isNull())
|
||||
throw XmlReaderException(QLatin1String("No endpoint for foreign key ") + foreignKeyName);
|
||||
|
||||
ForeignKeyInformation::Endpoint fkEndpoint;
|
||||
|
||||
fkEndpoint.service = getAttribute(endpoint, QStringLiteral("service"), true);
|
||||
fkEndpoint.serviceAbbr = getAttribute(endpoint, QStringLiteral("serviceAbbr"), true);
|
||||
fkEndpoint.schema = getAttribute(endpoint, QStringLiteral("schema"), true);
|
||||
fkEndpoint.tableName = getAttribute(endpoint, QStringLiteral("table"));
|
||||
fkEndpoint.columnName = getAttribute(endpoint, QStringLiteral("column"));
|
||||
|
||||
return fkEndpoint;
|
||||
}
|
||||
|
||||
ConstraintInformation XmlReader::parseConstraint(const QDomElement& constraint) const
|
||||
{
|
||||
ConstraintInformation ret;
|
||||
|
||||
ret.name = getAttribute(constraint, QStringLiteral("name"), true);
|
||||
const QString type = getAttribute(constraint, QStringLiteral("type")).toLower();
|
||||
if (type == QLatin1String("unique"))
|
||||
ret.type = ConstraintInformation::Type::unique;
|
||||
else if (type == QLatin1String("index"))
|
||||
ret.type = ConstraintInformation::Type::index;
|
||||
|
||||
QDomElement domColumn = constraint.firstChildElement(QStringLiteral("column"));
|
||||
if (domColumn.isNull())
|
||||
throw XmlReaderException(QLatin1String("No column for constraint") + ret.name);
|
||||
while (!domColumn.isNull())
|
||||
{
|
||||
ret.affectedColumns += getAttribute(domColumn, QStringLiteral("name"));
|
||||
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CustomServiceFunctionInformation XmlReader::parseCustomFunction(const QDomElement& customFunc,
|
||||
CustomServiceFunctionInformation::Type funcType) const
|
||||
{
|
||||
CustomServiceFunctionInformation ret;
|
||||
ret.methodName = getAttribute(customFunc, QStringLiteral("name"));
|
||||
ret.returnValue = getAttribute(customFunc, QStringLiteral("returns"), true);
|
||||
ret.isConst = getAttributeBool(customFunc, QStringLiteral("isConst"), true, false);
|
||||
if (ret.returnValue.isEmpty())
|
||||
ret.returnValue = QStringLiteral("bool");
|
||||
QMap<int, QPair<QString, QString>> paramHelper;
|
||||
QDomElement domParam = customFunc.firstChildElement(QStringLiteral("parameter"));
|
||||
while (!domParam.isNull())
|
||||
{
|
||||
int pos = getAttributeInt(domParam, QStringLiteral("pos"));
|
||||
if (paramHelper.contains(pos))
|
||||
throw XmlReaderException(QLatin1String("Doubled position entry for custom function ") + ret.methodName);
|
||||
QString paramName = getAttribute(domParam, QStringLiteral("name"));
|
||||
const QString paramType = getAttribute(domParam, QStringLiteral("type"));
|
||||
const QString paramDefault = getAttribute(domParam, QStringLiteral("default"), true);
|
||||
if (!paramDefault.isEmpty())
|
||||
paramName.append(QLatin1String(" = ") + paramDefault);
|
||||
paramHelper[pos] = qMakePair(paramName, paramType);
|
||||
domParam = domParam.nextSiblingElement(domParam.tagName());
|
||||
}
|
||||
ret.paramNames.reserve(paramHelper.size());
|
||||
ret.paramTypes.reserve(paramHelper.size());
|
||||
ret.type = funcType;
|
||||
QStringList signature;
|
||||
for (auto it = paramHelper.constBegin(); it != paramHelper.constEnd(); ++it)
|
||||
{
|
||||
ret.paramNames += it.value().first;
|
||||
ret.paramTypes += it.value().second;
|
||||
QString tmp = it.value().second;
|
||||
if (!it.value().second.endsWith(QLatin1Char('&')))
|
||||
tmp += QLatin1Char(' ');
|
||||
tmp += it.value().first;
|
||||
signature += tmp;
|
||||
}
|
||||
ret.signature += signature.join(QStringLiteral(", "));
|
||||
return ret;
|
||||
}
|
||||
|
||||
DbNotifcationInformation XmlReader::parseDbNotification(const QDomElement& customFunc) const
|
||||
{
|
||||
DbNotifcationInformation ret(parseCustomFunction(customFunc, CustomServiceFunctionInformation::Type::DbNotify));
|
||||
|
||||
const QString attribute(getAttribute(customFunc, QStringLiteral("notificationTypes")).toLower());
|
||||
const QStringList nfTypes = attribute.split(QLatin1Char(','));
|
||||
static const QMap<QString, DbNotifcationInformation::NotificationType> s_nfTypeMap = {
|
||||
{QLatin1String("beforeinsert"), DbNotifcationInformation::NotificationType::BeforeInsert},
|
||||
{QLatin1String("afterinsert"), DbNotifcationInformation::NotificationType::AfterInsert},
|
||||
{QLatin1String("beforeupdate"), DbNotifcationInformation::NotificationType::BeforeUpdate},
|
||||
{QLatin1String("afterupdate"), DbNotifcationInformation::NotificationType::AfterUpdate},
|
||||
{QLatin1String("beforedelete"), DbNotifcationInformation::NotificationType::BeforeDelete},
|
||||
{QLatin1String("afterdelete"), DbNotifcationInformation::NotificationType::AfterDelete}};
|
||||
for (const QString& nfType : nfTypes)
|
||||
{
|
||||
const auto it = s_nfTypeMap.find(nfType);
|
||||
if (it == s_nfTypeMap.end())
|
||||
throw XmlReaderException(QStringLiteral("Can not find notification type %2 in type map for function %1")
|
||||
.arg(ret.methodName, nfType));
|
||||
ret.notificationType += it.value();
|
||||
}
|
||||
if (ret.notificationType.isEmpty())
|
||||
throw XmlReaderException(
|
||||
QStringLiteral("Notification function '%1' without a valid notification type").arg(ret.methodName));
|
||||
if (ret.paramTypes.size() != 1)
|
||||
throw XmlReaderException(
|
||||
QStringLiteral("Notification function '%1' must have exactly one parameter").arg(ret.methodName));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TopicTypeInformation XmlReader::loadExternalTopicType(const QString& externalServiceName, const QString& topicTypeName)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << topicTypeName;
|
||||
|
||||
if (!loadExternalService(externalServiceName))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not find service " << externalServiceName << " referenced by external topic "
|
||||
<< topicTypeName;
|
||||
return TopicTypeInformation();
|
||||
}
|
||||
auto extXmlReader = m_externalXmlInformations.value(externalServiceName);
|
||||
if (!extXmlReader)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'";
|
||||
return TopicTypeInformation();
|
||||
}
|
||||
|
||||
TopicTypeInformation tti = extXmlReader->getIdlStructInformation().value(topicTypeName);
|
||||
if (tti.isValid())
|
||||
{
|
||||
tti.name = externalServiceName + QLatin1String("::") + tti.name;
|
||||
m_externalTopicTypes += tti;
|
||||
m_usedServices[externalServiceName] = true;
|
||||
return tti;
|
||||
}
|
||||
WARNING_LOG(m_logger) << "Could not find topic " << topicTypeName << " in external service definition for service "
|
||||
<< externalServiceName;
|
||||
return TopicTypeInformation();
|
||||
}
|
||||
|
||||
EnumInformation XmlReader::getExternalEnumeration(const QString& externalServiceName, const QString& enumName,
|
||||
bool verbose)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << enumName;
|
||||
|
||||
if (!loadExternalService(externalServiceName))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not find service '" << externalServiceName << "'"
|
||||
<< " referenced by external enumeration " << enumName;
|
||||
return EnumInformation();
|
||||
}
|
||||
auto extXmlReader = m_externalXmlInformations.value(externalServiceName);
|
||||
if (!extXmlReader)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'";
|
||||
return EnumInformation();
|
||||
}
|
||||
|
||||
const QVector<EnumInformation> enums = extXmlReader->getEnumerations();
|
||||
for (const EnumInformation& ei : enums)
|
||||
{
|
||||
if (ei.enumName == enumName)
|
||||
{
|
||||
m_usedServices[externalServiceName] = true;
|
||||
return ei;
|
||||
}
|
||||
}
|
||||
if (verbose)
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not find enumeration " << enumName << " in external service definition for '"
|
||||
<< externalServiceName << "'";
|
||||
}
|
||||
return EnumInformation();
|
||||
}
|
||||
|
||||
bool XmlReader::loadExternalService(const QString& serviceName)
|
||||
{
|
||||
if (serviceName == getServiceNamespace())
|
||||
return false;
|
||||
if (m_externalXmlInformations.contains(serviceName))
|
||||
return true;
|
||||
|
||||
QString fileName = xmlFileForService(serviceName);
|
||||
if (fileName.isEmpty())
|
||||
{
|
||||
const QVector<QString> dirs = NGL::directories(NGL::FileType::ConfigFile);
|
||||
for (const QString& dirname : dirs)
|
||||
{
|
||||
QDirIterator dirIt(dirname + QStringLiteral("/xml"), QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
|
||||
while (dirIt.hasNext())
|
||||
{
|
||||
dirIt.next();
|
||||
if (!dirIt.fileName().endsWith(QLatin1String(".xml")))
|
||||
continue;
|
||||
|
||||
fileName = dirIt.filePath();
|
||||
const QString svcNamespace = serviceNamespaceFromFile(fileName);
|
||||
if (svcNamespace == serviceName)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fileName.isEmpty())
|
||||
return false;
|
||||
|
||||
DEBUG_LOG(m_logger) << "xml file for " << serviceName << ": " << fileName;
|
||||
auto extXmlReader = QSharedPointer<XmlReader>(new XmlReader);
|
||||
if (!extXmlReader->readServiceFile(fileName, QString(), this))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error reading service file " << fileName << " for service " << serviceName;
|
||||
return false;
|
||||
}
|
||||
m_externalXmlInformations[serviceName] = extXmlReader;
|
||||
if (!m_usedServices.contains(serviceName))
|
||||
m_usedServices[serviceName] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void XmlReader::loadIdlInfofile(const QString& idlServiceInfoFile)
|
||||
{
|
||||
if (idlServiceInfoFile.isEmpty())
|
||||
return;
|
||||
QFile f(idlServiceInfoFile);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Could not read idl service info file " << idlServiceInfoFile;
|
||||
return;
|
||||
}
|
||||
const QList<QByteArray> files = f.readAll().split('\n');
|
||||
for (const QByteArray& filePath : files)
|
||||
{
|
||||
|
||||
if (filePath.startsWith("--") || filePath.trimmed().isEmpty())
|
||||
continue;
|
||||
const QString fileName = QString::fromLocal8Bit(filePath.trimmed());
|
||||
const QStringList parts = fileName.split(QStringLiteral("/"));
|
||||
getServiceNameInfoFromFilePath(parts);
|
||||
const auto fn = parts.last().trimmed();
|
||||
if (fn.endsWith(QLatin1String(".xml")))
|
||||
{
|
||||
const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).split(QLatin1Char('_')).at(0);
|
||||
m_xmlFiles[svcAbbreveation] = fileName;
|
||||
DEBUG_LOG(m_logger) << "xml: " << svcAbbreveation << " = " << fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).toLower();
|
||||
if (svcAbbreveation.endsWith(QLatin1String("_enums")))
|
||||
continue;
|
||||
m_idlFiles[svcAbbreveation] = fileName;
|
||||
DEBUG_LOG(m_logger) << "idl: " << svcAbbreveation << " = " << fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString XmlReader::getIdlFileForService(const QMap<QString, QString>& data, const QString& serviceName) const
|
||||
{
|
||||
const QString serviceAbbr = serviceNameAbbreviation(serviceName);
|
||||
return data.value(serviceAbbr);
|
||||
}
|
||||
|
||||
QString XmlReader::serviceNameAbbreviation(const QString& serviceName) const
|
||||
{
|
||||
return m_fullServiceName2Abbreviation.value(serviceName);
|
||||
}
|
||||
|
||||
void XmlReader::getServiceNameInfoFromFilePath(const QStringList& parts)
|
||||
{
|
||||
if (parts.size() < 2)
|
||||
return;
|
||||
|
||||
const QString abbreviation = parts.last().split(QLatin1Char('.')).first().split(QLatin1Char('_')).first();
|
||||
const QString serviceName = parts.at(parts.size() - 2);
|
||||
DEBUG_LOG(m_logger) << "serviceName: " << serviceName << " = " << abbreviation;
|
||||
m_fullServiceName2Abbreviation[serviceName] = abbreviation;
|
||||
}
|
||||
|
||||
ExerciseDependency XmlReader::string2ExerciseDependency(const QString& str)
|
||||
{
|
||||
if (str.isEmpty())
|
||||
return ExerciseDependency::Undefined;
|
||||
const QStringList sd = str.toLower().split(QLatin1Char(','));
|
||||
int ret = ExerciseDependency::Undefined;
|
||||
for (const QString& s : sd)
|
||||
{
|
||||
if (s == QLatin1String("global"))
|
||||
ret |= ExerciseDependency::Global;
|
||||
else if (s == QLatin1String("exercise"))
|
||||
ret |= ExerciseDependency::Exercise;
|
||||
else if (s == QLatin1String("globalandexercise"))
|
||||
ret |= ExerciseDependency::GlobalAndExercise;
|
||||
}
|
||||
return static_cast<ExerciseDependency>(ret);
|
||||
}
|
||||
|
||||
QString XmlReader::serviceNamespaceFromFile(const QString& fn)
|
||||
{
|
||||
QFile f(fn);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return QString();
|
||||
QXmlStreamReader sr(&f);
|
||||
while (!sr.atEnd())
|
||||
{
|
||||
sr.readNext();
|
||||
if (sr.isStartElement() && sr.name() == QLatin1String("service"))
|
||||
return (sr.attributes().value(QLatin1String("namespace"))).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QStringView>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#ifdef RTI_DDS_AVAILABLE
|
||||
// Only include RTI headers if they are actually available
|
||||
#if __has_include(<dds/core/xtypes/EnumType.hpp>)
|
||||
#include <dds/core/xtypes/EnumType.hpp>
|
||||
#define RTI_HEADER_AVAILABLE 1
|
||||
#else
|
||||
#undef RTI_DDS_AVAILABLE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "codegen_base_export.h"
|
||||
|
||||
namespace NGL
|
||||
{
|
||||
class INGLLogger;
|
||||
}
|
||||
class QDomElement;
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum ExerciseDependency { Undefined = 0, Global = 0x1, Exercise = 0x2, GlobalAndExercise = 0x3 };
|
||||
|
||||
struct EnumKeyValuePair
|
||||
{
|
||||
QString key;
|
||||
int value = 0;
|
||||
};
|
||||
|
||||
struct EnumInformation
|
||||
{
|
||||
QString enumName;
|
||||
QString tableName;
|
||||
QString serviceName;
|
||||
QVector<EnumKeyValuePair> values;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !enumName.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct ColumnInformation
|
||||
{
|
||||
QString columnName;
|
||||
QString datatype;
|
||||
QString comment;
|
||||
QString defaultValue;
|
||||
bool isPrimaryKey = false;
|
||||
bool isAssignmentForPrimaryTablePk = false;
|
||||
bool isForeignKey = false;
|
||||
bool generateSelectRemove = false;
|
||||
bool canBeNull = true;
|
||||
bool hasSequence = false;
|
||||
bool isOptional = false;
|
||||
int sequenceStart = 0;
|
||||
int sequenceIncrement = 0;
|
||||
};
|
||||
|
||||
struct IdlColumnInformation
|
||||
{
|
||||
QString name;
|
||||
QString type;
|
||||
QString comment;
|
||||
bool isKey = false;
|
||||
bool isOptional = false;
|
||||
bool isTranslatable = false;
|
||||
};
|
||||
|
||||
struct ForeignKeyInformation
|
||||
{
|
||||
struct Endpoint
|
||||
{
|
||||
QString service;
|
||||
QString serviceAbbr;
|
||||
QString schema;
|
||||
QString tableName;
|
||||
QString columnName;
|
||||
};
|
||||
|
||||
QString name;
|
||||
Endpoint source;
|
||||
Endpoint destination;
|
||||
enum class OnDeleteAction {
|
||||
NoAction,
|
||||
Cascade,
|
||||
Restrict,
|
||||
SetNull,
|
||||
SetDefault
|
||||
} deleteAction = OnDeleteAction::Cascade;
|
||||
};
|
||||
|
||||
struct CustomServiceFunctionInformation
|
||||
{
|
||||
QString methodName;
|
||||
QString returnValue;
|
||||
QString signature;
|
||||
bool isConst = false;
|
||||
QVector<QString> paramTypes;
|
||||
QVector<QString> paramNames;
|
||||
enum class Type { Db, DbNotify, Dtc } type = Type::Db;
|
||||
};
|
||||
|
||||
struct DbNotifcationInformation : public CustomServiceFunctionInformation
|
||||
{
|
||||
DbNotifcationInformation() = default;
|
||||
|
||||
explicit DbNotifcationInformation(const CustomServiceFunctionInformation& d) : CustomServiceFunctionInformation(d)
|
||||
{
|
||||
}
|
||||
enum class NotificationType { BeforeInsert, AfterInsert, BeforeUpdate, AfterUpdate, BeforeDelete, AfterDelete };
|
||||
QVector<NotificationType> notificationType;
|
||||
};
|
||||
|
||||
struct ConstraintInformation
|
||||
{
|
||||
enum struct Type { unknown = 0x00, unique = 0x01, index = 0x02 } type = Type::unknown;
|
||||
QString name;
|
||||
QStringList affectedColumns;
|
||||
};
|
||||
|
||||
struct TableInformation
|
||||
{
|
||||
QString name;
|
||||
QString structName;
|
||||
QString comment;
|
||||
QVector<ColumnInformation> plainColumns;
|
||||
QVector<QSharedPointer<ForeignKeyInformation>> foreignKeys;
|
||||
QVector<ConstraintInformation> constraints;
|
||||
ExerciseDependency exerciseDependency = ExerciseDependency::Undefined;
|
||||
|
||||
QSharedPointer<ForeignKeyInformation> fkInformationForColumn(const QString& tableName,
|
||||
const QString& columnName) const
|
||||
{
|
||||
for (const QSharedPointer<ForeignKeyInformation>& fki : foreignKeys)
|
||||
{
|
||||
if (fki->source.columnName == columnName && fki->source.tableName == tableName)
|
||||
return fki;
|
||||
}
|
||||
return QSharedPointer<ForeignKeyInformation>();
|
||||
}
|
||||
};
|
||||
|
||||
struct ViewInformation
|
||||
{
|
||||
QString name;
|
||||
QString comment;
|
||||
QString structName;
|
||||
QString baseTable;
|
||||
QString baseSchema;
|
||||
QString filter;
|
||||
ExerciseDependency exerciseDependency = ExerciseDependency::Undefined;
|
||||
};
|
||||
|
||||
struct ServiceStructInformation
|
||||
{
|
||||
QString name;
|
||||
QString primaryTable;
|
||||
bool hasStaticContent = false;
|
||||
|
||||
bool operator<(const ServiceStructInformation& o) const
|
||||
{
|
||||
if (hasStaticContent != o.hasStaticContent)
|
||||
return hasStaticContent < o.hasStaticContent;
|
||||
if (name != o.name)
|
||||
return name < o.name;
|
||||
return primaryTable < o.primaryTable;
|
||||
}
|
||||
|
||||
QVector<DbNotifcationInformation> dbNotifications;
|
||||
};
|
||||
|
||||
struct TopicTypeInformationBase
|
||||
{
|
||||
TopicTypeInformationBase() = default;
|
||||
virtual ~TopicTypeInformationBase() = default;
|
||||
|
||||
QString name;
|
||||
QString baseValueType;
|
||||
QString comment;
|
||||
bool isDataObject = false;
|
||||
bool dtsAutogen = true;
|
||||
bool dtcAutogen = true;
|
||||
bool recordTopic = false;
|
||||
bool routeToClient = true;
|
||||
bool routeToServer = false;
|
||||
bool routeInReplay = true;
|
||||
bool autosubscribe = true;
|
||||
|
||||
#ifdef RTI_DDS_AVAILABLE
|
||||
dds::core::xtypes::ExtensibilityKind extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE;
|
||||
#else
|
||||
int extensibility = 0; // Placeholder when RTI DDS is not available
|
||||
#endif
|
||||
|
||||
virtual bool isValid() const
|
||||
{
|
||||
return !name.isEmpty();
|
||||
}
|
||||
|
||||
bool operator<(const TopicTypeInformationBase& o) const
|
||||
{
|
||||
if (name != o.name)
|
||||
return name < o.name;
|
||||
return baseValueType < o.baseValueType;
|
||||
}
|
||||
};
|
||||
|
||||
struct TopicTypeInformation : public TopicTypeInformationBase
|
||||
{
|
||||
QVector<IdlColumnInformation> columns;
|
||||
};
|
||||
|
||||
struct FunctionTopic
|
||||
{
|
||||
QString name;
|
||||
QString requestTopicType;
|
||||
QString replyTopicType;
|
||||
QString requestTopicName;
|
||||
QString replyTopicName;
|
||||
bool routeInReplay = false;
|
||||
};
|
||||
|
||||
class CODEGEN_BASE_EXPORT XmlReader final
|
||||
{
|
||||
public:
|
||||
XmlReader();
|
||||
|
||||
bool readServiceFile(const QString& svcFile, const QString& idlServiceInfoFile = QString(),
|
||||
const XmlReader* parent = nullptr);
|
||||
|
||||
QString errorString() const
|
||||
{
|
||||
return m_errorString;
|
||||
}
|
||||
|
||||
QString getServiceNamespace() const
|
||||
{
|
||||
return m_svcNamespace;
|
||||
}
|
||||
|
||||
QString getServiceAbbr() const
|
||||
{
|
||||
return m_svcAbbr;
|
||||
}
|
||||
|
||||
QStringList getGroupNames() const
|
||||
{
|
||||
return m_groupNames;
|
||||
}
|
||||
|
||||
int getPrio() const
|
||||
{
|
||||
return m_prio;
|
||||
}
|
||||
|
||||
QStringList getProcNames() const
|
||||
{
|
||||
return m_svcProcNames;
|
||||
}
|
||||
|
||||
bool hasCustomRecordReplayPlugin() const
|
||||
{
|
||||
return m_customRecordReplay;
|
||||
}
|
||||
|
||||
bool getDevMode() const
|
||||
{
|
||||
return m_svcDevMode;
|
||||
}
|
||||
|
||||
bool isServiceExerciseIndependent() const
|
||||
{
|
||||
return m_exerciseDependency == ExerciseDependency::Global;
|
||||
}
|
||||
|
||||
ExerciseDependency defaultExerciseDependency() const
|
||||
{
|
||||
return m_exerciseDependency;
|
||||
}
|
||||
|
||||
QMap<QString, QVector<QString>> getDbClass2TableMapping() const
|
||||
{
|
||||
return m_dbClassTables;
|
||||
}
|
||||
|
||||
QMap<QString, QString> getStruct2BaseClassRelations() const
|
||||
{
|
||||
return m_struct2BaseClass;
|
||||
}
|
||||
|
||||
QVector<ServiceStructInformation> getStructToGenerate() const
|
||||
{
|
||||
return m_structsToGenerate;
|
||||
}
|
||||
|
||||
QMap<QString, TopicTypeInformation> getIdlStructInformation() const
|
||||
{
|
||||
return m_idlStructsToGenerate;
|
||||
}
|
||||
|
||||
QVector<TopicTypeInformation> getIdlStructToGenerate() const
|
||||
{
|
||||
QVector<TopicTypeInformation> res;
|
||||
res.reserve(m_idlStructsToGenerate.size());
|
||||
auto it = m_idlStructsToGenerate.constBegin();
|
||||
while (it != m_idlStructsToGenerate.constEnd())
|
||||
{
|
||||
res.append(it.value());
|
||||
++it;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
QMap<QString, QSharedPointer<TableInformation>> getTables() const
|
||||
{
|
||||
return m_tables;
|
||||
}
|
||||
|
||||
QMap<QString, QSharedPointer<ViewInformation>> getViews() const
|
||||
{
|
||||
return m_views;
|
||||
}
|
||||
|
||||
QSharedPointer<TableInformation> getTable(const QString& table) const
|
||||
{
|
||||
return m_tables.value(table);
|
||||
}
|
||||
|
||||
QVector<EnumInformation> getEnumerations() const
|
||||
{
|
||||
return m_enumerations;
|
||||
}
|
||||
|
||||
QVector<IdlColumnInformation> getIdlColumns(const QString& structName) const
|
||||
{
|
||||
return m_idlStructsToGenerate.value(structName).columns;
|
||||
}
|
||||
|
||||
QVector<FunctionTopic> getFunctionTopics() const
|
||||
{
|
||||
return m_functionTopics;
|
||||
}
|
||||
|
||||
QVector<TopicTypeInformation> getExternalTopicTypes() const
|
||||
{
|
||||
return m_externalTopicTypes;
|
||||
}
|
||||
|
||||
QString idlFileForService(const QString& serviceName) const
|
||||
{
|
||||
return getIdlFileForService(m_idlFiles, serviceName);
|
||||
}
|
||||
|
||||
QString xmlFileForService(const QString& serviceName) const
|
||||
{
|
||||
return getIdlFileForService(m_xmlFiles, serviceName);
|
||||
}
|
||||
|
||||
QMap<QString, bool> usedServices() const
|
||||
{
|
||||
return m_usedServices;
|
||||
}
|
||||
|
||||
QVector<CustomServiceFunctionInformation> getCustomFunctions() const
|
||||
{
|
||||
return m_customFunctions;
|
||||
}
|
||||
|
||||
TopicTypeInformation loadExternalTopicType(const QString& externalServiceName, const QString& topicTypeName);
|
||||
EnumInformation getExternalEnumeration(const QString& externalServiceName, const QString& enumName, bool verbose);
|
||||
|
||||
static ExerciseDependency string2ExerciseDependency(const QString& str);
|
||||
|
||||
XmlReader(const XmlReader&) = delete;
|
||||
XmlReader& operator=(const XmlReader&) = delete;
|
||||
|
||||
private:
|
||||
NGL::INGLLogger& m_logger;
|
||||
void parseDBSserviceStructs(const QDomElement& rootElement);
|
||||
void parseDBTables(const QDomElement& rootElement);
|
||||
void parseDBViews(const QDomElement& rootElement);
|
||||
void parseEnumerations(const QDomElement& rootElement);
|
||||
void parseTopicTypes(const QDomElement& rootElement);
|
||||
void parseFunctionTopics(const QDomElement& rootElement);
|
||||
void parseCustomDtcFunctions(const QDomElement& rootElement);
|
||||
|
||||
static QString getAttribute(const QDomElement& element, const QString& attributeName, bool bOptional = false);
|
||||
static bool getAttributeBool(const QDomElement& element, const QString& attributeName, bool bOptional,
|
||||
bool defaultValue);
|
||||
static int getAttributeInt(const QDomElement& element, const QString& attributeName, bool bOptional = false,
|
||||
int defaultValue = 0);
|
||||
|
||||
ColumnInformation parseTableColumn(const QDomElement& column, const QString& tableName) const;
|
||||
IdlColumnInformation parseIdlStructColumn(const QDomElement& column) const;
|
||||
|
||||
QSharedPointer<ForeignKeyInformation> parseForeignKey(const QDomElement& foreignKey) const;
|
||||
ForeignKeyInformation::Endpoint parseForeignKeyEndpoint(const QString& foreignKeyName,
|
||||
const QDomElement& endpoint) const;
|
||||
ConstraintInformation parseConstraint(const QDomElement& constraint) const;
|
||||
CustomServiceFunctionInformation parseCustomFunction(const QDomElement& customFunc,
|
||||
CustomServiceFunctionInformation::Type funcType) const;
|
||||
DbNotifcationInformation parseDbNotification(const QDomElement& customFunc) const;
|
||||
bool loadExternalService(const QString& serviceName);
|
||||
void loadIdlInfofile(const QString& idlServiceInfoFile);
|
||||
QString getIdlFileForService(const QMap<QString, QString>& data, const QString& serviceName) const;
|
||||
QString serviceNameAbbreviation(const QString& serviceName) const;
|
||||
void getServiceNameInfoFromFilePath(const QStringList& parts);
|
||||
static QString serviceNamespaceFromFile(const QString& fn);
|
||||
|
||||
QString m_svcNamespace;
|
||||
|
||||
QString m_svcAbbr;
|
||||
|
||||
QStringList m_svcProcNames;
|
||||
|
||||
bool m_customRecordReplay;
|
||||
|
||||
ExerciseDependency m_exerciseDependency{ExerciseDependency::Exercise};
|
||||
|
||||
QMap<QString, QVector<QString>> m_dbClassTables;
|
||||
|
||||
QMap<QString, QString> m_struct2BaseClass;
|
||||
QVector<ServiceStructInformation> m_structsToGenerate;
|
||||
|
||||
QMap<QString, TopicTypeInformation> m_idlStructsToGenerate;
|
||||
|
||||
QVector<FunctionTopic> m_functionTopics;
|
||||
QVector<TopicTypeInformation> m_externalTopicTypes;
|
||||
QVector<CustomServiceFunctionInformation> m_customFunctions;
|
||||
|
||||
QMap<QString, QSharedPointer<TableInformation>> m_tables;
|
||||
QMap<QString, QSharedPointer<ViewInformation>> m_views;
|
||||
QVector<EnumInformation> m_enumerations;
|
||||
QString m_errorString;
|
||||
|
||||
QMap<QString, QSharedPointer<XmlReader>> m_externalXmlInformations;
|
||||
QMap<QString, QString> m_idlFiles;
|
||||
QMap<QString, QString> m_xmlFiles;
|
||||
QMap<QString, bool> m_usedServices;
|
||||
QMap<QString, QString> m_fullServiceName2Abbreviation;
|
||||
|
||||
QStringList m_groupNames;
|
||||
int m_prio;
|
||||
bool m_svcDevMode{false};
|
||||
};
|
||||
} // namespace CodeGen
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#ifndef CODEGEN_BASE_EXPORT
|
||||
#ifdef BUILD_STATIC_LIBRARIES
|
||||
#define CODEGEN_BASE_EXPORT
|
||||
#else
|
||||
#if defined(MAKE_CODEGEN_BASE_LIB)
|
||||
#define CODEGEN_BASE_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define CODEGEN_BASE_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
log4j.reset=true
|
||||
log4j.rootLogger=WARN, stdout
|
||||
|
||||
# CodeGeneration Tool
|
||||
###########################################################
|
||||
|
||||
log4j.logger.CodeGen.main=WARN
|
||||
log4j.logger.CodeGen=INFO
|
||||
log4j.logger.CodeGen.Settings=INFO
|
||||
log4j.logger.CodeGen.FtsImpl=INFO
|
||||
log4j.logger.CodeGen.FtcImpl=INFO
|
||||
|
||||
# DTS/DTC, FTS/FTC
|
||||
log4j.logger.Generic.DTS=INFO
|
||||
log4j.logger.Generic.DTC=INFO
|
||||
log4j.logger.Generic.FTS.Function=INFO
|
||||
log4j.logger.Generic.FTC=INFO
|
||||
log4j.logger.DTC.Subscriber=INFO
|
||||
log4j.logger.DTC.SubscriberListener=INFO
|
||||
log4j.logger.FTS=INFO
|
||||
log4j.logger.FTC=WARN
|
||||
|
||||
# connext (request/reply feature)
|
||||
log4j.logger.connext.requester=WARN
|
||||
log4j.logger.connext.replier=WARN
|
||||
|
||||
# CodeGen
|
||||
log4j.logger.CodeGen.XmlReader=WARN
|
||||
|
||||
# SqlGenerator
|
||||
log4j.logger.CodeGen.SqlGenerator=WARN
|
||||
log4j.logger.CodeGen.StructBuilder=INFO
|
||||
|
||||
log4j.appender.file=org.apache.log4j.RollingFileAppender
|
||||
log4j.appender.file.MaxFileSize=1MB
|
||||
log4j.appender.file.MaxBackupIndex=9
|
||||
log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_codeGen.log
|
||||
log4j.appender.file.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n
|
||||
|
||||
|
||||
# Appnd_Console
|
||||
###########################################################
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n
|
||||
|
||||
|
||||
# Appnd_Syslog
|
||||
###########################################################
|
||||
log4j.appender.syslog=org.apache.log4j.SyslogAppender
|
||||
log4j.appender.syslog.Threshold=ERROR
|
||||
log4j.appender.syslog.SysLogHost=localhost
|
||||
log4j.appender.syslog.Facility=LOCAL7
|
||||
log4j.appender.syslog.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m
|
||||
log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true
|
||||
log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO
|
||||
log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL
|
||||
|
||||
|
||||
###########################################################
|
||||
###########################################################
|
||||
###########################################################
|
||||
# 1) Available Levels in LOG4CXX Version 0.10.0:
|
||||
# TRACE,
|
||||
# DEBUG,
|
||||
# INFO,
|
||||
# WARN,
|
||||
# ERROR
|
||||
# FATAL
|
||||
#
|
||||
# d = date/time
|
||||
# p = priority (Level)
|
||||
# c = category/channel
|
||||
# m = message
|
||||
# F = file
|
||||
# L = line
|
||||
# n = platform dependent line separator character or characters.
|
||||
# ....
|
||||
#
|
||||
# Examples for console:
|
||||
# a)
|
||||
# %d %p %c #%m# (%.50F:%L)%n
|
||||
# date/time
|
||||
# priority
|
||||
# category/channel
|
||||
# #message#
|
||||
# (Maximal 50Character des Filename von rechts:Line)
|
||||
# line separator
|
||||
#
|
||||
# b)
|
||||
# %d (%.15F:%L) %-5p %c |#| %m%n
|
||||
# date/time
|
||||
# (Maximal 15Character des Filename von rechts:Line)
|
||||
# priority, Right pad with spaces if the priority name is less than 5 characters long.
|
||||
# category/channel
|
||||
# message
|
||||
# line separator
|
||||
#
|
||||
# Examples for files:
|
||||
@@ -0,0 +1,498 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
class FtData(object):
|
||||
def __init__( self ):
|
||||
self.name = ''
|
||||
self.namespace = ''
|
||||
self.requestTopicType = ''
|
||||
self.requestTopicName = ''
|
||||
self.replyTopicType = ''
|
||||
self.replyTopicName = ''
|
||||
|
||||
def fqnVar( self, name ):
|
||||
if name.find('::') < 0:
|
||||
name = self.namespace + '::' + name
|
||||
return name
|
||||
def stripNamespace( self, str ):
|
||||
return str.split('::')[-1]
|
||||
|
||||
def getReplyTopicName( self ):
|
||||
return 'LTS_' + self.stripNamespace( self.replyTopicName ).replace( '::', '_' )
|
||||
|
||||
def getReplyTopicNameType( self, withNamespace = True ):
|
||||
var = self.replyTopicName
|
||||
return self.fqnVar( var ) if withNamespace else self.stripNamespace( var )
|
||||
|
||||
def getRequestTopicName( self ):
|
||||
return 'LTS_' + self.stripNamespace( self.requestTopicName ).replace( '::', '_' )
|
||||
|
||||
def getRequestTopicNameType( self, withNamespace = True ):
|
||||
var = self.requestTopicName
|
||||
return self.fqnVar( var ) if withNamespace else self.stripNamespace( var )
|
||||
|
||||
def getReplyTopicType( self ):
|
||||
return self.fqnVar( self.replyTopicType )
|
||||
def getRequestTopicType( self ):
|
||||
return self.fqnVar( self.requestTopicType )
|
||||
|
||||
def needReplyTopicTypedef( self ):
|
||||
return self.getReplyTopicNameType() != self.getReplyTopicType()
|
||||
def needRequestTopicTypedef( self ):
|
||||
return self.getRequestTopicNameType() != self.getRequestTopicType()
|
||||
|
||||
class GuiPluginRights(object):
|
||||
def __init__( self ):
|
||||
self.name = ''
|
||||
self.offset = 0
|
||||
self.groups = []
|
||||
class GuiPluginRightsGroup(object):
|
||||
def __init__( self ):
|
||||
self.name = ''
|
||||
self.desc = ''
|
||||
self.values = []
|
||||
|
||||
class FileParser(object):
|
||||
def __init__( self ):
|
||||
self.filename = ''
|
||||
self.service_abbr = ''
|
||||
self.module_name = ''
|
||||
self.struct_names = []
|
||||
self.ft_enums = []
|
||||
self.ft_replies = []
|
||||
self.ft_data = dict()
|
||||
self.op_right_offset = 0
|
||||
self.op_rights = []
|
||||
|
||||
def build(self, path):
|
||||
""" Parse the whole stuff ... """
|
||||
self.filename = path
|
||||
self.read_file_xml(self.filename)
|
||||
|
||||
def read_file_xml(self, filename):
|
||||
""" Read the given xml service file"""
|
||||
|
||||
"""print >> sys.stdout, 'Reading xml file %s' % filename"""
|
||||
xmlTree = ET.parse(filename)
|
||||
root = xmlTree.getroot()
|
||||
self.module_name = root.attrib['namespace']
|
||||
self.service_abbr = root.attrib['abbreviation'].lower()
|
||||
|
||||
for child in root.iter('topicType'):
|
||||
struct_name = child.attrib['name']
|
||||
if 'isDataObject' in child.attrib and int(child.attrib['isDataObject']) != 0:
|
||||
self.struct_names.append(struct_name)
|
||||
for child in root.iter('functionTopic'):
|
||||
ft_name = child.attrib['name']
|
||||
self.ft_enums.append(ft_name)
|
||||
|
||||
ft_data = FtData()
|
||||
ft_data.name = ft_name
|
||||
ft_data.namespace = self.module_name
|
||||
ft_data.requestTopicType = child.attrib['requestTopicType']
|
||||
ft_data.requestTopicName = child.attrib['requestTopicName']
|
||||
ft_data.replyTopicType = child.attrib['replyTopicType']
|
||||
ft_data.replyTopicName = child.attrib['replyTopicName']
|
||||
self.ft_data[ft_name] = ft_data
|
||||
|
||||
for child in root.iter('operatorRights'):
|
||||
self.op_right_offset = int(child.attrib['offset'])
|
||||
for pluginChild in child.iter('guiPlugin'):
|
||||
r = GuiPluginRights()
|
||||
r.name = pluginChild.attrib['name']
|
||||
r.offset = int(pluginChild.attrib['offset'])
|
||||
r.groups = [];
|
||||
for groups in pluginChild.iter('operatorRightGroup'):
|
||||
g = GuiPluginRightsGroup();
|
||||
g.name = groups.attrib['name']
|
||||
g.desc = groups.attrib['description'] if 'description' in groups.attrib else ''
|
||||
|
||||
for right in groups.iter('operatorRight'):
|
||||
desc = right.attrib['description'] if 'description' in right.attrib else ''
|
||||
g.values.append((right.attrib['right'], int(right.attrib['value']), desc))
|
||||
r.groups.append(g)
|
||||
self.op_rights.append(r)
|
||||
|
||||
|
||||
def outputHeader( outfile, parser, description, with_dds_include = True, with_pragma = True ):
|
||||
outfile.write('\n')
|
||||
outfile.write('/**\n')
|
||||
outfile.write('* %s definitions\n' % description)
|
||||
outfile.write('*/\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('// warning: this file is generated with command\n')
|
||||
outfile.write('// %s\n' % ( " ".join(sys.argv) ))
|
||||
outfile.write('// at %s\n' % time.strftime( '%c' ))
|
||||
outfile.write('// it will be overwritten by next run - do not modify!\n')
|
||||
outfile.write('\n')
|
||||
if with_pragma:
|
||||
outfile.write('#pragma once\n')
|
||||
outfile.write('\n')
|
||||
if with_dds_include:
|
||||
outfile.write('#include "%s.hpp"\n' % ( parser.service_abbr ))
|
||||
|
||||
class DtsGenerator(object):
|
||||
def generate( self, outfile, parser ):
|
||||
outputHeader( outfile, parser, 'DTS' )
|
||||
outfile.write('\n')
|
||||
outfile.write('/**\n')
|
||||
outfile.write(' * Type definitions \n')
|
||||
outfile.write(' * => DataWriterObjects (DWOs)\n')
|
||||
outfile.write(' * => DataTopicServers (DTSs)\n')
|
||||
outfile.write(' */\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace DDSGeneric\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write('template <typename T>\n')
|
||||
outfile.write('class GenericDataWriter;\n')
|
||||
outfile.write('}\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace DTS\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write(' template<typename T> class GenericDts;\n')
|
||||
outfile.write(' template<typename T> class GenericDtsImpl;\n')
|
||||
outfile.write('\n')
|
||||
|
||||
for struct_name in sorted(parser.struct_names):
|
||||
|
||||
topic_name = "%s::%s" % ( parser.module_name, struct_name )
|
||||
dwo_name = "Dwo%s" % ( struct_name )
|
||||
outfile.write(' // Topic %s\n' % ( topic_name ))
|
||||
outfile.write(' // Interface\n')
|
||||
outfile.write(' using %s = GenericDts<%s>;\n' % ( struct_name, topic_name ))
|
||||
outfile.write(' // Interface impl\n')
|
||||
outfile.write(' using %s = DDSGeneric::GenericDataWriter<%s>;\n' % ( dwo_name, topic_name ))
|
||||
outfile.write(' using %sImpl = GenericDtsImpl<%s>;\n' % ( struct_name, dwo_name ))
|
||||
outfile.write('\n')
|
||||
|
||||
outfile.write('} // namespace DTS\n')
|
||||
outfile.write('\n')
|
||||
|
||||
class DtcGenerator(object):
|
||||
def generate( self, outfile, parser ):
|
||||
outfile.write('\n')
|
||||
outfile.write('/**\n')
|
||||
outfile.write('* %s definitions\n' % 'DTC')
|
||||
outfile.write('*/\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('// warning: this file is generated with command\n')
|
||||
outfile.write('// %s\n' % ( " ".join(sys.argv) ))
|
||||
outfile.write('// at %s\n' % time.strftime( '%c' ))
|
||||
outfile.write('// it will be overwritten by next run - do not modify!\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('#pragma once\n')
|
||||
outfile.write('\n')
|
||||
# Include Qt headers FIRST to avoid namespace conflicts with DDS
|
||||
outfile.write('#include <QtCore/QObject>\n')
|
||||
outfile.write('#include <QtCore/QTimer>\n')
|
||||
outfile.write('\n')
|
||||
# Include DDS headers BEFORE project headers to avoid namespace conflicts
|
||||
outfile.write('#include <DDS/Generic/generic_data_reader.h>\n')
|
||||
outfile.write('#include <DDS/DTC/generic_dtc.h>\n')
|
||||
outfile.write('#include <DDS/DTC/generic_dtc_impl.h>\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('#include "%s.hpp"\n' % ( parser.service_abbr ))
|
||||
outfile.write('\n')
|
||||
outfile.write('/**\n')
|
||||
outfile.write(' * Type definitions \n')
|
||||
outfile.write(' * => DataTopicClients (DTCs)\n')
|
||||
outfile.write(' */\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace DDSGeneric\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write('template <typename T>\n')
|
||||
outfile.write('class GenericDataReader;\n')
|
||||
outfile.write('template <typename T>\n')
|
||||
outfile.write('class GenericDataReaderContentFilter;\n')
|
||||
outfile.write('class DynamicDataReader;\n')
|
||||
outfile.write('}\n')
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace DTC\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write(' template<typename T> class GenericDtc;\n')
|
||||
outfile.write(' template<typename T> class GenericDtcImpl;\n')
|
||||
outfile.write(' template<typename T> class GenericDtcSampleInfo;\n')
|
||||
outfile.write(' template<typename T> class GenericDtcImplSampleInfo;\n')
|
||||
outfile.write('\n')
|
||||
|
||||
for struct_name in sorted(parser.struct_names):
|
||||
|
||||
topic_name = "%s::%s" % ( parser.module_name, struct_name )
|
||||
dro_name = "Dro%s" % ( struct_name )
|
||||
dro_name_cf = "Dro%sCF" % ( struct_name )
|
||||
|
||||
outfile.write(' // Topic %s\n' % ( topic_name ))
|
||||
outfile.write(' using %s = DDSGeneric::GenericDataReader<%s>;\n' % ( dro_name, topic_name ))
|
||||
outfile.write(' using %s = GenericDtc<%s>;\n' % ( struct_name, dro_name ))
|
||||
outfile.write(' using %sImpl = GenericDtcImpl<%s>;\n' % ( struct_name, dro_name ))
|
||||
|
||||
outfile.write(' using %s = DDSGeneric::GenericDataReaderContentFilter<%s>;\n' % ( dro_name_cf, topic_name ))
|
||||
outfile.write(' using %sCF = GenericDtc<%s>;\n' % ( struct_name, dro_name_cf ))
|
||||
outfile.write(' using %sCFImpl = GenericDtcImpl<%s>;\n' % ( struct_name, dro_name_cf ))
|
||||
|
||||
outfile.write(' namespace SampleInfo {\n')
|
||||
outfile.write(' using %s = GenericDtcSampleInfo<%s>;\n' % ( struct_name, dro_name ))
|
||||
outfile.write(' using %sImpl = GenericDtcImplSampleInfo<%s>;\n' % ( struct_name, dro_name ))
|
||||
outfile.write(' }\n')
|
||||
outfile.write('\n')
|
||||
|
||||
outfile.write('} // namespace DTC\n')
|
||||
outfile.write('\n')
|
||||
|
||||
def printFtTopicTypeNames( outfile, ft_data ):
|
||||
outfile.write(' template<> struct TopicNames<%s>' % ft_data.name)
|
||||
outfile.write(' {\n')
|
||||
outfile.write(' static constexpr const char *functionTopicName() { return "%s"; }\n' % ft_data.name)
|
||||
outfile.write(' static constexpr const char *replyTopicName() { return "%s"; }\n' % ft_data.getReplyTopicName())
|
||||
outfile.write(' static constexpr const char *replyTopicTypeName() { return "%s"; }\n' % ft_data.getReplyTopicNameType( True ))
|
||||
outfile.write(' static constexpr const char *requestTopicName() { return "%s"; }\n' % ft_data.getRequestTopicName())
|
||||
outfile.write(' static constexpr const char *requestTopicTypeName() { return "%s"; }\n' % ft_data.getRequestTopicNameType( True ))
|
||||
outfile.write(' };\n')
|
||||
|
||||
def printFtTypedefs( outfile, ft_data ):
|
||||
if ft_data.needRequestTopicTypedef():
|
||||
outfile.write(' using %s = %s;\n' % ( ft_data.getRequestTopicNameType( False ), ft_data.getRequestTopicType() ))
|
||||
if ft_data.needReplyTopicTypedef():
|
||||
outfile.write(' using %s = %s;\n' % ( ft_data.getReplyTopicNameType( False ), ft_data.getReplyTopicType() ))
|
||||
|
||||
class FtsGenerator(object):
|
||||
def generate( self, outfile, parser):
|
||||
outputHeader( outfile, parser, 'FTS' )
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace FTS\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write('template <typename REQ, typename REP, typename DUMMY> class GenericFtsImpl;\n')
|
||||
outfile.write('template<typename GENERIC_FTS> struct TopicNames;\n')
|
||||
outfile.write('}\n')
|
||||
outfile.write('namespace %s\n' % ( parser.module_name ))
|
||||
outfile.write('{\n')
|
||||
outfile.write(' // TypeDefinitions for Replies that are not implemented => using Core::FunctionReply\n')
|
||||
for ft_name in sorted(parser.ft_enums):
|
||||
printFtTypedefs( outfile, parser.ft_data[ft_name] )
|
||||
|
||||
outfile.write('} // namespace %s\n' % ( parser.module_name ))
|
||||
outfile.write('\n')
|
||||
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace FTS\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write(' // TypeDefinitions for GenericFtsImpl\n')
|
||||
for ft_name in sorted(parser.ft_enums):
|
||||
ft_data = parser.ft_data[ft_name]
|
||||
|
||||
dummy = '%sTemplateHelperDummy' % ft_data.name;
|
||||
outfile.write(' class %s;\n' % dummy)
|
||||
outfile.write(' using %s = GenericFtsImpl<%s, %s, %s>;\n' % (ft_data.name, ft_data.getRequestTopicType(), ft_data.getReplyTopicNameType(), dummy))
|
||||
printFtTopicTypeNames( outfile, ft_data )
|
||||
outfile.write('\n')
|
||||
|
||||
outfile.write('} // namespace FTS\n')
|
||||
outfile.write('\n')
|
||||
|
||||
class FtcGenerator(object):
|
||||
def generate( self, outfile, parser ):
|
||||
outputHeader( outfile, parser, 'FTC' )
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace FTC\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write('template <typename REQ, typename REP, typename DUMMY> class GenericFtcImpl;\n')
|
||||
outfile.write('template<typename GENERIC_FTC> struct TopicNames;\n')
|
||||
outfile.write('}\n')
|
||||
outfile.write('namespace %s\n' % ( parser.module_name ))
|
||||
outfile.write('{\n')
|
||||
outfile.write(' // TypeDefinitions for Replies that are not implemented => using Core::FunctionReply\n')
|
||||
for ft_name in sorted(parser.ft_enums):
|
||||
printFtTypedefs( outfile, parser.ft_data[ft_name] )
|
||||
|
||||
outfile.write('} // namespace %s\n' % ( parser.module_name ))
|
||||
outfile.write('\n')
|
||||
|
||||
outfile.write('\n')
|
||||
outfile.write('namespace FTC\n')
|
||||
outfile.write('{\n')
|
||||
outfile.write(' // TypeDefinitions for GenericFtcImpl\n')
|
||||
for ft_name in sorted(parser.ft_enums):
|
||||
ft_data = parser.ft_data[ft_name]
|
||||
|
||||
dummy = '%sTemplateHelperDummy' % ft_data.name;
|
||||
outfile.write(' class %s;\n' % dummy)
|
||||
outfile.write(' using %s = GenericFtcImpl<%s, %s, %s>;\n' % (ft_data.name, ft_data.getRequestTopicType(), ft_data.getReplyTopicNameType(), dummy))
|
||||
printFtTopicTypeNames( outfile, ft_data )
|
||||
outfile.write('\n')
|
||||
|
||||
outfile.write('} // namespace FTC\n')
|
||||
outfile.write('\n')
|
||||
|
||||
class OperatorRights(object):
|
||||
def generate( self, outfile, parser ):
|
||||
outputHeader( outfile, parser, 'Operator Rights', False )
|
||||
outfile.write('namespace %s \n' % parser.module_name)
|
||||
outfile.write('{\n')
|
||||
outfile.write(' static const int Offset = %s; \n' % parser.op_right_offset)
|
||||
outfile.write('}\n\n')
|
||||
|
||||
outfile.write('namespace OperatorRights\n')
|
||||
outfile.write('{\n')
|
||||
globalOffset = parser.op_right_offset << 24
|
||||
for r in parser.op_rights:
|
||||
outfile.write('enum %s\n' % r.name)
|
||||
outfile.write('{\n')
|
||||
offsetPlugin = globalOffset + (r.offset << 16)
|
||||
groupCount = -1
|
||||
for group in r.groups:
|
||||
groupCount = groupCount + 1
|
||||
groupOffset = offsetPlugin + (groupCount << 8)
|
||||
for k,v,d in group.values:
|
||||
v += groupOffset
|
||||
outfile.write(' %s = %s,\n' % (k, hex(v)))
|
||||
outfile.write('};\n')
|
||||
|
||||
outfile.write('\n')
|
||||
outfile.write('enum Groups_%s\n' % parser.module_name)
|
||||
outfile.write('{\n')
|
||||
pattern = re.compile(r'\W+')
|
||||
for r in parser.op_rights:
|
||||
offsetPlugin = globalOffset + (r.offset << 16)
|
||||
for group in r.groups:
|
||||
groupOffset = offsetPlugin + (groupCount << 8)
|
||||
name = pattern.sub('', group.name)
|
||||
outfile.write(' Group%s = %s,\n' % (name, hex(groupOffset)))
|
||||
outfile.write('};\n')
|
||||
|
||||
outfile.write('\n')
|
||||
outfile.write('enum Plugins_%s\n' % parser.module_name)
|
||||
outfile.write('{\n')
|
||||
for r in parser.op_rights:
|
||||
offsetPlugin = globalOffset + (r.offset << 16)
|
||||
name = pattern.sub('', r.name)
|
||||
outfile.write(' Plugin%s = %s,\n' % (name, hex(offsetPlugin)))
|
||||
outfile.write('};\n')
|
||||
|
||||
outfile.write('} // namespace OperatorRights\n')
|
||||
|
||||
class OperatorRightsCpp(object):
|
||||
def generate( self, outfile, parser ):
|
||||
outputHeader( outfile, parser, 'Operator Rights (source)', False, False )
|
||||
outfile.write('#include "%s_operator_rights.h"\n' % parser.service_abbr)
|
||||
outfile.write('#include <QCoreApplication>\n\n\n')
|
||||
if len(parser.op_rights) == 0:
|
||||
return
|
||||
outfile.write('\nvoid doNothing()\n{\n const char * txt[] {\n')
|
||||
|
||||
disamb = 'OperatorRights'
|
||||
globalOffset = parser.op_right_offset << 24
|
||||
for r in parser.op_rights:
|
||||
offsetPlugin = globalOffset + (r.offset << 16)
|
||||
groupCount = -1
|
||||
for group in r.groups:
|
||||
groupCount = groupCount + 1
|
||||
groupOffset = offsetPlugin + (groupCount << 8)
|
||||
if group.name != '':
|
||||
outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, group.name))
|
||||
if group.desc != '':
|
||||
outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, group.desc))
|
||||
for aclName, aclId, aclDescr in group.values:
|
||||
outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, aclDescr))
|
||||
outfile.write(' };\n Q_UNUSED(txt);\n}\n')
|
||||
|
||||
class OperatorRightsSql(object):
|
||||
def generate( self, outfile, parser ):
|
||||
outfile.write('BEGIN;\n')
|
||||
globalOffset = parser.op_right_offset << 24
|
||||
if globalOffset > 0:
|
||||
outfile.write('--- Offset: %s = %s\n' % (globalOffset, hex(globalOffset)))
|
||||
|
||||
groupId = globalOffset;
|
||||
|
||||
for r in parser.op_rights:
|
||||
offsetPlugin = globalOffset + (r.offset << 16)
|
||||
groupCount = -1
|
||||
for group in r.groups:
|
||||
groupCount = groupCount + 1
|
||||
groupOffset = offsetPlugin + (groupCount << 8)
|
||||
|
||||
outfile.write('\n--- %s - %s = %s\n' % (group.name, hex(groupOffset), groupOffset))
|
||||
str = "INSERT INTO opm.opm_group (id, name, description, type_id, is_hidden) VALUES (%s, '%s', '%s', 4, false);\n"
|
||||
outfile.write(str % (groupOffset, group.name, group.desc))
|
||||
for aclName, aclId, aclDescr in group.values:
|
||||
aclId += groupOffset
|
||||
outfile.write('--- Base: %s, %s - %s = %s\n' % (groupOffset, aclName, aclId, hex(aclId)))
|
||||
str = "INSERT INTO opm.opm_access_control_level (id, name, description) VALUES (%s, '%s', '%s');\n";
|
||||
outfile.write(str % (aclId, aclName, aclDescr));
|
||||
str = "INSERT INTO opm.opm_group (id, name, description, type_id, is_hidden) VALUES (%s, '%s', '%s', 1, true);\n";
|
||||
outfile.write(str % (groupId, aclName, aclDescr));
|
||||
str = "INSERT INTO opm.opm_acl_allow (group_id, acl_id) VALUES (%s, %s); -- group_id is: %s = %s\n";
|
||||
outfile.write(str % (groupId, aclId, groupId, hex(groupId)));
|
||||
str = "INSERT INTO opm.opm_group_group (group_id, is_member_of_group_id) VALUES (%s, %s); -- %s\n"
|
||||
outfile.write(str % (groupOffset, groupId, group.name))
|
||||
groupId = groupId + 1
|
||||
outfile.write('\n')
|
||||
outfile.write('END;\n')
|
||||
|
||||
def generate( parser, generator, out_filename ):
|
||||
try:
|
||||
outfile = open(out_filename, 'w')
|
||||
except:
|
||||
print("Could not open %s for writing" % out_filename, file=sys.stderr)
|
||||
sys.exit(4)
|
||||
generator.generate(outfile, parser)
|
||||
outfile.close()
|
||||
|
||||
def usage( error ):
|
||||
print(error, file=sys.stderr)
|
||||
print("Usage: %s DTS|DTC|FTS|FTC|SQL <service_file.xml> <outfile> " % sys.argv[0], file=sys.stderr)
|
||||
print(" %s ALL <service_file.xml> <outdir> " % sys.argv[0], file=sys.stderr)
|
||||
print("i.e.: %s DTS exm_service.xml DtsTypeDefinitions.h" % sys.argv[0], file=sys.stderr)
|
||||
print("call: '%s'" % " ".join(sys.argv), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
usage("Invalid argument count")
|
||||
|
||||
option_str = sys.argv[1]
|
||||
xml_filename = sys.argv[2]
|
||||
|
||||
parser = FileParser()
|
||||
parser.build(xml_filename)
|
||||
|
||||
if option_str == "DTS":
|
||||
generate(parser, DtsGenerator(), sys.argv[3])
|
||||
elif option_str == "DTC":
|
||||
generate(parser, DtcGenerator(), sys.argv[3])
|
||||
elif option_str == "FTS":
|
||||
generate(parser, FtsGenerator(), sys.argv[3])
|
||||
elif option_str == "FTC":
|
||||
generate(parser, FtcGenerator(), sys.argv[3])
|
||||
elif option_str == "SQL":
|
||||
out_dir = sys.argv[3]
|
||||
generate(parser, OperatorRightsSql(), os.path.join(out_dir, parser.service_abbr + '_operator_rights.sql'))
|
||||
elif option_str == "ALL":
|
||||
out_dir = sys.argv[3]
|
||||
fn = out_dir + '/' + parser.service_abbr + '_%s_types.h'
|
||||
generate(parser, DtsGenerator(), fn % 'dts')
|
||||
generate(parser, DtcGenerator(), fn % 'dtc')
|
||||
generate(parser, FtsGenerator(), fn % 'fts')
|
||||
generate(parser, FtcGenerator(), fn % 'ftc')
|
||||
generate(parser, OperatorRights(), out_dir + '/' + parser.service_abbr + '_operator_rights.h')
|
||||
generate(parser, OperatorRightsCpp(), out_dir + '/' + parser.service_abbr + '_operator_rights.cpp')
|
||||
else:
|
||||
usage("invalid option '%s'" % option_str)
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,167 @@
|
||||
|
||||
|
||||
#include "CppTopicGeneratorGrantlee.h"
|
||||
#include "DaoGeneratorGrantlee.h"
|
||||
#include "DatabaseGeneratorGrantlee.h"
|
||||
#include "SqlGenerator.h"
|
||||
#include "StructBuilder.h"
|
||||
#include "XmlReader.h"
|
||||
|
||||
#include <LtsCommandLineParser/LtsCommandLineParser.h>
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
#include <QtCore/QDir>
|
||||
#include <nglapplication/nglcoreapplication.h>
|
||||
|
||||
class CmdLineParser : public LtsCommandLineParser::LtsCommandLineParser
|
||||
{
|
||||
public:
|
||||
CmdLineParser() : LtsCommandLineParser(LtsCommandLineParser::LtsCommandLineParser::None)
|
||||
{
|
||||
addOption({QStringList() << QStringLiteral("s") << QStringLiteral("servicefile"),
|
||||
QStringLiteral("full path to the service xml-file"), QStringLiteral("service xml-file"),
|
||||
QStringLiteral("/home")});
|
||||
|
||||
addOption({QStringList() << QStringLiteral("a") << QStringLiteral("action"),
|
||||
QStringLiteral("the action to execute"), QStringLiteral("DATABASE|DATABASE_SERVER|IDL|DDS_TOPICS"),
|
||||
QStringLiteral("/0")});
|
||||
|
||||
addOption({QStringList() << QStringLiteral("o") << QStringLiteral("outdir"),
|
||||
QStringLiteral("the output directory where to put the generated files"),
|
||||
QStringLiteral("output directory"), QStringLiteral("/home")});
|
||||
|
||||
addOption({QStringList() << QStringLiteral("i") << QStringLiteral("idlinfo"),
|
||||
QStringLiteral("the info file with all available idl service files"),
|
||||
QStringLiteral("info file for idl"), QStringLiteral("/0")});
|
||||
}
|
||||
|
||||
bool init()
|
||||
{
|
||||
if (!LtsCommandLineParser::init())
|
||||
return false;
|
||||
|
||||
if (serviceFile().isEmpty() || action().isEmpty() || outDir().isEmpty())
|
||||
{
|
||||
showHelp();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString serviceFile() const
|
||||
{
|
||||
return value(QStringLiteral("s"));
|
||||
}
|
||||
|
||||
QString action() const
|
||||
{
|
||||
return value(QStringLiteral("a"));
|
||||
}
|
||||
|
||||
QString outDir() const
|
||||
{
|
||||
return value(QStringLiteral("o"));
|
||||
}
|
||||
|
||||
QString idlInfoFile() const
|
||||
{
|
||||
return value(QStringLiteral("i"));
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
NGLCoreApplication app(argc, argv);
|
||||
app.createLogger("CodeGen", "codegen_log4cxx.props", true);
|
||||
|
||||
NGL::INGLLogger& m_logger = app.baseLogger();
|
||||
|
||||
CmdLineParser options;
|
||||
if (!options.init())
|
||||
return -1;
|
||||
|
||||
QScopedPointer<CodeGen::XmlReader> reader(new CodeGen::XmlReader);
|
||||
|
||||
DEBUG_LOG(m_logger) << "ServiceFile: " << options.serviceFile();
|
||||
DEBUG_LOG(m_logger) << "IdlInfoFile: " << options.idlInfoFile();
|
||||
if (!reader->readServiceFile(options.serviceFile(), options.idlInfoFile()))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error parsing service file '" << options.serviceFile() << ": " << reader->errorString();
|
||||
return 2;
|
||||
}
|
||||
|
||||
QDir dir(options.outDir());
|
||||
DEBUG_LOG(m_logger) << "Directory: " << options.outDir();
|
||||
if (!dir.exists() && !dir.mkpath(options.outDir()))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Directory '" << options.outDir() << "' does not exist and can not be created!";
|
||||
return 3;
|
||||
}
|
||||
|
||||
QScopedPointer<CodeGen::StructBuilder> structBuilder(new CodeGen::StructBuilder(reader.data()));
|
||||
if (!structBuilder->buildStructures())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error building up structures";
|
||||
return 4;
|
||||
}
|
||||
|
||||
const QString action = options.action().toLower();
|
||||
|
||||
QScopedPointer<CodeGen::GeneratorGrantleeBase> generator;
|
||||
if (action == QStringLiteral("dao"))
|
||||
generator.reset(new CodeGen::DaoGeneratorGrantlee);
|
||||
else if (action == QStringLiteral("database"))
|
||||
generator.reset(new CodeGen::DatabaseGeneratorGrantlee(false));
|
||||
else if (action == QStringLiteral("database_server"))
|
||||
generator.reset(new CodeGen::DatabaseGeneratorGrantlee(true));
|
||||
else if (action == QStringLiteral("dds_topics"))
|
||||
generator.reset(new CodeGen::CppTopicGeneratorGrantlee);
|
||||
else if (action == QStringLiteral("sql"))
|
||||
{
|
||||
CodeGen::SqlGenerator sql(reader.data(), structBuilder.data());
|
||||
if (!sql.generateData(35))
|
||||
return 8;
|
||||
|
||||
if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Exercise))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr();
|
||||
return 10;
|
||||
}
|
||||
|
||||
if (!sql.generateData(42))
|
||||
return 8;
|
||||
|
||||
if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Exercise))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr();
|
||||
return 10;
|
||||
}
|
||||
|
||||
sql.generateData(0);
|
||||
if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Global))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr();
|
||||
return 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Invalid action '" << options.action() << "' given!";
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (!generator->init(reader.data(), structBuilder.data()))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error initializing generator!";
|
||||
return 6;
|
||||
}
|
||||
|
||||
const auto posArgs = options.positionalArguments();
|
||||
for (const QString& file : posArgs)
|
||||
{
|
||||
|
||||
if (!generator->generateData(file, dir.absolutePath()))
|
||||
return 7;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
|
||||
|
||||
#include "main_grantlee_gui.h"
|
||||
|
||||
#include "CppTopicGeneratorGrantlee.h"
|
||||
#include "DatabaseGeneratorGrantlee.h"
|
||||
#include "StructBuilder.h"
|
||||
#include "XmlReader.h"
|
||||
#include "ui_main_grantlee_gui.h"
|
||||
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <nglapplication/nglapplication.h>
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QMainWindow(parent), m_xmlReader(nullptr), m_structBuilder(nullptr),
|
||||
m_generator(new CodeGen::CppTopicGeneratorGrantlee), m_engine(new KTextTemplate::Engine), m_ui(new Ui_MainGrantleeGui)
|
||||
{
|
||||
m_idlServiceInfoFile = QStringLiteral("/home/chehrlic/TDS/TDS-build-product-clang/service_idl_files.txt");
|
||||
m_ui->setupUi(this);
|
||||
connect(m_ui->actionExit, &QAction::triggered, this, &MainWindow::close);
|
||||
connect(m_ui->actionSelect_Data_Source, &QAction::triggered, this, &MainWindow::onSelectDataSource);
|
||||
connect(m_ui->actionOpen_Template, &QAction::triggered, this, &MainWindow::onOpenTemplate);
|
||||
connect(m_ui->actionSave_Template, &QAction::triggered, this, &MainWindow::onSaveTemplate);
|
||||
connect(m_ui->pbRefresh, &QPushButton::clicked, this, &MainWindow::onRefreshOutput);
|
||||
connect(m_ui->actionDatabaseGenerator, &QAction::triggered, this, &MainWindow::onGeneratorDb);
|
||||
connect(m_ui->actionDDS_Generator, &QAction::triggered, this, &MainWindow::onGeneratorDds);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete m_xmlReader;
|
||||
delete m_structBuilder;
|
||||
delete m_generator;
|
||||
delete m_engine;
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
static QLatin1String s_startDir("/home/chehrlic/TDS/TDS-source-product/Lts/");
|
||||
|
||||
void MainWindow::onSelectDataSource()
|
||||
{
|
||||
const QString dataSource =
|
||||
QFileDialog::getOpenFileName(this, tr("Select data source"), s_startDir, tr("Service Files (*.xml)"));
|
||||
if (dataSource.isEmpty())
|
||||
return;
|
||||
delete m_xmlReader;
|
||||
m_xmlReader = new CodeGen::XmlReader;
|
||||
if (!m_xmlReader->readServiceFile(dataSource, m_idlServiceInfoFile))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid data source"),
|
||||
tr("The selected data source is not valid: %1").arg(m_xmlReader->errorString()));
|
||||
return;
|
||||
}
|
||||
delete m_structBuilder;
|
||||
m_structBuilder = new CodeGen::StructBuilder(m_xmlReader);
|
||||
if (!m_structBuilder->buildStructures())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid data source"), tr("Error building structures from data"));
|
||||
return;
|
||||
}
|
||||
m_ui->teOuput->clear();
|
||||
m_generator->init(m_xmlReader, m_structBuilder);
|
||||
QMessageBox::information(this, tr("Data source"), tr("Successfully loaded data source"));
|
||||
}
|
||||
|
||||
void MainWindow::onOpenTemplate()
|
||||
{
|
||||
const QString templateFile =
|
||||
QFileDialog::getOpenFileName(this, tr("Select data source"), s_startDir, tr("Templates (*.grantlee)"));
|
||||
if (templateFile.isEmpty())
|
||||
return;
|
||||
QFile f(templateFile);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error reading template file"), tr("Can not read file: %1").arg(f.errorString()));
|
||||
return;
|
||||
}
|
||||
const QString content = QString::fromUtf8(f.readAll());
|
||||
m_ui->teInput->setText(content);
|
||||
m_templateFile = templateFile;
|
||||
}
|
||||
|
||||
void MainWindow::onSaveTemplate()
|
||||
{
|
||||
QFile f(m_templateFile);
|
||||
if (!f.open(QIODevice::WriteOnly))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error saving template file"), tr("Can not save file: %1").arg(f.errorString()));
|
||||
return;
|
||||
}
|
||||
f.write(m_ui->teInput->toPlainText().toUtf8());
|
||||
f.close();
|
||||
QMessageBox::information(this, tr("Saving template file"),
|
||||
tr("Successfully saved template to %1").arg(m_templateFile));
|
||||
}
|
||||
|
||||
void MainWindow::onRefreshOutput()
|
||||
{
|
||||
const QString in = m_ui->teInput->toPlainText();
|
||||
if (!m_generator->setTemplate(QString(), in))
|
||||
{
|
||||
m_ui->teOuput->setText(m_generator->errorString());
|
||||
return;
|
||||
}
|
||||
m_ui->teOuput->setText(m_generator->render());
|
||||
}
|
||||
|
||||
void MainWindow::onGeneratorDb()
|
||||
{
|
||||
if (dynamic_cast<CodeGen::DatabaseGeneratorGrantlee*>(m_generator))
|
||||
return;
|
||||
delete m_generator;
|
||||
m_generator = new CodeGen::DatabaseGeneratorGrantlee(false);
|
||||
if (m_xmlReader && m_structBuilder)
|
||||
m_generator->init(m_xmlReader, m_structBuilder);
|
||||
}
|
||||
|
||||
void MainWindow::onGeneratorDds()
|
||||
{
|
||||
if (dynamic_cast<CodeGen::CppTopicGeneratorGrantlee*>(m_generator))
|
||||
return;
|
||||
delete m_generator;
|
||||
m_generator = new CodeGen::CppTopicGeneratorGrantlee;
|
||||
if (m_xmlReader && m_structBuilder)
|
||||
m_generator->init(m_xmlReader, m_structBuilder);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
NGLApplication app(argc, argv);
|
||||
app.createLogger("CodeGen", "codegen_log4cxx.props");
|
||||
|
||||
NGL::INGLLogger& m_logger = app.baseLogger();
|
||||
STARTUP_LOG(m_logger);
|
||||
|
||||
int iRet = 0;
|
||||
{
|
||||
QScopedPointer<MainWindow> mw(new MainWindow);
|
||||
mw->show();
|
||||
iRet = app.exec();
|
||||
}
|
||||
|
||||
SHUTDOWN_LOG(m_logger);
|
||||
return iRet;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace CodeGen
|
||||
{
|
||||
class XmlReader;
|
||||
class StructBuilder;
|
||||
class GeneratorGrantleeBase;
|
||||
} // namespace CodeGen
|
||||
|
||||
namespace KTextTemplate
|
||||
{
|
||||
class Engine;
|
||||
}
|
||||
class Ui_MainGrantleeGui;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget* parent = nullptr);
|
||||
~MainWindow();
|
||||
private Q_SLOTS:
|
||||
void onSelectDataSource();
|
||||
void onOpenTemplate();
|
||||
void onSaveTemplate();
|
||||
void onRefreshOutput();
|
||||
void onGeneratorDb();
|
||||
void onGeneratorDds();
|
||||
|
||||
private:
|
||||
CodeGen::XmlReader* m_xmlReader;
|
||||
CodeGen::StructBuilder* m_structBuilder;
|
||||
CodeGen::GeneratorGrantleeBase* m_generator;
|
||||
KTextTemplate::Engine* m_engine;
|
||||
QString m_idlServiceInfoFile;
|
||||
QString m_templateFile;
|
||||
Ui_MainGrantleeGui* m_ui;
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainGrantleeGui</class>
|
||||
<widget class="QMainWindow" name="MainGrantleeGui">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>838</width>
|
||||
<height>532</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Input</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="teInput"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="pbRefresh">
|
||||
<property name="text">
|
||||
<string>Refresh Output</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+R</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QTextBrowser" name="teOuput"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>838</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>Fi&le</string>
|
||||
</property>
|
||||
<addaction name="actionSelect_Data_Source"/>
|
||||
<addaction name="actionOpen_Template"/>
|
||||
<addaction name="actionSave_Template"/>
|
||||
<addaction name="actionExit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuGenerator">
|
||||
<property name="title">
|
||||
<string>&Generator</string>
|
||||
</property>
|
||||
<addaction name="actionDatabaseGenerator"/>
|
||||
<addaction name="actionDDS_Generator"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuGenerator"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<action name="actionExit">
|
||||
<property name="text">
|
||||
<string>&Exit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpen_Template">
|
||||
<property name="text">
|
||||
<string>&Open Template</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSelect_Data_Source">
|
||||
<property name="text">
|
||||
<string>&Select Data Source</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave_Template">
|
||||
<property name="text">
|
||||
<string>Sa&ve Template</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDatabaseGenerator">
|
||||
<property name="text">
|
||||
<string>&DatabaseGenerator</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDDS_Generator">
|
||||
<property name="text">
|
||||
<string>DDS &Generator</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for idl struct definitions
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Hanna E. Rathje, SLSE, 2015-08-05
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}.idl
|
||||
#ifndef {{ namespace_abbr }}_idl_included
|
||||
#define {{ namespace_abbr }}_idl_included
|
||||
|
||||
//@copy #include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> // #define NDDSUSERDllExport for linux
|
||||
|
||||
//@copy #include "{{ namespace_abbr }}_enums.hpp"
|
||||
#include "{{ namespace_abbr }}_enums.idl"
|
||||
|
||||
{% for ext_service in ext_services.member %}
|
||||
//@copy #include <{{ ext_service.baseFilePath }}.hpp>
|
||||
#include <{{ ext_service.baseFilePath }}.idl>
|
||||
{% endfor %}
|
||||
|
||||
module {{ namespace }}
|
||||
{
|
||||
{% for idl_struct in idl_structs %}
|
||||
|
||||
{% if idl_struct.comment %} // {{ idl_struct.comment }} {% endif %}
|
||||
struct {{ idl_struct.name }} {% if idl_struct.baseValueType %}: {{ idl_struct.baseValueType }} {% endif %}
|
||||
{ {% for idl_member in idl_struct.member %}
|
||||
{{ idl_member.type }} {{ idl_member.name }}; {{ idl_member.isKey }} {{ idl_member.isOptional }} {% endfor %}
|
||||
}; //@Extensibility {{ idl_struct.extensibility }}
|
||||
|
||||
{% endfor %}
|
||||
}; // namespace {{ namespace }}
|
||||
|
||||
#endif
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id: svc_db_impl.cpp.grantlee 56901 2022-04-22 04:21:30Z karwat $
|
||||
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/svc_db_impl.cpp.grantlee $
|
||||
* $Author: karwat $
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dao.cpp
|
||||
#include "{{ namespace_abbr }}_dao.h"
|
||||
{% if custom_db_procs %}
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_db_impl_custom.h>
|
||||
{% endif %}
|
||||
|
||||
#include <ngldb/dbtransaction.h>
|
||||
#include <ngldb/nsqlquery.h>
|
||||
#include <ngldb/dbconnection.h>
|
||||
#include <ngldb/sql_query_janitor.h>
|
||||
#include <QtSql/QSqlError>
|
||||
#include <QVariant>
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
|
||||
Dao::Dao(int exerciseId)
|
||||
: m_query(nullptr)
|
||||
, m_exerciseId(exerciseId)
|
||||
, m_bEmitMessageOnError(true)
|
||||
, m_bCanHandleSchemas(true)
|
||||
, m_ownDatabaseConnection(true)
|
||||
, m_logger(getLogger("Db"))
|
||||
{}
|
||||
|
||||
Dao::~Dao()
|
||||
{
|
||||
m_query = nullptr;
|
||||
{% if has_db_notifications %}
|
||||
QSqlDriver *driver = m_dbConnection.driver();
|
||||
if (driver && m_dbConnection.isOpen())
|
||||
{
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} driver->unsubscribeFromNotification(QStringLiteral("{{ db_notify }}"));
|
||||
{% endfor %}{% endfor %} }
|
||||
{% endif %}
|
||||
if (m_ownDatabaseConnection && m_dbConnection.isOpen())
|
||||
m_dbConnection.close();
|
||||
}
|
||||
|
||||
bool Dao::init(const QString &dbUser,
|
||||
const QString &dbPasswd,
|
||||
const QString &dbDriver,
|
||||
const QString &dbHost,
|
||||
const QString &dbName)
|
||||
{
|
||||
m_ownDatabaseConnection = true;
|
||||
|
||||
m_dbConnection = DBConnection::open(m_logger, dbUser, dbPasswd, dbDriver, dbHost, dbName, schemaName());
|
||||
|
||||
return initInternal();
|
||||
}
|
||||
|
||||
bool Dao::initInternal()
|
||||
{
|
||||
QSqlDriver *driver = m_dbConnection.driver();
|
||||
if (!driver)
|
||||
{
|
||||
WARNING_LOG(m_logger) << "No SQL driver loaded - no database available!";
|
||||
return false;
|
||||
}
|
||||
const QString driverName = m_dbConnection.driverName();
|
||||
DEBUG_LOG(m_logger) << "Connected to database " << m_dbConnection.databaseName() << "@" << m_dbConnection.hostName()
|
||||
<< ", schema: " << schemaName() << ", used driver: " << driverName;
|
||||
if (!m_dbConnection.isOpen())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Database connection is no open - can not initialize database interface";
|
||||
return false;
|
||||
}
|
||||
if (driverName == QLatin1String("QSQLITE"))
|
||||
m_bCanHandleSchemas = false;
|
||||
|
||||
{% if has_db_notifications %}
|
||||
if (!driver->hasFeature(QSqlDriver::EventNotifications))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Can not subscribe to notifications - sqldriver does not support this feature!";
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(driver, QOverload<const QString&,QSqlDriver::NotificationSource,const QVariant&>::of(&QSqlDriver::notification),
|
||||
this, &Dao::onNotification);
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}driver->subscribeToNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
m_query = std::unique_ptr<NSqlQuery>(new NSqlQuery(m_dbConnection, m_logger));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Dao::schemaName() const
|
||||
{
|
||||
if (!m_bCanHandleSchemas)
|
||||
return QString();
|
||||
if (m_exerciseId <= 0)
|
||||
return QStringLiteral("{{ namespace_abbr }}");
|
||||
return DBCommon::getExerciseSchemaName(m_exerciseId);
|
||||
}
|
||||
|
||||
QString Dao::dbUser() const
|
||||
{
|
||||
if (m_exerciseId > 0)
|
||||
return QStringLiteral("ets_ex_00");
|
||||
else
|
||||
return QStringLiteral("ets_") + schemaName();
|
||||
}
|
||||
|
||||
void Dao::setEmitMessageOnError(bool bEmit)
|
||||
{
|
||||
m_bEmitMessageOnError = bEmit;
|
||||
}
|
||||
|
||||
QString Dao::getTableName(const QString &tableName) const
|
||||
{
|
||||
if (!m_bCanHandleSchemas)
|
||||
return tableName;
|
||||
if (m_exerciseId > 0)
|
||||
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
|
||||
return QStringLiteral("{{ namespace_abbr }}.") + tableName;
|
||||
}
|
||||
|
||||
QSqlError Dao::getLastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
{% if has_db_notifications %}
|
||||
void Dao::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Notification source = " << source;
|
||||
if (source == QSqlDriver::SelfSource)
|
||||
return;
|
||||
// search coresponding signal
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
|
||||
if (name == QStringLiteral("{{ db_notify }}"))
|
||||
{
|
||||
emit signal{{ db_notify }}(payload.toInt());
|
||||
|
||||
return;
|
||||
}
|
||||
{% endfor %}{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
//
|
||||
// --> Struct: Database::{{ cpp_struct.name }}
|
||||
//
|
||||
|
||||
bool Dao::select(QVector<Database::{{ cpp_struct.name }}> &dataVec, const QString &where)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
dataVec.push_back(data);
|
||||
};
|
||||
return selectPrepareInternal{{ cpp_struct.name }}(where) && selectFetchInternal{{ cpp_struct.name }}(cb);
|
||||
}
|
||||
|
||||
bool Dao::selectPrepareInternal{{ cpp_struct.name }}(const QString &where)
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.viewTableName }}")));
|
||||
const QString stmt(QStringLiteral("SELECT {% for row in cpp_struct.viewMember %}{{ row.dbColumnNameView }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" FROM ") + tableName + where);
|
||||
return m_query->prepare(stmt);
|
||||
}
|
||||
|
||||
bool Dao::selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb)
|
||||
{
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
Database::{{ cpp_struct.name }} data;
|
||||
while (m_query->next())
|
||||
{
|
||||
int i = 0;
|
||||
{% for row in cpp_struct.viewMember %}
|
||||
data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %}
|
||||
cb(data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// <-- Database::{{ cpp_struct.name }} end
|
||||
{% endfor %}
|
||||
|
||||
/*static*/ QString Dao::qVariantToJsonString(const QVariant &value)
|
||||
{
|
||||
return value.isNull() ? QStringLiteral("{}") : value.toString();
|
||||
}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id: svc_db_impl.h.grantlee 53676 2021-06-10 10:21:06Z diers $
|
||||
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/svc_db_impl.h.grantlee $
|
||||
* $Author: diers $
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dao.h
|
||||
#pragma once
|
||||
|
||||
#include <QPointF>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QSqlDriver>
|
||||
#include <QSqlDatabase>
|
||||
#include <QtSql/QSqlError>
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_db.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
|
||||
class NSqlQuery;
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
class {{ namespace_abbr_upper }}_COMMON_EXPORT Dao {% if not custom_db_procs %}final {%endif %}: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Dao(int exerciseId);
|
||||
~Dao();
|
||||
|
||||
bool init(const QString &dbUser,
|
||||
const QString &dbPasswd,
|
||||
const QString &dbDriver,
|
||||
const QString &dbHost,
|
||||
const QString &dbName);
|
||||
|
||||
QString schemaName() const;
|
||||
QString dbUser() const;
|
||||
const QSqlDatabase &database() const { return m_dbConnection; }
|
||||
void setEmitMessageOnError(bool bEmit);
|
||||
QSqlError getLastError() const;
|
||||
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
// --> Database::{{ cpp_struct.name }}
|
||||
public:
|
||||
bool select(QVector<Database::{{ cpp_struct.name }}> &dataVec, const QString &where = QString());
|
||||
|
||||
private:
|
||||
bool selectPrepareInternal{{ cpp_struct.name }}(const QString &where);
|
||||
bool selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb);
|
||||
// <-- Database::{{ cpp_struct.name }}
|
||||
{% endfor %}
|
||||
|
||||
static QString qVariantToJsonString(const QVariant &value);
|
||||
|
||||
protected:
|
||||
QString getTableName(const QString &tableName) const;
|
||||
{% if has_db_notifications %}
|
||||
private Q_SLOTS:
|
||||
void onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload);
|
||||
|
||||
Q_SIGNALS:
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} void signal{{ db_notify }}(int id);
|
||||
{% endfor %}{% endfor %}{% endif %}
|
||||
protected:
|
||||
QSqlDatabase m_dbConnection;
|
||||
std::unique_ptr<NSqlQuery> m_query;
|
||||
QSqlError m_lastError;
|
||||
int m_exerciseId;
|
||||
bool m_bEmitMessageOnError;
|
||||
bool m_bCanHandleSchemas;
|
||||
bool m_ownDatabaseConnection;
|
||||
LOGGER_T m_logger;
|
||||
|
||||
private:
|
||||
bool initInternal();
|
||||
};// class Dao
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_db.h
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
#include <QtSql/QSqlError>
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_db_structs.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
|
||||
|
||||
{% for ext_service in ext_services.member %}
|
||||
#include <{{ ext_service.baseFilePath }}.hpp>
|
||||
{% endfor %}
|
||||
|
||||
namespace NGL
|
||||
{
|
||||
class INGLLogger;
|
||||
}
|
||||
class QSqlDatabase;
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
|
||||
class {{ namespace_abbr_upper }}_COMMON_EXPORT DatabaseInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual ~DatabaseInterface() = default;
|
||||
|
||||
/**
|
||||
* Create a new instance for {{ namespace }}::DatabaseInterface.
|
||||
* @param exerciseId if <= 0 -> global service
|
||||
*/
|
||||
static std::shared_ptr<DatabaseInterface> create(int exerciseId);
|
||||
|
||||
/**
|
||||
* @brief initialize the database interface (must be called after the db connection was opened!)
|
||||
* @return true on succcess
|
||||
*/
|
||||
virtual bool init(const QSqlDatabase &dbConnection) = 0;
|
||||
virtual bool init(const QString &dbUser,
|
||||
const QString &dbPasswd,
|
||||
const QString &dbDriver,
|
||||
const QString &dbHost,
|
||||
const QString &dbName) = 0;
|
||||
|
||||
/**
|
||||
* @brief return the current schema name depending if exercise id > 0 or not
|
||||
*/
|
||||
virtual QString schemaName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief return the current database user dependening if exercise id > 0 or not
|
||||
*/
|
||||
virtual QString dbUser() const = 0;
|
||||
|
||||
/**
|
||||
* @brief return the database connection used in this DatabaseInterface
|
||||
*/
|
||||
virtual const QSqlDatabase &database() const = 0;
|
||||
|
||||
/**
|
||||
* emit an error message (ERROR_LOG) when a statement fails to execute (default: on)
|
||||
*/
|
||||
virtual void setEmitMessageOnError(bool bEmit) = 0;
|
||||
|
||||
virtual QSqlError getLastError() const = 0;
|
||||
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
//
|
||||
// Database::{{ cpp_struct.name }}
|
||||
//
|
||||
public:
|
||||
virtual bool insert(Database::{{ cpp_struct.name }} &data) = 0;
|
||||
virtual bool upsert(Database::{{ cpp_struct.name }} &data) = 0;
|
||||
virtual bool update(const Database::{{ cpp_struct.name }} &data) = 0;
|
||||
{% if cpp_struct.insertStructures|length == 1 %}
|
||||
virtual bool insert(std::vector<Database::{{ cpp_struct.name }}> &cont) = 0;
|
||||
virtual bool upsert(std::vector<Database::{{ cpp_struct.name }}> &cont) = 0;
|
||||
virtual bool update(const std::vector<Database::{{ cpp_struct.name }}> &cont) = 0;
|
||||
{% endif %}
|
||||
virtual bool select(QVector<Database::{{ cpp_struct.name }}> &dataVec) = 0;
|
||||
virtual bool select(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb) = 0;
|
||||
{% for fk in cpp_struct.selectRemoveMember %}
|
||||
virtual bool selectBy{{ fk.cppMemberNameCc }}(QVector<Database::{{ cpp_struct.name }}> &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) = 0;
|
||||
virtual bool selectBy{{ fk.cppMemberNameCc }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) = 0;
|
||||
{% endfor %}
|
||||
virtual bool select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) = 0;
|
||||
virtual bool remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) = 0;
|
||||
{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %}
|
||||
virtual bool remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) = 0;
|
||||
{% endfor %} {% endif %}
|
||||
virtual bool fixUpSequence{{ cpp_struct.name }}() = 0;
|
||||
virtual bool truncate{{ cpp_struct.name }}() = 0;
|
||||
{% if cpp_struct.dbNotifications %}
|
||||
Q_SIGNALS:
|
||||
{% for dbnotify in cpp_struct.dbNotifications %}
|
||||
// database notification on insert/update/delete
|
||||
void {{ dbnotify }}(const QVariant &payload);
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
//
|
||||
// Database::{{ cpp_struct.name }} end
|
||||
//
|
||||
{% endfor %}
|
||||
{% if data_structs|length > 0 %}
|
||||
public:{% for data_struct in data_structs %}
|
||||
// read only struct Database::{{ data_struct.name }}
|
||||
virtual bool select(QVector<Database::{{ data_struct.name }}> &dataVec) = 0;
|
||||
{% endfor %} {% endif %}
|
||||
{% if custom_db_procs|length > 0 %}
|
||||
public:{% for db_proc in custom_db_procs %}
|
||||
// custom proc, body must be implemented by user
|
||||
virtual {{ db_proc.signature }} = 0;
|
||||
{% endfor %} {% endif %}
|
||||
|
||||
public:
|
||||
|
||||
// vector of database struct to/from JSON-array
|
||||
// helper for BaseDownloader
|
||||
template<typename T>
|
||||
static QJsonArray toJson(const QVector<T> &dataVec)
|
||||
{
|
||||
return Database::toJson<T>(dataVec);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::vector<T> fromJson(const QJsonArray &arr)
|
||||
{
|
||||
return Database::fromJson<T>(arr);
|
||||
}
|
||||
};// class DatabaseInterface
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+732
@@ -0,0 +1,732 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_db_impl.cpp
|
||||
#include "{{ namespace_abbr }}_db_impl.h"
|
||||
{% if custom_db_procs %}
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_db_impl_custom.h>
|
||||
{% endif %}
|
||||
|
||||
#include <ngldb/dbtransaction.h>
|
||||
#include <ngldb/nsqlquery.h>
|
||||
#include <ngldb/dbconnection.h>
|
||||
#include <ngldb/sql_query_janitor.h>
|
||||
#include <QtSql/QSqlError>
|
||||
#include <QVariant>
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
|
||||
QString qVariantToJsonString(const QVariant &value)
|
||||
{
|
||||
return value.isNull() ? QStringLiteral("{}") : value.toString();
|
||||
}
|
||||
|
||||
QString fixupEmptyJsonString(const QString &str)
|
||||
{
|
||||
return str.isEmpty() ? QStringLiteral("{}") : str;
|
||||
}
|
||||
|
||||
// DatabaseInterface
|
||||
std::shared_ptr<DatabaseInterface> DatabaseInterface::create(int exerciseId)
|
||||
{
|
||||
{% if custom_db_procs %}
|
||||
return std::make_shared<DatabaseInterfaceImplCustom>(exerciseId);
|
||||
{% else %}
|
||||
return std::make_shared<DatabaseInterfaceImpl>(exerciseId);
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
DatabaseInterfaceImpl::DatabaseInterfaceImpl(int exerciseId)
|
||||
: m_query(nullptr)
|
||||
, m_exerciseId(exerciseId)
|
||||
, m_bEmitMessageOnError(true)
|
||||
, m_bCanHandleSchemas(true)
|
||||
, m_ownDatabaseConnection(true)
|
||||
, m_logger(getLogger("Db"))
|
||||
{}
|
||||
|
||||
DatabaseInterfaceImpl::~DatabaseInterfaceImpl()
|
||||
{
|
||||
m_query = nullptr;
|
||||
{% if has_db_notifications %}
|
||||
QSqlDriver *driver = m_dbConnection.driver();
|
||||
if (driver && m_dbConnection.isOpen())
|
||||
{
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
|
||||
driver->unsubscribeFromNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
if (m_ownDatabaseConnection && m_dbConnection.isOpen())
|
||||
m_dbConnection.close();
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::init(const QSqlDatabase &dbConnection)
|
||||
{
|
||||
m_dbConnection = dbConnection;
|
||||
m_ownDatabaseConnection = false;
|
||||
return initInternal();
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::init(const QString &dbUser,
|
||||
const QString &dbPasswd,
|
||||
const QString &dbDriver,
|
||||
const QString &dbHost,
|
||||
const QString &dbName)
|
||||
{
|
||||
m_ownDatabaseConnection = true;
|
||||
|
||||
m_dbConnection = DBConnection::open(m_logger, dbUser, dbPasswd, dbDriver, dbHost, dbName, schemaName());
|
||||
|
||||
return initInternal();
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::initInternal()
|
||||
{
|
||||
QSqlDriver *driver = m_dbConnection.driver();
|
||||
if (!driver)
|
||||
{
|
||||
WARNING_LOG(m_logger) << "No SQL driver loaded - no database available!";
|
||||
return false;
|
||||
}
|
||||
const QString driverName = m_dbConnection.driverName();
|
||||
DEBUG_LOG(m_logger) << "Connected to database " << m_dbConnection.databaseName() << "@" << m_dbConnection.hostName()
|
||||
<< ", schema: " << schemaName() << ", used driver: " << driverName;
|
||||
if (!m_dbConnection.isOpen())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Database connection is no open - can not initialize database interface";
|
||||
return false;
|
||||
}
|
||||
if (driverName == QLatin1String("QSQLITE"))
|
||||
m_bCanHandleSchemas = false;
|
||||
|
||||
{% if has_db_notifications %}
|
||||
if (!driver->hasFeature(QSqlDriver::EventNotifications))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Can not subscribe to notifications - sqldriver does not support this feature!";
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(driver, QOverload<const QString&,QSqlDriver::NotificationSource,const QVariant&>::of(&QSqlDriver::notification),
|
||||
this, &DatabaseInterfaceImpl::onNotification);
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
|
||||
driver->subscribeToNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
m_query = std::unique_ptr<NSqlQuery>(new NSqlQuery(m_dbConnection, m_logger));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DatabaseInterfaceImpl::schemaName() const
|
||||
{
|
||||
if (!m_bCanHandleSchemas)
|
||||
return QString();
|
||||
if (m_exerciseId <= 0)
|
||||
return QStringLiteral("{{ namespace_abbr }}");
|
||||
return DBCommon::getExerciseSchemaName(m_exerciseId);
|
||||
}
|
||||
|
||||
QString DatabaseInterfaceImpl::dbUser() const
|
||||
{
|
||||
if (m_exerciseId > 0)
|
||||
return QStringLiteral("ets_ex_00");
|
||||
else
|
||||
return QStringLiteral("ets_") + schemaName();
|
||||
}
|
||||
|
||||
void DatabaseInterfaceImpl::setEmitMessageOnError(bool bEmit)
|
||||
{
|
||||
m_bEmitMessageOnError = bEmit;
|
||||
}
|
||||
|
||||
QString DatabaseInterfaceImpl::getTableName(const QString &tableName) const
|
||||
{
|
||||
if (!m_bCanHandleSchemas)
|
||||
return tableName;
|
||||
if (m_exerciseId > 0)
|
||||
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
|
||||
return QStringLiteral("{{ namespace_abbr }}.") + tableName;
|
||||
}
|
||||
|
||||
QSqlError DatabaseInterfaceImpl::getLastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
{% if has_db_notifications %}
|
||||
void DatabaseInterfaceImpl::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "Notification source = " << source;
|
||||
// no need to emit notification signals
|
||||
if (source == QSqlDriver::SelfSource)
|
||||
return;
|
||||
// search coresponding signal
|
||||
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
|
||||
if (name == QLatin1String("{{ db_notify }}"))
|
||||
{
|
||||
emit {{ db_notify }}(payload);
|
||||
}{% endfor %}{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
//
|
||||
// Struct: Database::{{ cpp_struct.name }}
|
||||
//
|
||||
bool DatabaseInterfaceImpl::insert(Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
{% if cpp_struct.insertStructures|length > 1 %}
|
||||
DBTransaction transaction(m_dbConnection);
|
||||
{% endif %}
|
||||
{% for insertStruct in cpp_struct.insertStructures %}
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
|
||||
|
||||
QString keyColumnInsertKey;
|
||||
QString keyColumnInsertVal;
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
const bool bInsertWithKey = data.{{cpp_struct.primaryKey.cppMemberName}} != {{cpp_struct.primaryKey.cppDefault}};
|
||||
if (bInsertWithKey)
|
||||
{
|
||||
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
}
|
||||
{% endif %}
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
|
||||
QStringLiteral(" (") + keyColumnInsertKey +
|
||||
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" ) VALUES (") + keyColumnInsertVal +
|
||||
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )"));
|
||||
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (bInsertWithKey)
|
||||
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
|
||||
{% endif %}
|
||||
{% for row in insertStruct.member %}
|
||||
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
|
||||
{% endfor %}
|
||||
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% if insertStruct.serialMember.cppMemberName %}
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
|
||||
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
|
||||
{% else %}
|
||||
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
|
||||
{% endif %}
|
||||
{% if insertStruct.baseId.cppMemberName %}
|
||||
data.{{ insertStruct.baseId.cppMemberName }} = data.{{ insertStruct.serialMember.cppMemberName }};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% if cpp_struct.insertStructures|length > 1 %}
|
||||
transaction.commit();
|
||||
{% endif %}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::upsert(Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
{% if cpp_struct.insertStructures|length > 1 %}
|
||||
DBTransaction transaction(m_dbConnection);
|
||||
{% endif %}
|
||||
{% for insertStruct in cpp_struct.insertStructures %}
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
|
||||
|
||||
QString keyColumnInsertKey;
|
||||
QString keyColumnInsertKeyConflict;
|
||||
QString keyColumnInsertVal;
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
const bool bInsertWithKey = true;
|
||||
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
{% endif %}
|
||||
|
||||
keyColumnInsertKeyConflict = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }} ");
|
||||
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
|
||||
QStringLiteral(" (") + keyColumnInsertKey +
|
||||
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" ) VALUES (") + keyColumnInsertVal +
|
||||
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )") +
|
||||
QStringLiteral(" ON CONFLICT (") + keyColumnInsertKeyConflict + QStringLiteral(") DO UPDATE ") +
|
||||
QStringLiteral(" SET"
|
||||
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" WHERE excluded.{{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
|
||||
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (bInsertWithKey)
|
||||
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
|
||||
{% endif %}
|
||||
{% for row in insertStruct.member %}
|
||||
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
|
||||
{% endfor %}
|
||||
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% if insertStruct.serialMember.cppMemberName %}
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
|
||||
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
|
||||
{% else %}
|
||||
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
|
||||
{% endif %}
|
||||
{% if insertStruct.baseId.cppMemberName %}
|
||||
data.{{ insertStruct.baseId.cppMemberName }} = data.{{ insertStruct.serialMember.cppMemberName }};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% if cpp_struct.insertStructures|length > 1 %}
|
||||
transaction.commit();
|
||||
{% endif %}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::update(const Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
{% if cpp_struct.insertStructures|length > 1 %}
|
||||
DBTransaction transaction(m_dbConnection);
|
||||
{% endif %}
|
||||
{% for insertStruct in cpp_struct.insertStructures %}
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
|
||||
const QString stmt(QStringLiteral("UPDATE ") + tableName +
|
||||
QStringLiteral(" SET"
|
||||
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" WHERE {{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
|
||||
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% for row in insertStruct.member %}
|
||||
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
|
||||
{% endfor %}
|
||||
{% if insertStruct.serialMember.cppMemberName %}
|
||||
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
|
||||
{% endif %}
|
||||
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
{% endfor %}
|
||||
{% if cpp_struct.insertStructures|length > 1 %}
|
||||
transaction.commit();
|
||||
{% endif %}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% if cpp_struct.insertStructures|length == 1 %}
|
||||
// current only implemented for 'plain' structures with only one affected table
|
||||
bool DatabaseInterfaceImpl::insert(std::vector<Database::{{ cpp_struct.name }}> &cont)
|
||||
{
|
||||
if (cont.empty())
|
||||
return true;
|
||||
|
||||
{% for insertStruct in cpp_struct.insertStructures %}
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
|
||||
|
||||
QString keyColumnInsertKey;
|
||||
QString keyColumnInsertVal;
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
const bool bInsertWithKey = cont[0].{{cpp_struct.primaryKey.cppMemberName}} != {{cpp_struct.primaryKey.cppDefault}};
|
||||
// the first element determines the stmt to be used
|
||||
if (bInsertWithKey)
|
||||
{
|
||||
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
}
|
||||
{% endif %}
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
|
||||
QStringLiteral(" (") + keyColumnInsertKey +
|
||||
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" ) VALUES (") + keyColumnInsertVal +
|
||||
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )"));
|
||||
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &data : cont)
|
||||
{
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (bInsertWithKey)
|
||||
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
|
||||
{% endif %}
|
||||
{% for row in insertStruct.member %}
|
||||
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
|
||||
{% endfor %}
|
||||
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% if insertStruct.serialMember.cppMemberName %}
|
||||
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
|
||||
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (bInsertWithKey)
|
||||
fixUpSequence{{ cpp_struct.nameCc }}();
|
||||
{% endif %}
|
||||
|
||||
}
|
||||
{% endfor %}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::upsert(std::vector<Database::{{ cpp_struct.name }}> &cont)
|
||||
{
|
||||
if (cont.empty())
|
||||
return true;
|
||||
|
||||
{% for insertStruct in cpp_struct.insertStructures %}
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
|
||||
|
||||
QString keyColumnInsertKey;
|
||||
QString keyColumnInsertKeyConflict;
|
||||
QString keyColumnInsertVal;
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
const bool bInsertWithKey = true;
|
||||
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
|
||||
{% endif %}
|
||||
// the first element determines the stmt to be used
|
||||
keyColumnInsertKeyConflict = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }} ");
|
||||
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
|
||||
QStringLiteral(" (") + keyColumnInsertKey +
|
||||
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" ) VALUES (") + keyColumnInsertVal +
|
||||
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )") +
|
||||
QStringLiteral(" ON CONFLICT (") + keyColumnInsertKeyConflict + QStringLiteral(") DO UPDATE ") +
|
||||
QStringLiteral(" SET"
|
||||
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" WHERE excluded.{{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
|
||||
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &data : cont)
|
||||
{
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (bInsertWithKey)
|
||||
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
|
||||
{% endif %}
|
||||
{% for row in insertStruct.member %}
|
||||
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
|
||||
{% endfor %}
|
||||
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
{% if insertStruct.serialMember.cppMemberName %}
|
||||
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
|
||||
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
|
||||
if (bInsertWithKey)
|
||||
fixUpSequence{{ cpp_struct.nameCc }}();
|
||||
{% endif %}
|
||||
|
||||
}
|
||||
{% endfor %}
|
||||
return true;
|
||||
}
|
||||
|
||||
// current only implemented for 'plain' structures with only one affected table
|
||||
bool DatabaseInterfaceImpl::update(const std::vector<Database::{{ cpp_struct.name }}> &cont)
|
||||
{
|
||||
if (cont.empty())
|
||||
return true;
|
||||
|
||||
{% for insertStruct in cpp_struct.insertStructures %}
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
|
||||
const QString stmt(QStringLiteral("UPDATE ") + tableName +
|
||||
QStringLiteral(" SET"
|
||||
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" WHERE {{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
|
||||
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &data : cont)
|
||||
{
|
||||
{% for row in insertStruct.member %}
|
||||
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
|
||||
{% endfor %}
|
||||
{% if insertStruct.serialMember.cppMemberName %}
|
||||
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
|
||||
{% endif %}
|
||||
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{% endfor %}
|
||||
return true;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
bool DatabaseInterfaceImpl::select(QVector<Database::{{ cpp_struct.name }}> &dataVec)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
dataVec.push_back(data);
|
||||
};
|
||||
return selectPrepareInternal{{ cpp_struct.name }}(QString()) && selectFetchInternal{{ cpp_struct.name }}(cb);
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::select(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
return selectPrepareInternal{{ cpp_struct.name }}(QString()) && selectFetchInternal{{ cpp_struct.name }}(cb);
|
||||
}
|
||||
|
||||
{% for fk in cpp_struct.selectRemoveMember %}
|
||||
bool DatabaseInterfaceImpl::selectBy{{ fk.cppMemberNameCc }}(QVector<Database::{{ cpp_struct.name }}> &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
if (!selectPrepareInternal{{ cpp_struct.name }}(QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.dbColumnName }}") + orderBy))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text();
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
m_query->bindValue(QStringLiteral(":{{ fk.dbColumnName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }});
|
||||
const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
dataVec.push_back(data);
|
||||
};
|
||||
return selectFetchInternal{{ cpp_struct.name }}(cb);
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::selectBy{{ fk.cppMemberNameCc }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
if (!selectPrepareInternal{{ cpp_struct.name }}(QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.dbColumnName }}") + orderBy))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text();
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
m_query->bindValue(QStringLiteral(":{{ fk.dbColumnName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }});
|
||||
return selectFetchInternal{{ cpp_struct.name }}(cb);
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
bool DatabaseInterfaceImpl::selectPrepareInternal{{ cpp_struct.name }}(const QString &where)
|
||||
{
|
||||
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.viewTableName }}")));
|
||||
const QString stmt(QStringLiteral("SELECT {% for row in cpp_struct.viewMember %}{{ row.dbColumnNameView }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" FROM ") + tableName + where);
|
||||
return m_query->prepare(stmt);
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb)
|
||||
{
|
||||
if (!m_query->exec(m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
Database::{{ cpp_struct.name }} data;
|
||||
while (m_query->next())
|
||||
{
|
||||
int i = 0;
|
||||
{% for row in cpp_struct.viewMember %}
|
||||
data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %}
|
||||
cb(data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
int iCnt = 0;
|
||||
const auto cb =[&data, &iCnt] (const Database::{{ cpp_struct.name }} &content)
|
||||
{
|
||||
data = content;
|
||||
++iCnt;
|
||||
};
|
||||
const QString where = QStringLiteral(" WHERE {{ cpp_struct.primaryKey.dbColumnName }} = %1").arg({{ cpp_struct.primaryKey.cppMemberName }});
|
||||
if (!selectPrepareInternal{{ cpp_struct.name }}(where) || !selectFetchInternal{{ cpp_struct.name }}(cb))
|
||||
return false;
|
||||
|
||||
return (iCnt == 1);
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }})
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
|
||||
const QString stmt(QStringLiteral("DELETE FROM ") + tableName +
|
||||
QStringLiteral(" WHERE {{ cpp_struct.primaryKey.dbColumnName }} = ") +
|
||||
QString::number({{ cpp_struct.primaryKey.cppMemberName }}));
|
||||
|
||||
if (!m_query->exec(stmt, m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %}
|
||||
bool DatabaseInterfaceImpl::remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }})
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
|
||||
const QString stmt(QStringLiteral("DELETE FROM ") + tableName +
|
||||
QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.cppMemberName }}"));
|
||||
|
||||
if (!m_query->prepare(stmt))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text();
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_query->bindValue(QStringLiteral(":{{ fk.cppMemberName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }});
|
||||
|
||||
|
||||
return m_query->exec(m_bEmitMessageOnError);
|
||||
}
|
||||
{% endfor %} {% endif %}
|
||||
bool DatabaseInterfaceImpl::fixUpSequence{{ cpp_struct.nameCc }}()
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
|
||||
const QString stmt(QStringLiteral("SELECT setval('") + tableName + QStringLiteral("_{{ cpp_struct.primaryKey.dbColumnName }}_seq', COALESCE((SELECT MAX({{ cpp_struct.primaryKey.dbColumnName }})+1 FROM ") +
|
||||
tableName + QStringLiteral("), 1),false)"));
|
||||
|
||||
if (!m_query->exec(stmt, m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DatabaseInterfaceImpl::truncate{{ cpp_struct.nameCc }}()
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
|
||||
const QString stmt(QStringLiteral("TRUNCATE TABLE ") + tableName + QStringLiteral(" CASCADE"));
|
||||
|
||||
if (!m_query->exec(stmt, m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// Database::{{ cpp_struct.name }} end
|
||||
{% endfor %}
|
||||
|
||||
{% for ds in data_structs %}
|
||||
bool DatabaseInterfaceImpl::select(QVector<Database::{{ ds.name }}> &dataVec)
|
||||
{
|
||||
SqlQueryJanitor janitor(m_query);
|
||||
const QString tableName(getTableName(QStringLiteral("{{ ds.viewTableName }}")));
|
||||
const QString stmt(QStringLiteral("SELECT {% for row in ds.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
|
||||
" FROM ") + tableName);
|
||||
|
||||
if (!m_query->exec(stmt, m_bEmitMessageOnError))
|
||||
{
|
||||
m_lastError = m_query->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
Database::{{ ds.name }} data;
|
||||
while (m_query->next())
|
||||
{
|
||||
int i = 0;
|
||||
{% for row in ds.member %}
|
||||
data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %}
|
||||
dataVec.append(data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
{% endfor %}
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_db_impl.h
|
||||
#pragma once
|
||||
|
||||
#include <QPointF>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QSqlDriver>
|
||||
#include <QSqlDatabase>
|
||||
#include <QtSql/QSqlError>
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_db.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
|
||||
class NSqlQuery;
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
class {{ namespace_abbr_upper }}_COMMON_EXPORT DatabaseInterfaceImpl {% if not custom_db_procs %}final {%endif %}: public DatabaseInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DatabaseInterfaceImpl(int exerciseId);
|
||||
~DatabaseInterfaceImpl();
|
||||
|
||||
bool init(const QSqlDatabase &dbConnection) override final;
|
||||
bool init(const QString &dbUser,
|
||||
const QString &dbPasswd,
|
||||
const QString &dbDriver,
|
||||
const QString &dbHost,
|
||||
const QString &dbName) override final;
|
||||
|
||||
QString schemaName() const override final;
|
||||
QString dbUser() const override final;
|
||||
const QSqlDatabase &database() const override final { return m_dbConnection; }
|
||||
void setEmitMessageOnError(bool bEmit) override final;
|
||||
QSqlError getLastError() const override final;
|
||||
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
|
||||
// Database::{{ cpp_struct.name }}
|
||||
bool insert(Database::{{ cpp_struct.name }} &data) override final;
|
||||
bool upsert(Database::{{ cpp_struct.name }} &data) override final;
|
||||
bool update(const Database::{{ cpp_struct.name }} &data) override final;
|
||||
{% if cpp_struct.insertStructures|length == 1 %}
|
||||
bool insert(std::vector<Database::{{ cpp_struct.name }}> &cont) override final;
|
||||
bool upsert(std::vector<Database::{{ cpp_struct.name }}> &cont) override final;
|
||||
bool update(const std::vector<Database::{{ cpp_struct.name }}> &cont) override final;
|
||||
{% endif %}
|
||||
bool select(QVector<Database::{{ cpp_struct.name }}> &dataVec) override final;
|
||||
bool select(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb) override final;
|
||||
{% for fk in cpp_struct.selectRemoveMember %}
|
||||
bool selectBy{{ fk.cppMemberNameCc }}(QVector<Database::{{ cpp_struct.name }}> &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) override final;
|
||||
bool selectBy{{ fk.cppMemberNameCc }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) override final;
|
||||
{% endfor %}
|
||||
protected:
|
||||
bool selectPrepareInternal{{ cpp_struct.name }}(const QString &where);
|
||||
bool selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb);
|
||||
public:
|
||||
bool select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) override final;
|
||||
bool remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) override final;
|
||||
{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %}
|
||||
bool remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) override final;
|
||||
{% endfor %} {% endif %}
|
||||
bool fixUpSequence{{ cpp_struct.name }}() override final;
|
||||
bool truncate{{ cpp_struct.name }}() override final;
|
||||
// Database::{{ cpp_struct.name }}
|
||||
{% endfor %}
|
||||
|
||||
{% for data_struct in data_structs %}
|
||||
// read only struct{{ data_struct.name }}
|
||||
bool select(QVector<Database::{{ data_struct.name }}> &dataVec) override final;
|
||||
{% endfor %}
|
||||
|
||||
protected:
|
||||
QString getTableName(const QString &tableName) const;
|
||||
{% if has_db_notifications %}
|
||||
private Q_SLOTS:
|
||||
void onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload);
|
||||
{% endif %}
|
||||
protected:
|
||||
QSqlDatabase m_dbConnection;
|
||||
std::unique_ptr<NSqlQuery> m_query;
|
||||
QSqlError m_lastError;
|
||||
int m_exerciseId;
|
||||
bool m_bEmitMessageOnError;
|
||||
bool m_bCanHandleSchemas;
|
||||
bool m_ownDatabaseConnection;
|
||||
LOGGER_T m_logger;
|
||||
private:
|
||||
bool initInternal();
|
||||
};// class DatabaseInterfaceImpl
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_db_structs.cpp
|
||||
#include "{{ namespace_abbr }}_db_structs.h"
|
||||
#include "{{ namespace_abbr }}_export.h"
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Database
|
||||
{
|
||||
QString jsonValueToJsonString(const QJsonValue &value)
|
||||
{
|
||||
return value.isNull() ? QStringLiteral("{}") : value.toString();
|
||||
}
|
||||
|
||||
QString qVariantToJsonString(const QVariant &value)
|
||||
{
|
||||
return value.isNull() ? QStringLiteral("{}") : value.toString();
|
||||
}
|
||||
|
||||
QString fixupEmptyJsonString(const QString &str)
|
||||
{
|
||||
return str.isEmpty() ? QStringLiteral("{}") : str;
|
||||
}
|
||||
|
||||
// Basic type toJson overloads
|
||||
QJsonValue toJson(int val)
|
||||
{
|
||||
return QJsonValue(val);
|
||||
}
|
||||
QJsonValue toJson(double val)
|
||||
{
|
||||
return QJsonValue(val);
|
||||
}
|
||||
QJsonValue toJson(qint64 val)
|
||||
{
|
||||
return QJsonValue(QString::number(val));
|
||||
}
|
||||
QJsonValue toJson(const std::string &val)
|
||||
{
|
||||
return QJsonValue(QString::fromStdString(val));
|
||||
}
|
||||
|
||||
// QPointF
|
||||
QJsonValue toJson(const QPointF &element)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("x"), QJsonValue(element.x()));
|
||||
obj.insert(QStringLiteral("y"), QJsonValue(element.y()));
|
||||
return QJsonValue(obj);
|
||||
}
|
||||
template<>
|
||||
QPointF fromJson(const QJsonValue &val)
|
||||
{
|
||||
QJsonObject obj = val.toObject();
|
||||
return QPointF(obj.value(QStringLiteral("x")).toDouble(), obj.value(QStringLiteral("y")).toDouble());
|
||||
}
|
||||
{% if need_point3d %}
|
||||
// QPoint3D
|
||||
QJsonValue toJson(const LtsUtils::QPoint3D &element)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("x"), QJsonValue(element.x()));
|
||||
obj.insert(QStringLiteral("y"), QJsonValue(element.y()));
|
||||
obj.insert(QStringLiteral("z"), QJsonValue(element.z()));
|
||||
return QJsonValue(obj);
|
||||
}
|
||||
template<>
|
||||
LtsUtils::QPoint3D fromJson(const QJsonValue &val)
|
||||
{
|
||||
QJsonObject obj = val.toObject();
|
||||
return LtsUtils::QPoint3D(obj.value(QStringLiteral("x")).toDouble(), obj.value(QStringLiteral("y")).toDouble(), obj.value(QStringLiteral("z")).toDouble());
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
//
|
||||
// Struct: {{ cpp_struct.name }}
|
||||
//
|
||||
QJsonObject toJson(const {{ cpp_struct.name }} &element)
|
||||
{
|
||||
QJsonObject obj;
|
||||
{% for row in cpp_struct.jsonMember %}
|
||||
{{ row.dbToJsonElement }};
|
||||
{% endfor %}
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<>
|
||||
{{ cpp_struct.name }} fromJson(const QJsonValue &value)
|
||||
{
|
||||
const QJsonObject objElement(value.toObject());
|
||||
{{ cpp_struct.name }} element;
|
||||
|
||||
{% for row in cpp_struct.jsonMember %}
|
||||
{ {{ row.jsonElementToDb }}; } {% endfor %}
|
||||
|
||||
return element;
|
||||
}
|
||||
// {{ cpp_struct.name }} end
|
||||
{% endfor %}
|
||||
|
||||
} // namespace Database
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for 'real' structures including insert/update/delete functions for db
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_db_structs.h
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QPointF>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
{% if need_point3d %}
|
||||
#include <LtsUtilsCore/qpoint3d.h>
|
||||
{% endif %}
|
||||
{% if need_pgarray %}
|
||||
#include <LtsUtilsCore/pgarray.h>
|
||||
{% endif %}
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
|
||||
|
||||
{% for ext_service in ext_services.member %}
|
||||
#include <{{ ext_service.baseFilePath }}.hpp>
|
||||
{% endfor %}
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Database
|
||||
{
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
// {{ cpp_struct.name }}
|
||||
struct {{ cpp_struct.name }}
|
||||
{
|
||||
{% for m in cpp_struct.cppMember %}{{ m.cppType }} {{ m.cppMemberName }}{% if m.cppDefault %} = {{ m.cppDefault }}{% endif %};
|
||||
{% endfor %}
|
||||
};{% endfor %}
|
||||
|
||||
{% for data_struct in data_structs %}
|
||||
// read only struct {{ data_struct.name }}
|
||||
struct {{ data_struct.name }}
|
||||
{
|
||||
{% for m in data_struct.cppMember %}{{ m.cppType }} {{ m.cppMemberName }}{% if m.cppDefault %} = {{ m.cppDefault }}{% endif %};
|
||||
{% endfor %}
|
||||
};{% endfor %}
|
||||
|
||||
// converting DbStructs to/from JSON
|
||||
template<typename T>
|
||||
T fromJson(const QJsonValue &value);
|
||||
{% for cpp_struct in cpp_structs %}
|
||||
// {{ cpp_struct.name }}
|
||||
template <> {{ namespace_abbr_upper }}_{{ library_target }}_EXPORT {{ cpp_struct.name }} fromJson(const QJsonValue &value);
|
||||
{{ namespace_abbr_upper }}_{{ library_target }}_EXPORT QJsonObject toJson(const {{ cpp_struct.name }} &element);{% endfor %}
|
||||
|
||||
// vector of database struct to/from JSON-array
|
||||
template<typename T>
|
||||
QJsonArray toJson(const QVector<T> &dataVec)
|
||||
{
|
||||
QJsonArray arr;
|
||||
for (const T &data : dataVec)
|
||||
arr.append(QJsonValue(toJson(data)));
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> fromJson(const QJsonArray &arr)
|
||||
{
|
||||
std::vector<T> dataVec;
|
||||
dataVec.reserve(arr.size());
|
||||
for (const QJsonValue &val : arr)
|
||||
dataVec.push_back(fromJson<T>(val));
|
||||
|
||||
return dataVec;
|
||||
}
|
||||
};// namespace Database
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* {{ namespace_abbr_upper }} Data Topic Client Interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Hanna E. Rathje, SLSE, 2016-01-04
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dtc.h
|
||||
#pragma once
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
#include <LtsUtilsCore/data_modification_enum.h>
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
|
||||
|
||||
// Bring Core namespace types into scope for Interface namespace
|
||||
using namespace {{ namespace }};
|
||||
|
||||
namespace DDSConnector
|
||||
{
|
||||
class DdsConnector;
|
||||
}
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class {{ namespace_abbr_upper }}_COMMON_EXPORT Dtc : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
Dtc() = default;
|
||||
public:
|
||||
virtual ~Dtc() = default;
|
||||
|
||||
/**
|
||||
* Create a new instance for {{ namespace }}::Interface::Dtc.
|
||||
*/
|
||||
static std::shared_ptr<Dtc> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
|
||||
/**
|
||||
* A unique identifier for this DTC
|
||||
*/
|
||||
static inline const char *identifier() { return "{{ namespace }}.Interface.Dtc"; }
|
||||
|
||||
/**
|
||||
* initialize this Dts instance with all data topic readers
|
||||
*/
|
||||
virtual bool enableAll(bool enable = true) = 0;
|
||||
/**
|
||||
* only initialize the specific reader (can be called instead enableAll())
|
||||
*/
|
||||
{% for dt in dataTopics.member %} {% if dt.dtcImpl %}
|
||||
virtual bool enable{{ dt.name }}(bool enable = true) = 0;
|
||||
virtual bool is{{ dt.name }}Enabled() const = 0;
|
||||
virtual bool waitForHistoricalData{{ dt.name }}(int timeoutMSec = -1) = 0;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
{% for cp in custom_dtc_procs %}
|
||||
// custom procs, body must be implemented by user
|
||||
virtual {{ cp.signature }} = 0;
|
||||
{% endfor %}
|
||||
|
||||
{% for dt in dataTopics.member %}
|
||||
public:
|
||||
//
|
||||
// Data Topic '{{ dt.name }}'
|
||||
// {% if dt.key.name %}
|
||||
virtual QMap<{{ dt.key.cppType }}, ::{{ namespace }}::{{ dt.type }}> {{ dt.nameFlc }}s() = 0;
|
||||
virtual bool {{ dt.nameFlc }}s(const std::function<void(const ::{{ namespace }}::{{ dt.type }} &data)> &cb) = 0;
|
||||
// when the cb returns true, the loop is stopped (useful when searching for a specific topic)
|
||||
virtual bool {{ dt.nameFlc }}s(const std::function<bool(const ::{{ namespace }}::{{ dt.type }} &data)> &cb) = 0;
|
||||
virtual ::{{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) = 0;
|
||||
{% else %}
|
||||
virtual ::{{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}() = 0;
|
||||
{% endif %}
|
||||
Q_SIGNALS:
|
||||
void {{ dt.nameFlc }}Changed(const ::{{ namespace }}::{{ dt.type }} &data, DataModification modType);
|
||||
|
||||
{% endfor %}
|
||||
|
||||
};
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* {{ namespace_abbr_upper }} Data Topic Client Interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-04-07
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dtc_impl.cpp
|
||||
|
||||
#include "{{ namespace_abbr }}_dtc_impl.h"
|
||||
{% if custom_dtc_procs %}
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_impl_custom.h>
|
||||
{% endif %}
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
#include <DDS/DTC/generic_dtc_impl.h>
|
||||
#include <DDS/Core/logger.h>
|
||||
#include <dds/core/cond/StatusCondition.hpp>
|
||||
#include <dds/core/cond/WaitSet.hpp>
|
||||
#include <unordered_map>
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
|
||||
using namespace NGL;
|
||||
using namespace Core;
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
// initializer function
|
||||
std::shared_ptr<Dtc> Dtc::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
{
|
||||
// share only for main thread
|
||||
if (qApp->thread() != QThread::currentThread())
|
||||
{% if custom_dtc_procs %}
|
||||
return std::make_shared<DtcImplCustom>(ddsConnector);
|
||||
{% else %}
|
||||
return std::make_shared<DtcImpl>(ddsConnector);
|
||||
{% endif %}
|
||||
|
||||
static std::unordered_map<DDSConnector::DdsConnector*, std::weak_ptr<Dtc>> s_availableConnectors;
|
||||
|
||||
auto it = s_availableConnectors.find(ddsConnector.get());
|
||||
if (it != s_availableConnectors.end())
|
||||
{
|
||||
auto ret = it->second.lock();
|
||||
if (ret)
|
||||
return ret;
|
||||
s_availableConnectors.erase(it);
|
||||
}
|
||||
{% if custom_dtc_procs %}
|
||||
auto ret = std::make_shared<DtcImplCustom>(ddsConnector);
|
||||
{% else %}
|
||||
auto ret = std::make_shared<DtcImpl>(ddsConnector);
|
||||
{% endif %}
|
||||
s_availableConnectors.insert(std::make_pair(ddsConnector.get(), ret));
|
||||
return ret; // moved implicitly (C++17; explicit std::move on a local triggers -Wredundant-move)
|
||||
}
|
||||
|
||||
// DtcImpl
|
||||
DtcImpl::DtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
: Dtc()
|
||||
, m_ddsConnector(ddsConnector)
|
||||
, m_logger(getLogger("Dtc"))
|
||||
{}
|
||||
|
||||
DtcImpl::~DtcImpl()
|
||||
{
|
||||
enableAll(false);
|
||||
}
|
||||
|
||||
bool DtcImpl::enableAll(bool enable)
|
||||
{ {% for dt in dataTopics.member %} {% if dt.dtcImpl %} {% if dt.autosubscribe %}
|
||||
if (!enable{{ dt.name }}(enable))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error initializing data topic {{ dt.name }}";
|
||||
return false;
|
||||
}
|
||||
{% endif %} {% endif %} {% endfor %}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% for dt in dataTopics.member %} {% if dt.dtcImpl %} {% if dt.key.name %}
|
||||
template <>
|
||||
struct TopicConstraints<{{ dt.type }}>
|
||||
{
|
||||
using KEY = {{ dt.key.cppType }};
|
||||
};
|
||||
{% endif %} {% endif %} {% endfor %}
|
||||
|
||||
|
||||
template <typename DTC>
|
||||
bool DtcImpl::checkEnabled(const DTC &dtc) const
|
||||
{
|
||||
if (!dtc)
|
||||
{
|
||||
using TOPIC = typename DtcConstraints<DTC>::TOPIC ;
|
||||
WARNING_LOG(m_logger) << "Error retrieving data topic "
|
||||
<< DDS::topic_type_name<TOPIC>::value() << " - not enabled";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename DTC>
|
||||
bool DtcImpl::waitForHistoricalData(DTC &dtc, int timeoutMSec)
|
||||
{
|
||||
if (!checkEnabled(dtc))
|
||||
return false;
|
||||
return dtc->waitForHistoricalData(timeoutMSec);
|
||||
}
|
||||
|
||||
template <typename T, typename K = typename TopicConstraints<T>::KEY>
|
||||
K getKey(const T &val);
|
||||
|
||||
template <typename DTC,
|
||||
typename T,
|
||||
typename K>
|
||||
QMap<K, T> DtcImpl::getAll(const DTC &dtc) const
|
||||
{
|
||||
if (!checkEnabled(dtc))
|
||||
return {};
|
||||
|
||||
QMap<K, T> retMap;
|
||||
for (const auto &sample : dtc->readAlive())
|
||||
retMap.insert(getKey(sample.data()), sample);
|
||||
|
||||
return retMap;
|
||||
}
|
||||
|
||||
template <typename DTC, typename C, typename T, typename K>
|
||||
T DtcImpl::getOne(const DTC &dtc, const C &cont, const K &key) const
|
||||
{
|
||||
if (!checkEnabled(dtc))
|
||||
return {};
|
||||
const auto it = cont.find(key);
|
||||
return it == cont.end() ? T() : it.value();
|
||||
}
|
||||
|
||||
template <typename DTC, typename C, typename T>
|
||||
bool DtcImpl::iterateAll(const DTC &dtc, const C &cont, const std::function<void(const T &data)> &cb) const
|
||||
{
|
||||
if (!checkEnabled(dtc))
|
||||
return false;
|
||||
|
||||
for (const auto &sample : cont)
|
||||
cb(sample);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename DTC, typename C, typename T>
|
||||
bool DtcImpl::iterateUntil(const DTC &dtc, const C &cont, const std::function<bool(const T &data)> &cb) const
|
||||
{
|
||||
if (!checkEnabled(dtc))
|
||||
return false;
|
||||
|
||||
for (const auto &sample : cont)
|
||||
{
|
||||
if (cb(sample))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
{% for dt in dataTopics.member %} {% if dt.dtcImpl %}
|
||||
//
|
||||
// Data Topic '{{ dt.name }}'
|
||||
//
|
||||
bool DtcImpl::enable{{ dt.name }}(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
if (m_dtc{{ dt.name }})
|
||||
{
|
||||
INFO_LOG(m_logger) << "{{ dt.name }} already enabled!";
|
||||
return true;
|
||||
}
|
||||
m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC<DTC::{{ dt.name }}Impl>());
|
||||
assert(m_dtc{{ dt.name }}.get());
|
||||
m_dtc{{ dt.name }}->setSubscriberCallback([this](const {{ namespace }}::{{ dt.name }} &data, DataModification modType)
|
||||
{
|
||||
{{ dt.nameFlc }}ChangedInternal(data, modType);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dtc{{ dt.name }} = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DtcImpl::waitForHistoricalData{{ dt.name }}(int timeoutMSec)
|
||||
{
|
||||
|
||||
if (!is{{ dt.name }}Enabled())
|
||||
{
|
||||
enable{{ dt.name }}();
|
||||
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
|
||||
}
|
||||
|
||||
if (!waitForHistoricalData(m_dtc{{ dt.name }}, timeoutMSec))
|
||||
return false;
|
||||
{% if dt.key.name %}
|
||||
m_{{ dt.nameFlc }}Cont = getAll(m_dtc{{ dt.name }});
|
||||
{% else %}
|
||||
auto samples = m_dtc{{ dt.name }}->readAlive();
|
||||
if (samples.length() == 0)
|
||||
return false;
|
||||
m_{{ dt.nameFlc }} = samples[samples.length() - 1].data();
|
||||
{% endif %}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% if dt.key.name %}
|
||||
template <>
|
||||
{{ dt.key.cppType }} getKey(const {{ dt.type }} &val)
|
||||
{
|
||||
return val.{{ dt.key.name }};
|
||||
}
|
||||
|
||||
QMap<{{ dt.key.cppType }},::{{ namespace }}::{{ dt.type }}> DtcImpl::{{ dt.nameFlc }}s()
|
||||
{
|
||||
if (!is{{ dt.name }}Enabled())
|
||||
{
|
||||
enable{{ dt.name }}();
|
||||
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
|
||||
waitForHistoricalData{{ dt.name }}(10);
|
||||
}
|
||||
return m_{{ dt.nameFlc }}Cont;
|
||||
}
|
||||
|
||||
bool DtcImpl::{{ dt.nameFlc }}s(const std::function<void(const ::{{ namespace }}::{{ dt.type }} &data)> &cb)
|
||||
{
|
||||
if (!is{{ dt.name }}Enabled())
|
||||
{
|
||||
enable{{ dt.name }}();
|
||||
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
|
||||
waitForHistoricalData{{ dt.name }}(10);
|
||||
}
|
||||
return iterateAll(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, cb);
|
||||
}
|
||||
|
||||
bool DtcImpl::{{ dt.nameFlc }}s(const std::function<bool(const ::{{ namespace }}::{{ dt.type }} &data)> &cb)
|
||||
{
|
||||
if (!is{{ dt.name }}Enabled())
|
||||
{
|
||||
enable{{ dt.name }}();
|
||||
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
|
||||
waitForHistoricalData{{ dt.name }}(10);
|
||||
}
|
||||
return iterateUntil(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, cb);
|
||||
}
|
||||
|
||||
::{{ namespace }}::{{ dt.type }} DtcImpl::{{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }})
|
||||
{
|
||||
if (!is{{ dt.name }}Enabled())
|
||||
{
|
||||
enable{{ dt.name }}();
|
||||
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
|
||||
waitForHistoricalData{{ dt.name }}(10);
|
||||
}
|
||||
return getOne(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, {{ dt.key.nameFlc }});
|
||||
}
|
||||
|
||||
void DtcImpl::{{ dt.nameFlc }}ChangedInternal(const ::{{ namespace }}::{{ dt.type }} &data, DataModification modType)
|
||||
{
|
||||
switch (modType)
|
||||
{
|
||||
case DataModification::eAdded:
|
||||
case DataModification::eModified:
|
||||
m_{{ dt.nameFlc }}Cont[getKey(data)] = data;
|
||||
break;
|
||||
case DataModification::eRemoved:
|
||||
m_{{ dt.nameFlc }}Cont.remove(getKey(data));
|
||||
break;
|
||||
}
|
||||
emit {{ dt.nameFlc }}Changed(data, modType);
|
||||
}
|
||||
|
||||
{% else %}
|
||||
::{{ namespace }}::{{ dt.type }} DtcImpl::{{ dt.nameFlc }}()
|
||||
{
|
||||
return m_{{ dt.nameFlc }};
|
||||
}
|
||||
void DtcImpl::{{ dt.nameFlc }}ChangedInternal(const ::{{ namespace }}::{{ dt.type }} &data, DataModification modType)
|
||||
{
|
||||
switch (modType)
|
||||
{
|
||||
case DataModification::eAdded:
|
||||
case DataModification::eModified:
|
||||
m_{{ dt.nameFlc }} = data;
|
||||
break;
|
||||
case DataModification::eRemoved:
|
||||
m_{{ dt.nameFlc }} = ::{{ namespace }}::{{ dt.type }}();
|
||||
break;
|
||||
}
|
||||
emit {{ dt.nameFlc }}Changed(data, modType);
|
||||
}
|
||||
|
||||
{% endif %} {% endif %} {% endfor %}
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* {{ namespace_abbr_upper }} Data Topic Client Interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-04-07
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dtc_impl.h
|
||||
#pragma once
|
||||
|
||||
#include <DDS/DTC/generic_dtc.h>
|
||||
#include <DDS/DTC/generic_dtc_impl.h>
|
||||
#include <DDS/Generic/generic_data_reader.h>
|
||||
#include <DDS/DDSConnector/dds_connector.h>
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_types.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
using namespace NGL;
|
||||
|
||||
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}CLIENT_LIB)
|
||||
#error "You are not allowed to include this file in your code!"
|
||||
#endif
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
template <typename UQ_DTC>
|
||||
struct DtcConstraints
|
||||
{
|
||||
using DTC = typename UQ_DTC::element_type;
|
||||
using TOPIC = typename DTC::TopicType;
|
||||
};
|
||||
|
||||
template <typename TOPIC>
|
||||
struct TopicConstraints
|
||||
{};
|
||||
|
||||
class DtcImpl {% if not custom_dtc_procs %}final {%endif %}: public Dtc
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
~DtcImpl();
|
||||
|
||||
bool enableAll(bool enable = true) override;
|
||||
{% for dt in dataTopics.member %} {% if dt.dtcImpl %}
|
||||
//
|
||||
// Data Topic '{{ dt.name }}'
|
||||
//
|
||||
public:
|
||||
bool enable{{ dt.name }}(bool enable = true) override final;
|
||||
bool is{{ dt.name }}Enabled() const override final { return m_dtc{{ dt.name }} != nullptr; }
|
||||
bool waitForHistoricalData{{ dt.name }}(int timeoutMSec = -1) override final;
|
||||
{% if dt.key.name %}
|
||||
QMap<{{ dt.key.cppType }}, ::{{ namespace }}::{{ dt.type }}> {{ dt.nameFlc }}s() override final;
|
||||
bool {{ dt.nameFlc }}s(const std::function<void(const ::{{ namespace }}::{{ dt.type }} &data)> &cb) override final;
|
||||
bool {{ dt.nameFlc }}s(const std::function<bool(const ::{{ namespace }}::{{ dt.type }} &data)> &cb) override final;
|
||||
::{{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) override final;
|
||||
{% else %}
|
||||
::{{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}() override final;
|
||||
{% endif %} {% endif %} {% endfor %}
|
||||
|
||||
private:
|
||||
template <typename DTC>
|
||||
bool checkEnabled(const DTC &dtc) const;
|
||||
template <typename DTC>
|
||||
bool waitForHistoricalData(DTC &dtc, int timeoutMSec);
|
||||
template <typename DTC,
|
||||
typename T = typename DtcConstraints<DTC>::TOPIC,
|
||||
typename K = typename TopicConstraints<T>::KEY>
|
||||
QMap<K, T> getAll(const DTC &dtc) const;
|
||||
template <typename DTC, typename C,
|
||||
typename T = typename DtcConstraints<DTC>::TOPIC,
|
||||
typename K = typename TopicConstraints<T>::KEY>
|
||||
T getOne(const DTC &dtc, const C &cont, const K &key) const;
|
||||
template <typename DTC, typename C,
|
||||
typename T = typename DtcConstraints<DTC>::TOPIC>
|
||||
bool iterateAll(const DTC &dtc, const C &cont, const std::function<void(const T &data)> &cb) const;
|
||||
template <typename DTC, typename C,
|
||||
typename T = typename DtcConstraints<DTC>::TOPIC>
|
||||
bool iterateUntil(const DTC &dtc, const C &cont, const std::function<bool(const T &data)> &cb) const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
|
||||
|
||||
{% for dt in dataTopics.member %}
|
||||
{% if dt.dtcImpl %}
|
||||
void {{ dt.nameFlc }}ChangedInternal(const ::{{ namespace }}::{{ dt.type }} &data, DataModification modType);
|
||||
std::unique_ptr<DTC::{{ dt.name }}> m_dtc{{ dt.name }}; {% if dt.key.name %}
|
||||
QMap<{{ dt.key.cppType }}, ::{{ namespace }}::{{ dt.type }}> m_{{ dt.nameFlc }}Cont; {% else %}
|
||||
::{{ namespace }}::{{ dt.type }} m_{{ dt.nameFlc }};
|
||||
{% endif %} {% endif %} {% endfor %}
|
||||
|
||||
LOGGER_T m_logger;
|
||||
|
||||
signals:
|
||||
{% for dt in dataTopics.member %}
|
||||
{% if dt.dtcImpl %}
|
||||
void {{ dt.nameFlc }}Changed(const ::{{ namespace }}::{{ dt.type }} &data, DataModification modType);
|
||||
{% endif %} {% endfor %}
|
||||
};
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* {{ namespace_abbr_upper }} Data Topic Server Interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Hanna E. Rathje, SLSE, 2016-01-05
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dts.h
|
||||
#pragma once
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
|
||||
|
||||
|
||||
namespace DDSConnector
|
||||
{
|
||||
class DdsConnector;
|
||||
}
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class Dts
|
||||
{
|
||||
protected:
|
||||
Dts() = default;
|
||||
public:
|
||||
virtual ~Dts() = default;
|
||||
|
||||
/**
|
||||
* Create a new instance for {{ namespace }}::Interface::Dts.
|
||||
* The caller takes ownership of the returned instance
|
||||
*/
|
||||
{{ namespace_abbr_upper }}_SERVER_EXPORT static std::shared_ptr<Dts> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
|
||||
/**
|
||||
* A unique identifier for this DTS
|
||||
*/
|
||||
{{ namespace_abbr_upper }}_SERVER_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Dts"; }
|
||||
|
||||
/**
|
||||
* initialize this Dts instance with all data topic writers
|
||||
*/
|
||||
virtual bool enableAll(bool enable = true) = 0;
|
||||
/**
|
||||
* only initialize the specific writer (can be called instead enableAll())
|
||||
*/
|
||||
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
|
||||
virtual bool enable{{ dt.name }}(bool enable = true) = 0;
|
||||
virtual bool is{{ dt.name }}Enabled() const = 0;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
{% for dt in dataTopics.member %}
|
||||
//
|
||||
// Data Topic '{{ dt.name }}'
|
||||
//
|
||||
virtual bool publish(const {{ namespace }}::{{ dt.type }} &data) = 0;
|
||||
{% if dt.key.name %}
|
||||
virtual bool remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists = true) = 0;
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* {{ namespace_abbr_upper }} Data Topic Server Interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-04-07
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dts_impl.cpp
|
||||
#include "{{ namespace_abbr }}_dts_impl.h"
|
||||
#include <DDS/DTS/generic_dts_impl.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
// initializer function
|
||||
std::shared_ptr<Dts> Dts::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
{
|
||||
return std::make_shared<DtsImpl>(ddsConnector);
|
||||
}
|
||||
|
||||
// DtsImpl
|
||||
DtsImpl::DtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
: Dts()
|
||||
, m_ddsConnector(ddsConnector)
|
||||
, m_logger(getLogger("Dts"))
|
||||
{}
|
||||
|
||||
DtsImpl::~DtsImpl()
|
||||
{
|
||||
enableAll(false);
|
||||
}
|
||||
|
||||
bool DtsImpl::enableAll(bool enable)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
|
||||
if (!enable{{ dt.name }}(enable))
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error initializing data topic {{ dt.name }}";
|
||||
return false;
|
||||
}
|
||||
{% endif %} {% endfor %}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
|
||||
//
|
||||
// Data Topic '{{ dt.name }}'
|
||||
//
|
||||
bool DtsImpl::enable{{ dt.name }}(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
if (m_dts{{ dt.name }})
|
||||
{
|
||||
INFO_LOG(m_logger) << "{{ ft.name }} already enabled!";
|
||||
return true;
|
||||
}
|
||||
m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS<DTS::{{ dt.name }}Impl>());
|
||||
assert(m_dts{{ dt.name }}.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dts{{ dt.name }} = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DtsImpl::publish(const {{ namespace }}::{{ dt.type }} &data)
|
||||
{
|
||||
if (!m_dts{{ dt.name }})
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error publishing data topic {{ dt.name }} - not enabled";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return m_dts{{ dt.name }}->write(data);
|
||||
}
|
||||
|
||||
{% if dt.key.name %}
|
||||
bool DtsImpl::remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists)
|
||||
{
|
||||
if (!m_dts{{ dt.name }})
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error removing data topic {{ dt.name }} - not enabled";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
{{ namespace }}::{{ dt.name }} data;
|
||||
// rtiddsgen -stl generates plain public data members, so the key is
|
||||
// assigned directly instead of the old setter call data.{{ dt.key.name }}(...).
|
||||
data.{{ dt.key.name }} = {{ dt.key.name }};
|
||||
return m_dts{{ dt.name }}->remove(data, errorMsgIfSampleDoesNotExists);
|
||||
}
|
||||
{% endif %}
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* {{ namespace_abbr_upper }} Data Topic Server Interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-04-07
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_dts_impl.h
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dts.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dts_types.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
|
||||
#include <DDS/DDSConnector/dds_connector.h>
|
||||
|
||||
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}SERVER_LIB)
|
||||
#error "You are not allowed to include this file in your code!"
|
||||
#endif
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class DtsImpl final : public Dts
|
||||
{
|
||||
public:
|
||||
explicit DtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
~DtsImpl();
|
||||
|
||||
bool enableAll(bool enable = true) override;
|
||||
|
||||
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
|
||||
//
|
||||
// Data Topic '{{ dt.name }}'
|
||||
//
|
||||
bool enable{{ dt.name }}(bool enable = true) override;
|
||||
bool is{{ dt.name }}Enabled() const override final { return m_dts{{ dt.name }} != nullptr; }
|
||||
bool publish(const {{ namespace }}::{{ dt.type }} &data) override;
|
||||
{% if dt.key.name %}
|
||||
bool remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists = true) override;
|
||||
{% endif %} {% endif %} {% endfor %}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
|
||||
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
|
||||
std::unique_ptr<DTS::{{ dt.name }}> m_dts{{ dt.name }};
|
||||
{% endif %} {% endfor %}
|
||||
LOGGER_T m_logger;
|
||||
};
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Template for idl struct definitions
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Hanna E. Rathje, SLSE, 2015-08-19
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_enums.idl
|
||||
#ifndef {{ namespace_abbr }}_enums_idl_included
|
||||
#define {{ namespace_abbr }}_enums_idl_included
|
||||
|
||||
//@copy #include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> // #define NDDSUSERDllExport for linux
|
||||
|
||||
|
||||
module {{ namespace }}
|
||||
{
|
||||
{% for idl_enum in idl_enums %}
|
||||
enum {{ idl_enum.name }}
|
||||
{
|
||||
{% for idl_member in idl_enum.member %}
|
||||
{{ idl_member.key }} = {{ idl_member.value }}{% if not forloop.last %},{% endif %} {% endfor %}
|
||||
};
|
||||
|
||||
{% endfor %}
|
||||
|
||||
}; // module {{ namespace }}
|
||||
|
||||
#endif
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* {{ namespace }} Function Topic Client interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_ftc.h
|
||||
#pragma once
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
|
||||
#include <DDS/Core/function_reply_object.h>
|
||||
|
||||
|
||||
namespace DDSConnector
|
||||
{
|
||||
class DdsConnector;
|
||||
}
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class Ftc
|
||||
{
|
||||
protected:
|
||||
Ftc() = default;
|
||||
public:
|
||||
virtual ~Ftc() = default;
|
||||
|
||||
/**
|
||||
* Create a new instance for {{ namespace }}::Interface::Ftc.
|
||||
* The caller takes ownership of the returned instance!
|
||||
*/
|
||||
{{ namespace_abbr_upper }}_CLIENT_EXPORT static std::shared_ptr<Ftc> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector, bool forceCreate = false);
|
||||
|
||||
/**
|
||||
* A unique identifier for this FTC
|
||||
*/
|
||||
{{ namespace_abbr_upper }}_CLIENT_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Ftc"; }
|
||||
|
||||
/**
|
||||
* initialize this Ftc instance with all function topic clients
|
||||
*/
|
||||
virtual bool enableAll(bool enable = true) = 0;
|
||||
/**
|
||||
* only initialize the specific function topic client (can be called instead enableAll())
|
||||
*/
|
||||
{% for ft in functionTopics.member %}
|
||||
virtual bool enable{{ ft.name }}(bool enable = true) = 0;
|
||||
virtual bool is{{ ft.name }}Enabled() const = 0;
|
||||
{% endfor %}
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
//
|
||||
// Function Topic '{{ ft.name }}'
|
||||
//
|
||||
class {{ ft.replyHandler }}
|
||||
{
|
||||
public:
|
||||
{{ ft.replyHandler }}() = default;
|
||||
virtual ~{{ ft.replyHandler }}() = default;
|
||||
virtual void {{ ft.onReply }}(const {{ ft.replyTypeFro }} &reply) = 0;
|
||||
};
|
||||
virtual int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) = 0;
|
||||
virtual int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function<void(const {{ ft.replyTypeFro }}&)> &cb) = 0;
|
||||
virtual {% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} {{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) = 0; // await
|
||||
virtual int64_t last{{ ft.request }}UniqueId() const = 0;
|
||||
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* {{ namespace }} Function Topic Client implementation
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_ftc_impl.cpp
|
||||
#include "{{ namespace_abbr }}_ftc_impl.h"
|
||||
|
||||
#include <DDS/FTC/generic_ftc_impl.h>
|
||||
#include <LtsUtilsCore/await.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
|
||||
|
||||
//using namespace std::literals::chrono_literals;
|
||||
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
// initializer function
|
||||
Q_DECL_EXPORT std::shared_ptr<Ftc> Ftc::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector, bool forceCreate)
|
||||
{
|
||||
// share only for main thread
|
||||
if (qApp->thread() != QThread::currentThread() || forceCreate)
|
||||
return std::make_shared<FtcImpl>(ddsConnector);
|
||||
|
||||
static std::unordered_map<DDSConnector::DdsConnector*, std::weak_ptr<Ftc>> s_availableConnectors;
|
||||
|
||||
auto it = s_availableConnectors.find(ddsConnector.get());
|
||||
if (it != s_availableConnectors.end())
|
||||
{
|
||||
auto ret = it->second.lock();
|
||||
if (ret)
|
||||
return ret;
|
||||
s_availableConnectors.erase(it);
|
||||
}
|
||||
|
||||
auto ret = std::make_shared<FtcImpl>(ddsConnector);
|
||||
s_availableConnectors.insert(std::make_pair(ddsConnector.get(), ret));
|
||||
return ret; // moved implicitly (C++17; explicit std::move on a local triggers -Wredundant-move)
|
||||
}
|
||||
|
||||
// FtcImpl
|
||||
FtcImpl::FtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
: Ftc()
|
||||
, m_ddsConnector(ddsConnector)
|
||||
, m_logger(getLogger("Ftc"))
|
||||
{}
|
||||
|
||||
bool FtcImpl::enableAll(bool enable)
|
||||
{ {% for ft in functionTopics.member %}
|
||||
if (!enable{{ ft.name }}(enable))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error initializing function topic {{ ft.name }}";
|
||||
return false;
|
||||
}
|
||||
{% endfor %}
|
||||
return true;
|
||||
}
|
||||
|
||||
FtcImpl::~FtcImpl()
|
||||
{
|
||||
enableAll(false);
|
||||
}
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
// Function Topic '{{ ft.name }}'
|
||||
bool FtcImpl::enable{{ ft.name }}(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
if (m_ftc{{ ft.name }})
|
||||
{
|
||||
INFO_LOG(m_logger) << "{{ ft.name }} already enabled!";
|
||||
return true;
|
||||
}
|
||||
m_ftc{{ ft.name }}.reset(
|
||||
m_ddsConnector->createFTC<FTC::{{ ft.name }}>(this));
|
||||
assert(m_ftc{{ ft.name }}.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ftc{{ ft.name }} = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
template<typename T>
|
||||
static T buildErrorReason(int reason, int requestId)
|
||||
{
|
||||
std::string errString;
|
||||
std::vector<std::string> errArgs;
|
||||
switch (reason)
|
||||
{
|
||||
case 1:
|
||||
errString = tr_lts("dds", "Service could not handle request");
|
||||
break;
|
||||
case 3:
|
||||
errString = tr_lts("dds", "Timeout while waiting for answer from server");
|
||||
break;
|
||||
default:
|
||||
errString = tr_lts("dds", "Unknown error reason: %1");
|
||||
errArgs.push_back(std::to_string(reason));
|
||||
break;
|
||||
}
|
||||
|
||||
T reply;
|
||||
reply.id = requestId;
|
||||
reply.errorString = errString;
|
||||
reply.errorParams = errArgs;
|
||||
return reply;
|
||||
}
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
//
|
||||
// {{ ft.name }}
|
||||
//
|
||||
int64_t FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler)
|
||||
{
|
||||
return {{ ft.requestFlc }}(request, [ handler ](const {{ ft.replyTypeFro }} &reply) { if (handler) handler->{{ ft.onReply }}(reply); });
|
||||
}
|
||||
|
||||
int64_t FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function<void(const {{ ft.replyTypeFro }}&)> &cb)
|
||||
{
|
||||
if (!m_ftc{{ ft.name }})
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error requesting function topic {{ ft.name }} - not enabled";
|
||||
Q_ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const slotReply = SLOT({{ ft.onReply }}({{ namespace }}::{{ ft.reply }},int,bool));
|
||||
static const char * const slotError = SLOT({{ ft.onError }}(int,int,int));
|
||||
int uniqueId = 0;
|
||||
if (!m_ftc{{ ft.name }}->requestDataClassic(request, slotReply, slotError, uniqueId))
|
||||
return 0;
|
||||
|
||||
m_{{ ft.replyHandlerFlc }}[uniqueId] = cb;
|
||||
// first 16 hex bytes from Sha3_512 hash for '{{ ft.name }}' converted to an int64
|
||||
m_last{{ ft.request }}UniqueId = uniqueId + {{ ft.replyHandlerHash }};
|
||||
return m_last{{ ft.request }}UniqueId;
|
||||
}
|
||||
|
||||
// NOTE: do not use if you call an FTC which returns an id while also containing an error as "error doesn't mean that the service might not reply with an id - so always take the id"
|
||||
{% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false)
|
||||
{
|
||||
if (!m_ftc{{ ft.name }})
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
throw std::runtime_error("Error requesting function topic {{ ft.name }} - not enabled");
|
||||
}
|
||||
|
||||
auto *requestWatcher = new Request2();
|
||||
auto future = m_ftc{{ ft.name }}->requestDataModern(request, requestWatcher);
|
||||
|
||||
m_currentlyOpen_{{ ft.requestFlc }}Requests++;
|
||||
//fprintf(stderr, "{{ ft.requestFlc }}: waiting for reply... (currently open requests: %d)\n", m_currentlyOpen_{{ ft.requestFlc }}Requests);
|
||||
LtsUtils::await(requestWatcher, &Request2::finished, true); // blocks until request has reply or failed
|
||||
m_currentlyOpen_{{ ft.requestFlc }}Requests--;
|
||||
//fprintf(stderr, "{{ ft.requestFlc }}: request done!\n");
|
||||
|
||||
// the generic ftc impl's 'requestDataModern' + 'exec()' guarantees that calling 'future.get()' will not result in a 'wait()' call by 'get()' - but we should still check!
|
||||
switch (future.wait_for(std::chrono::seconds(1)))
|
||||
{
|
||||
case std::future_status::deferred:
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
// fprintf(stderr, "{{ ft.requestFlc }}: deferred...\n");
|
||||
break;
|
||||
}
|
||||
case std::future_status::timeout:
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
// fprintf(stderr, "{{ ft.requestFlc }}: future timeout!\n");
|
||||
throw std::runtime_error("Future timeout!");
|
||||
}
|
||||
case std::future_status::ready:
|
||||
{
|
||||
// fprintf(stderr, "{{ ft.requestFlc }}: ready\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{% if ft.isCoreFt %}
|
||||
const auto _reply = future.get(); // throws if exception set
|
||||
|
||||
Core::FunctionReplyObject fro(
|
||||
{% for rp in ft.replyParams %} _reply.{{ rp.nameGetter }}{% if not forloop.last %},{% endif %} {% endfor %}
|
||||
);
|
||||
fro.setUniqueId(m_last{{ ft.request }}UniqueId);
|
||||
|
||||
if (fro.error())
|
||||
{
|
||||
ERROR_LOG(m_logger) << "FTC call failed: " << fro.translated();
|
||||
throw std::runtime_error(fro.translated().toStdString().c_str());
|
||||
}
|
||||
|
||||
const auto reply = fro.id();
|
||||
{% else %}
|
||||
const auto reply = future.get(); // throws if exception set
|
||||
{% endif %}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
void FtcImpl::{{ ft.onReply }}(const {{ namespace }}::{{ ft.reply }} &reply, int uniqueId, bool bRequestFinished)
|
||||
{
|
||||
auto it = m_{{ ft.replyHandlerFlc }}.find(uniqueId);
|
||||
if (it == m_{{ ft.replyHandlerFlc }}.end())
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error replying function topic {{ ft.name }} - no replyHandler";
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto cb = it.value();
|
||||
if (bRequestFinished)
|
||||
m_{{ ft.replyHandlerFlc }}.erase(it);
|
||||
if (!cb)
|
||||
return;
|
||||
|
||||
m_last{{ ft.request }}UniqueId = uniqueId + {{ ft.replyHandlerHash }};
|
||||
{% if ft.isCoreFt %}
|
||||
Core::FunctionReplyObject fro(
|
||||
{% for rp in ft.replyParams %}
|
||||
reply.{{ rp.nameGetter }}{% if not forloop.last %},{% endif %} {% endfor %}
|
||||
);
|
||||
fro.setUniqueId(m_last{{ ft.request }}UniqueId);
|
||||
cb(fro);
|
||||
{% else %}
|
||||
cb(reply);
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
void FtcImpl::{{ ft.onError }}(int reason, int uniqueId, int requestId)
|
||||
{
|
||||
{{ ft.onReply }}(buildErrorReason<{{ namespace }}::{{ ft.reply }}>(reason, requestId), uniqueId, true);
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* {{ namespace }} Function Topic Client implementation
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_ftc_impl.h
|
||||
#pragma once
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_ftc.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_ftc_types.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
|
||||
#include <DDS/FTC/abstract_requester.h>
|
||||
#include <DDS/DDSConnector/dds_connector.h>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}CLIENT_LIB)
|
||||
#error "You are not allowed to include this file in your code!"
|
||||
#endif
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class FtcImpl final : public QObject, public Ftc
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
~FtcImpl();
|
||||
|
||||
bool enableAll(bool enable = true) override;
|
||||
{% for ft in functionTopics.member %}
|
||||
bool enable{{ ft.name }}(bool enable = true) override;
|
||||
bool is{{ ft.name }}Enabled() const override { return m_ftc{{ ft.name }} != nullptr; }
|
||||
{% endfor %}
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
//
|
||||
// Function Topic '{{ ft.name }}'
|
||||
//
|
||||
|
||||
/// Classic request:
|
||||
public:
|
||||
int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) override; // returns the uniqueId used for this request
|
||||
int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function<void(const {{ ft.replyTypeFro }}&)> &cb) override; // returns the uniqueId used for this request
|
||||
int64_t last{{ ft.request }}UniqueId() const override { return m_last{{ ft.request }}UniqueId; }
|
||||
|
||||
private Q_SLOTS:
|
||||
void {{ ft.onReply }}(const {{ namespace }}::{{ ft.reply }} &reply, int uniqueId, bool bRequestFinished);
|
||||
void {{ ft.onError }}(int reason, int uniqueId, int requestId);
|
||||
|
||||
private:
|
||||
QHash<int, std::function<void(const {{ ft.replyTypeFro }}&)>> m_{{ ft.replyHandlerFlc }};
|
||||
int64_t m_last{{ ft.request }}UniqueId = -1;
|
||||
|
||||
/// Modern request:
|
||||
public:
|
||||
/* Executes the request and blocks (enters Qt's event loop) until the reply has arrived.
|
||||
* NOTE: If reply has a FunctionReplyWithState, this function only returns the first reply and returns.
|
||||
{% if ft.isCoreFt %} * Returns the id of the FunctionReply.{% endif %}*/
|
||||
{% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} {{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) override;
|
||||
|
||||
private:
|
||||
int m_currentlyOpen_{{ ft.requestFlc }}Requests { 0 };
|
||||
std::unique_ptr<FTC::{{ ft.name }}> m_ftc{{ ft.name }};
|
||||
{% endfor %}
|
||||
|
||||
private:
|
||||
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
|
||||
LOGGER_T m_logger;
|
||||
};
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* {{ namespace }} Function Topic Server interface
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_fts.h
|
||||
#pragma once
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
|
||||
#include <DDS/Core/function_reply_object.h>
|
||||
|
||||
namespace DDSConnector
|
||||
{
|
||||
class DdsConnector;
|
||||
}
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class Fts
|
||||
{
|
||||
protected:
|
||||
Fts() = default;
|
||||
public:
|
||||
virtual ~Fts() = default;
|
||||
|
||||
/**
|
||||
* Create a new instance for {{ namespace }}::Interface::Dts.
|
||||
* The caller takes ownership of the returned instance
|
||||
*/
|
||||
{{ namespace_abbr_upper }}_SERVER_EXPORT static std::shared_ptr<Fts> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
|
||||
/**
|
||||
* A unique identifier for this FTC
|
||||
*/
|
||||
{{ namespace_abbr_upper }}_SERVER_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Fts"; }
|
||||
|
||||
/**
|
||||
* initialize this Fts instance with all function topic servers
|
||||
*/
|
||||
virtual bool enableAll(bool enable = true) = 0;
|
||||
/**
|
||||
* only initialize the specific function topic writer (can be called instead enableAll())
|
||||
*/
|
||||
{% for ft in functionTopics.member %}
|
||||
virtual bool enable{{ ft.name }}(bool enable = true) = 0;
|
||||
virtual bool is{{ ft.name }}Enabled() const = 0;
|
||||
{% endfor %}
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
//
|
||||
// Function Topic '{{ ft.name }}'
|
||||
//
|
||||
class {{ ft.requestHandler }}
|
||||
{
|
||||
public:
|
||||
explicit {{ ft.requestHandler }}(const std::shared_ptr<Fts> &fts)
|
||||
: m_fts(fts)
|
||||
{
|
||||
if (fts)
|
||||
fts->set{{ ft.requestHandler }}(this);
|
||||
}
|
||||
virtual ~{{ ft.requestHandler }}()
|
||||
{
|
||||
auto sp = m_fts.lock();
|
||||
if (sp)
|
||||
sp->set{{ ft.requestHandler }}(nullptr);
|
||||
}
|
||||
virtual void {{ ft.onRequest }}(const rti::core::SampleIdentity &cookie,
|
||||
const {{ ft.requestType }} &request) = 0;
|
||||
private:
|
||||
std::weak_ptr<Fts> m_fts;
|
||||
};
|
||||
virtual void set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) = 0;
|
||||
virtual bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) = 0;
|
||||
{% if ft.isCoreFt %}
|
||||
virtual bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) = 0;
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* {{ namespace }} Function Topic Server implementation
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_fts_impl.cpp
|
||||
#include "{{ namespace_abbr }}_fts_impl.h"
|
||||
#include <DDS/FTS/generic_fts_impl.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
// initializer function
|
||||
std::shared_ptr<Fts> Fts::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
{
|
||||
return std::make_shared<FtsImpl>(ddsConnector);
|
||||
}
|
||||
|
||||
// FtsImpl
|
||||
FtsImpl::FtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
|
||||
: Fts()
|
||||
, m_ddsConnector(ddsConnector)
|
||||
, m_logger(getLogger("Fts"))
|
||||
{}
|
||||
|
||||
FtsImpl::~FtsImpl()
|
||||
{
|
||||
enableAll(false);
|
||||
}
|
||||
|
||||
bool FtsImpl::enableAll(bool enable)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "FtsImpl::enableAll(" << (enable ? "true" : "false") << ")";
|
||||
{% for ft in functionTopics.member %}
|
||||
if (!enable{{ ft.name }}(enable))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error initializing function topic {{ ft.name }}";
|
||||
return false;
|
||||
}
|
||||
{% endfor %}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
//
|
||||
// Function Topic '{{ ft.name }}'
|
||||
//
|
||||
bool FtsImpl::enable{{ ft.name }}(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
if (m_fts{{ ft.name }})
|
||||
{
|
||||
INFO_LOG(m_logger) << "{{ ft.name }} already enabled!";
|
||||
return true;
|
||||
}
|
||||
m_fts{{ ft.name }}.reset(m_ddsConnector->createFTS<FTS::{{ ft.name }}>());
|
||||
assert(m_fts{{ ft.name }}.get());
|
||||
m_fts{{ ft.name }}->setSubscriberCallback([this] (const rti::core::SampleIdentity &identity, const {{ namespace }}::{{ ft.request }} &request) { this->{{ ft.onRequest }}(identity, request); });
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fts{{ ft.name }}.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FtsImpl::{{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply)
|
||||
{
|
||||
if (!m_fts{{ ft.name }})
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Error publishing function topic {{ dt.name }} - not enabled";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_fts{{ ft.name }}->sendReply(cookie, reply);
|
||||
}
|
||||
{% if ft.isCoreFt %}
|
||||
bool FtsImpl::{{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject)
|
||||
{
|
||||
return {{ ft.replyFlc }}(cookie, {{ ft.replyType }}(replyObject.id(), replyObject.str(), replyObject.args(), replyObject.additionalInfo()));
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
|
||||
void FtsImpl::set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler)
|
||||
{
|
||||
m_{{ ft.requestHandlerFlc }} = handler;
|
||||
}
|
||||
|
||||
void FtsImpl::{{ ft.onRequest }}(const rti::core::SampleIdentity &cookie,
|
||||
const {{ namespace }}::{{ ft.request }} &request)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "{{ ft.onRequest }}";
|
||||
if (!m_{{ ft.requestHandlerFlc }})
|
||||
{
|
||||
WARNING_LOG(m_logger) << "Incoming {{ ft.request }} without registered handler!";
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_{{ ft.requestHandlerFlc }}->{{ ft.onRequest }}(cookie, request);
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* {{ namespace }} Function Topic Server implementation
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_fts_impl.h
|
||||
#pragma once
|
||||
#include <DDS/FTS/generic_fts_impl.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_fts.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_fts_types.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
|
||||
#include <DDS/DDSConnector/dds_connector.h>
|
||||
#include <QObject>
|
||||
|
||||
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}SERVER_LIB)
|
||||
#error "You are not allowed to include this file in your code!"
|
||||
#endif
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
namespace Interface
|
||||
{
|
||||
class FtsImpl final : public QObject, public Fts
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
|
||||
~FtsImpl();
|
||||
|
||||
bool enableAll(bool enable = true) override;
|
||||
|
||||
{% for ft in functionTopics.member %}
|
||||
//
|
||||
// {{ ft.name }}
|
||||
//
|
||||
public:
|
||||
bool enable{{ ft.name }}(bool enable = true) override;
|
||||
bool is{{ ft.name }}Enabled() const override { return m_fts{{ ft.name }} != nullptr && m_{{ ft.requestHandlerFlc }} != nullptr; }
|
||||
bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) override;
|
||||
{% if ft.isCoreFt %}
|
||||
bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) override;
|
||||
{% endif %}
|
||||
|
||||
void set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void {{ ft.onRequest }}(const rti::core::SampleIdentity &cookie,
|
||||
const {{ namespace }}::{{ ft.request }} &request);
|
||||
private:
|
||||
std::unique_ptr<FTS::{{ ft.name }}> m_fts{{ ft.name }};
|
||||
{{ ft.requestHandler }} *m_{{ ft.requestHandlerFlc }} = nullptr;
|
||||
|
||||
{% endfor %}
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
|
||||
LOGGER_T m_logger;
|
||||
};
|
||||
|
||||
} // namespace Interface
|
||||
} // namespace {{ namespace }}
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Json <-> DDS conversion routines
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2017-11-30
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_json.cpp
|
||||
|
||||
#include "{{ namespace_abbr }}_json.h"
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <LtsUtilsCore/dds_vector.h>
|
||||
#include <dds/core/String.hpp>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
template<>
|
||||
int fromJson(const QJsonValue &val, type<int>)
|
||||
{
|
||||
return val.toInt();
|
||||
}
|
||||
|
||||
template<>
|
||||
double fromJson(const QJsonValue &val, type<double>)
|
||||
{
|
||||
return val.toDouble();
|
||||
}
|
||||
|
||||
template<>
|
||||
qint64 fromJson(const QJsonValue &val, type<qint64>)
|
||||
{
|
||||
return val.toString().toLongLong();
|
||||
}
|
||||
|
||||
QJsonValue toJson(int vec)
|
||||
{
|
||||
return QJsonValue(vec);
|
||||
}
|
||||
QJsonValue toJson(double vec)
|
||||
{
|
||||
return QJsonValue(vec);
|
||||
}
|
||||
QJsonValue toJson(qint64 vec)
|
||||
{
|
||||
return QJsonValue(QString::number(vec));
|
||||
}
|
||||
// IDL 64-bit integers map to int64_t, which is 'long' on LP64 and therefore
|
||||
// distinct from qint64 ('long long'); without this overload the generic
|
||||
// toJson(std::vector<T>) is ambiguous for sequence<int64_t> members.
|
||||
QJsonValue toJson(long vec)
|
||||
{
|
||||
return QJsonValue(QString::number(vec));
|
||||
}
|
||||
template<>
|
||||
long fromJson(const QJsonValue &val, type<long>)
|
||||
{
|
||||
return val.toString().toLong();
|
||||
}
|
||||
|
||||
// std::string
|
||||
QJsonValue toJson(const std::string &element)
|
||||
{
|
||||
return QJsonValue(QString::fromStdString(element));
|
||||
}
|
||||
template<>
|
||||
std::string fromJson(const QJsonValue &val, type<std::string>)
|
||||
{
|
||||
return val.toString().toStdString();
|
||||
}
|
||||
|
||||
// std::vector<T>
|
||||
template<typename T>
|
||||
QJsonArray toJson(const std::vector<T> &vec)
|
||||
{
|
||||
QJsonArray arr;
|
||||
for (const auto &element : vec)
|
||||
arr.push_back(toJson(element));
|
||||
return arr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> fromJson(const QJsonValue &val, type<std::vector<T>>)
|
||||
{
|
||||
std::vector<T> ret;
|
||||
if (!val.isArray())
|
||||
return ret;
|
||||
const QJsonArray array(val.toArray());
|
||||
ret.reserve(array.size());
|
||||
for(int i = 0; i < array.size(); ++i)
|
||||
ret.push_back(fromJson(array.at(i), type<T>{}));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// dds::core:vector<uint8_t> (bytestream)
|
||||
inline QJsonValue toJson(const std::vector<uint8_t> &element)
|
||||
{
|
||||
return QJsonValue(QString::fromLatin1(QByteArray::fromRawData(reinterpret_cast<const char *>(&element.at(0)),
|
||||
static_cast<int>(element.size())).toBase64()));
|
||||
}
|
||||
template<>
|
||||
std::vector<uint8_t> fromJson(const QJsonValue &val, type<std::vector<uint8_t>>)
|
||||
{
|
||||
const QByteArray ba(QByteArray::fromBase64(val.toString().toLatin1()));
|
||||
return LtsUtils::convert<std::vector<uint8_t>>(ba);
|
||||
}
|
||||
|
||||
{% for dt in jsonTopics %}
|
||||
// {{ dt.name }}
|
||||
QJsonObject toJson(const {{ dt.name }} &element)
|
||||
{
|
||||
QJsonObject obj;
|
||||
{% for row in dt.cppMember %}
|
||||
{{ row.ddsToJsonElement }};
|
||||
{% endfor %}
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<>
|
||||
{{ dt.name }} fromJson(const QJsonValue &value, type<{{ dt.name }}>)
|
||||
{
|
||||
const QJsonObject objElement(value.toObject());
|
||||
{{ dt.name }} element;
|
||||
|
||||
{% for row in dt.cppMember %}
|
||||
{ {{ row.jsonElementToDds }}; } {% endfor %}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
//
|
||||
// extensions to support sequence of seqence of Non-POD :
|
||||
//
|
||||
|
||||
QJsonValue toJson(const std::vector <{{ dt.name }} > &element)
|
||||
{
|
||||
|
||||
if (element.size() == 0)
|
||||
return QJsonValue();
|
||||
QJsonArray array;
|
||||
for(size_t i = 0; i < element.size(); ++i)
|
||||
array.push_back(toJson(element.at(i)));
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector<{{ dt.name }}> fromJson(const QJsonValue &value, type< std::vector <{{ dt.name }} > >)
|
||||
{
|
||||
std::vector < {{ dt.name }} > ret;
|
||||
if (value.isNull())
|
||||
return ret;
|
||||
if (!value.isArray())
|
||||
return ret;
|
||||
|
||||
const QJsonArray array = value.toArray();
|
||||
ret.reserve(array.size());
|
||||
for(const auto &ele : array)
|
||||
ret.push_back(fromJson(ele, type<{{ dt.name }}>{}));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Json <-> DDS conversion routines
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2017-11-30
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_json.h
|
||||
#pragma once
|
||||
|
||||
#include "{{ namespace_abbr }}.hpp"
|
||||
#include "{{ namespace_abbr }}_export.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
template<typename T>
|
||||
struct type{};
|
||||
|
||||
template<typename T>
|
||||
T fromJson(const QJsonValue &val, type<T>);
|
||||
|
||||
template<typename T>
|
||||
T fromJson(const QJsonValue &val)
|
||||
{
|
||||
return fromJson(val, type<T>{});
|
||||
}
|
||||
|
||||
{% for dt in jsonTopics %}
|
||||
// {{ dt.name }}
|
||||
{{ namespace_abbr_upper }}_COMMON_EXPORT QJsonObject toJson(const {{ dt.name }} &element);
|
||||
template<> {{ namespace_abbr_upper }}_COMMON_EXPORT {{ dt.name }} fromJson(const QJsonValue &val, type<{{ dt.name }}>);
|
||||
{{ namespace_abbr_upper }}_COMMON_EXPORT QJsonValue toJson(const std::vector< {{ dt.name }} > &element);
|
||||
template<> {{ namespace_abbr_upper }}_COMMON_EXPORT std::vector<{{ dt.name }}> fromJson(const QJsonValue &value, type< std::vector< {{ dt.name }} > >);
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
//
|
||||
// extensions to support sequence of seqence of Non-POD :
|
||||
//
|
||||
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
+919
@@ -0,0 +1,919 @@
|
||||
/**
|
||||
* RecordReplay Plugin
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-07-21
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_record_replay_plugin.cpp
|
||||
|
||||
#include "{{ namespace_abbr }}_record_replay_plugin.h"
|
||||
#include <ngldb/nsqlquery.h>
|
||||
#include <DDS/DTS/generic_dts_impl.h>
|
||||
#include <DDS/DTC/generic_dtc_impl.h>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QtSql/QSqlError>
|
||||
#include <LtsUtilsCore/dds_vector.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct type{};
|
||||
|
||||
// conversion DDS -> QVariant, encapsulate complex members into json
|
||||
template<typename T>
|
||||
QJsonValue toJson(const T &val);
|
||||
|
||||
template<typename T>
|
||||
QVariant toQVariant(const T &val)
|
||||
{
|
||||
return QVariant::fromValue(val);
|
||||
}
|
||||
|
||||
template<>
|
||||
QJsonValue toJson(const int &vec)
|
||||
{
|
||||
return QJsonValue(vec);
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson(const double &vec)
|
||||
{
|
||||
return QJsonValue(vec);
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson(const qint64 &val)
|
||||
{
|
||||
return QJsonValue(QString::number(val));
|
||||
}
|
||||
|
||||
template<>
|
||||
QJsonValue toJson(const std::vector<int> &val)
|
||||
{
|
||||
if (val.size() == 0)
|
||||
return QJsonValue();
|
||||
QJsonArray array;
|
||||
for(size_t i = 0; i < val.size(); ++i)
|
||||
array.push_back(toJson(val.at(i)));
|
||||
|
||||
return QJsonValue(array);
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
QVariant toQVariant(const dds::core::safe_enum<T> &val)
|
||||
{
|
||||
// do not use T here - otherwise the implicit ctor dds::core::safe_enum<T>() is used which leads to infinite recursion
|
||||
return toQVariant<int>(val.underlying());
|
||||
}
|
||||
|
||||
template<>
|
||||
QJsonValue toJson(const std::string &element)
|
||||
{
|
||||
return QJsonValue(QString::fromStdString(element));
|
||||
}
|
||||
|
||||
template<>
|
||||
QVariant toQVariant(const std::string &val)
|
||||
{
|
||||
return QString::fromStdString(val);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
QVariant toQVariant(const std::vector<T> &data)
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return QString();
|
||||
QJsonArray array;
|
||||
for(size_t i = 0; i < data.size(); ++i)
|
||||
array.push_back(toJson(data.at(i)));
|
||||
|
||||
return QString::fromUtf8(QJsonDocument(array).toJson());
|
||||
}
|
||||
|
||||
template<>
|
||||
QVariant toQVariant(const std::vector<uint8_t> &data)
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return QVariant();
|
||||
return QVariant(LtsUtils::convert<QByteArray>(data));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static QVariant toQVariant(const dds::core::optional<T> &val)
|
||||
{
|
||||
if (val.is_set())
|
||||
return toQVariant(val.get());
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
// conversion QVariant -> DDS, decode complex members from json
|
||||
template<typename T>
|
||||
T fromJson(const QJsonValue &val, type<T>);
|
||||
|
||||
template<>
|
||||
int fromJson(const QJsonValue &val, type<int>)
|
||||
{
|
||||
return val.toInt();
|
||||
}
|
||||
template<>
|
||||
double fromJson(const QJsonValue &val, type<double>)
|
||||
{
|
||||
return val.toDouble();
|
||||
}
|
||||
template<>
|
||||
qint64 fromJson(const QJsonValue &val, type<qint64>)
|
||||
{
|
||||
return val.toString().toLongLong();
|
||||
}
|
||||
/*
|
||||
template<>
|
||||
std::vector<int> fromJson(const QJsonValue &val, type<std::vector<int>>)
|
||||
{
|
||||
std::vector<int> ret;
|
||||
if (val.isNull())
|
||||
return ret;
|
||||
const QJsonArray array(QJsonDocument::fromJson(val.toString().toUtf8()).array());
|
||||
ret.reserve(array.size());
|
||||
for(const auto &ele : array)
|
||||
ret.push_back(fromJson(ele, type<int>{} ));
|
||||
return ret;
|
||||
}
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> fromJson(const QJsonValue &jsonData, type<std::vector<T>>)
|
||||
{
|
||||
std::vector<T> ret;
|
||||
if (jsonData.isNull())
|
||||
return ret;
|
||||
const QJsonArray array(jsonData.toArray());
|
||||
ret.reserve(array.size());
|
||||
for(const auto &ele : array)
|
||||
ret.push_back(fromJson(ele, type<T>{}));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> fromJson(const QVariant &jsonData, type<std::vector<T>>)
|
||||
{
|
||||
std::vector<T> ret;
|
||||
if (jsonData.isNull())
|
||||
return ret;
|
||||
const QJsonArray array(QJsonDocument::fromJson(jsonData.toString().toUtf8()).array()); // warning: ugly and didn't work in the fromJson version above
|
||||
ret.reserve(array.size());
|
||||
for(const auto &ele : array)
|
||||
ret.push_back(fromJson(ele, type<T>{}));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Primary fallback for any migrated plain-`enum class` enumerators that have no
|
||||
// explicit specialization below - most importantly the nested foreign
|
||||
// ProjectDataManagement / Core enums a plugin reads back from the database (they are
|
||||
// not in this service's idl_enums, so the per-own-enum specializations do not cover
|
||||
// them). Giving the primary template a body (rather than leaving it declaration-only)
|
||||
// is what lets those foreign-enum instantiations resolve, so the plugin .so links. The
|
||||
// explicit specializations for primitives / own enums / structs / vectors / optionals
|
||||
// all take precedence over this fallback, so it is only ever instantiated for an enum
|
||||
// type, where `static_cast<T>(val.toInt())` is the correct conversion.
|
||||
template<typename T>
|
||||
T fromQVariant(const QVariant &val, type<T>)
|
||||
{
|
||||
return static_cast<T>(val.toInt());
|
||||
}
|
||||
|
||||
template<>
|
||||
bool fromQVariant(const QVariant &val, type<bool>)
|
||||
{
|
||||
return val.toBool();
|
||||
}
|
||||
|
||||
template<>
|
||||
short fromQVariant(const QVariant &val, type<short>)
|
||||
{
|
||||
return static_cast<short>(val.toInt());
|
||||
}
|
||||
|
||||
template<>
|
||||
int fromQVariant(const QVariant &val, type<int>)
|
||||
{
|
||||
return val.toInt();
|
||||
}
|
||||
|
||||
template<>
|
||||
long long fromQVariant(const QVariant &val, type<long long>)
|
||||
{
|
||||
return val.toLongLong();
|
||||
}
|
||||
|
||||
// rtiddsgen 4.7 spells the 64 bit integer members int64_t, which is `long` on LP64 and thus
|
||||
// a different type from the Qt side's qint64 (`long long`). The tag dispatch above is keyed on
|
||||
// the exact type, so the generated type needs its own conversion even though the two are the
|
||||
// same width. GrantleeWrappers::cppDdsType() names the generated member type, so this is the
|
||||
// overload the record replay code instantiates for those members.
|
||||
template<>
|
||||
int64_t fromQVariant(const QVariant &val, type<int64_t>)
|
||||
{
|
||||
return static_cast<int64_t>(val.toLongLong());
|
||||
}
|
||||
|
||||
template<>
|
||||
double fromQVariant(const QVariant &val, type<double>)
|
||||
{
|
||||
return val.toDouble();
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string fromJson(const QJsonValue &val, type<std::string>)
|
||||
{
|
||||
return val.toString().toStdString();
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string fromQVariant(const QVariant &val, type<std::string>)
|
||||
{
|
||||
return val.toString().toStdString();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
dds::core::safe_enum<T> fromQVariant(const QVariant &val, type<dds::core::safe_enum<T>>)
|
||||
{
|
||||
using inner = typename dds::core::safe_enum<T>::inner_enum;
|
||||
return static_cast<inner>(val.toInt());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> fromQVariant(const QVariant &val, type<std::vector<T>> t)
|
||||
{
|
||||
return fromJson(val, t);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector<uint8_t> fromQVariant(const QVariant &in, type<std::vector<uint8_t>>)
|
||||
{
|
||||
return LtsUtils::convert<std::vector<uint8_t>>(in.toByteArray());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static dds::core::optional<T> fromQVariant(const QVariant &val, type<dds::core::optional<T>>)
|
||||
{
|
||||
if (val.isNull())
|
||||
return dds::core::optional<T>();
|
||||
return fromQVariant(val, type<T>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T fromJson(const QJsonValue &val)
|
||||
{
|
||||
return fromJson(val, type<T>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T fromQVariant(const QVariant &val)
|
||||
{
|
||||
return fromQVariant(val, type<T>{});
|
||||
}
|
||||
|
||||
// rtiddsgen 7.7 spells the migrated enumerators as plain `enum class` in the service
|
||||
// namespace (the legacy dds::core::safe_enum<T> wrapper is gone). The record_replay
|
||||
// plugin still instantiates fromQVariant<Enum>() for each enum member it reads back,
|
||||
// and none of the generic overloads above are ever deduced for those types, so each of
|
||||
// this service's own enumerations needs its own explicit specialization. `idl_enums`
|
||||
// carries the service's own enumerations by local name and this block runs inside
|
||||
// `namespace {{ namespace }}`, so the local name resolves to the fully-qualified enum
|
||||
// type. (Nested foreign PDM enumerations are still not covered - same limitation as the
|
||||
// documented svcBuildings exclusion.)
|
||||
{% for enumeration in idl_enums %}
|
||||
template<>
|
||||
{{ enumeration.name }} fromQVariant(const QVariant &val, type<{{ enumeration.name }}>)
|
||||
{
|
||||
return static_cast<{{ enumeration.name }}>(val.toInt());
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
{% for dt in serializeTopics.member %}
|
||||
//
|
||||
// Begin {{ dt.typeFq }}
|
||||
//
|
||||
template<>
|
||||
{{ dt.typeFq }} fromJson(const QJsonValue &val, type<{{ dt.typeFq }}>)
|
||||
{
|
||||
const QJsonObject objElement(val.toObject());
|
||||
{{ dt.typeFq }} element;
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
{ {{ row.jsonElementToDds }}; } {% endfor %}
|
||||
|
||||
return element;
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson(const {{ dt.typeFq }} &element)
|
||||
{
|
||||
QJsonObject obj;
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
{{ row.ddsToJsonElement }}; {% endfor %}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <>
|
||||
QVariant toQVariant(const {{ dt.typeFq }} &val)
|
||||
{
|
||||
return QString::fromUtf8(QJsonDocument(toJson(val).toObject()).toJson());
|
||||
}
|
||||
|
||||
template<>
|
||||
{{ dt.typeFq }} fromQVariant(const QVariant &val, type<{{ dt.typeFq }}>)
|
||||
{
|
||||
return fromJson(QJsonDocument::fromJson(val.toString().toUtf8()).object(), type<{{ dt.typeFq }}>{});
|
||||
}
|
||||
|
||||
//
|
||||
// extensions to support sequence of seqence of Non-POD :
|
||||
//
|
||||
|
||||
template<>
|
||||
QJsonValue toJson(const std::vector < {{ dt.typeFq }} > &data)
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return QJsonValue();
|
||||
QJsonArray array;
|
||||
for(size_t i = 0; i < data.size(); ++i)
|
||||
array.push_back(toJson(data.at(i)));
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector < {{ dt.typeFq }} > fromJson(const QJsonValue &val)
|
||||
{
|
||||
std::vector < {{ dt.typeFq }} > ret;
|
||||
if (val.isNull())
|
||||
return ret;
|
||||
if (!val.isArray())
|
||||
return ret;
|
||||
const QJsonArray array = val.toArray();
|
||||
ret.reserve(array.size());
|
||||
for(const auto &ele : array)
|
||||
ret.push_back(fromJson(ele, type<{{ dt.typeFq }}>{}));
|
||||
return ret;
|
||||
}
|
||||
//
|
||||
// : extensions to support sequence of seqence of Non-POD
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// End {{ dt.typeFq }}
|
||||
//
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
|
||||
RecordReplayPlugin::RecordReplayPlugin()
|
||||
: m_exerciseId(0)
|
||||
, m_bCanHandleSchemasRead(true)
|
||||
, m_bCanHandleSchemasWrite(true)
|
||||
, m_logger(getLogger("RecordReplay"))
|
||||
{}
|
||||
|
||||
RecordReplayPlugin::~RecordReplayPlugin() = default;
|
||||
|
||||
QString RecordReplayPlugin::getTableName(const QString &tableName, bool bCanHandleSchemas) const
|
||||
{
|
||||
if (!bCanHandleSchemas)
|
||||
return tableName;
|
||||
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::initDtc(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
|
||||
int exerciseId)
|
||||
{
|
||||
m_ddsConnector = ddsConnector;
|
||||
m_exerciseId = exerciseId;
|
||||
if (!m_ddsConnector)
|
||||
return false;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC<DTC::{{ dt.name }}Impl>());
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::initDts(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
|
||||
int exerciseId,
|
||||
bool bCanHandleSchemas,
|
||||
NSqlQuery &query)
|
||||
{
|
||||
Q_UNUSED(query);
|
||||
m_ddsConnector = ddsConnector;
|
||||
m_exerciseId = exerciseId;
|
||||
m_bCanHandleSchemasRead = bCanHandleSchemas;
|
||||
m_bCanHandleSchemasWrite = bCanHandleSchemas;
|
||||
if (!m_ddsConnector)
|
||||
return false;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS<DTS::{{ dt.name }}Impl>());
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite)
|
||||
{
|
||||
m_exerciseId = exerciseId;
|
||||
m_bCanHandleSchemasRead = bCanHandleSchemasRead;
|
||||
m_bCanHandleSchemasWrite = bCanHandleSchemasWrite;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
// {{ dt.name }}
|
||||
if (!copyInitialData{{ dt.name }}(queryFrom, queryTo, fromTime)) return false;
|
||||
if (!copyData{{ dt.name }}(queryFrom, queryTo, fromTime, toTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::ddsTopicsToDatabase(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!write{{ dt.name }}(q, minRecordSampleTimes)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::setReplayStartTime(NSqlQuery &q, qint64 startTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!read{{ dt.name }}At(q, startTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!read{{ dt.name }}(q, fromTime, toTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::publishData(qint64 desiredTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!publish{{ dt.name }}(desiredTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecordReplayPlugin::clearData()
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
clearData{{ dt.name }}();
|
||||
{% endif %} {% endfor %}
|
||||
}
|
||||
|
||||
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> RecordReplayPlugin::getSnapshotTimes(NSqlQuery &q,
|
||||
qint64 toTime,
|
||||
qint64 interval,
|
||||
const QHash<QString, qint64> &minRecordedSampleTimes)
|
||||
{
|
||||
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> ret;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
{
|
||||
const QString &topicName(QStringLiteral("{{ dt.name }}"));
|
||||
std::vector<RecordReplayPlugin::RecSnapshot> vec;
|
||||
auto it = minRecordedSampleTimes.find(topicName);
|
||||
if (it != minRecordedSampleTimes.end())
|
||||
{
|
||||
qint64 nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, it.value()); // is 0 or -1 if no samples are available
|
||||
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
|
||||
|
||||
if (nextSampleTime != m_snapshot{{ dt.name }}MapTime)
|
||||
{
|
||||
// the current snapshot data does not match the starting time -> reread m_{{ dt.nameFlc }}Keys
|
||||
readLastSnapshotKeysFor{{ dt.name }}(q, nextSampleTime);
|
||||
//DEBUG_LOG(m_logger) << "fromTime: " << fromTime << ", m_snapshotParticipantMapTime: " << m_snapshotParticipantMapTime
|
||||
// << " - rereading snapshot times! Map now contains " << m_snapshot{{ dt.name }}Map.size() << " entries";
|
||||
}
|
||||
|
||||
while (nextSampleTime > 0 && nextSampleTime <= toTime)
|
||||
{
|
||||
const qint64 intervalStart = nextSampleTime;
|
||||
const qint64 intervalEnd = intervalStart + interval;
|
||||
updateSnapshotKeys{{ dt.name }}(q, intervalStart, intervalEnd);
|
||||
|
||||
vec.emplace_back(RecSnapshot(intervalEnd, m_snapshot{{ dt.name }}Map));
|
||||
|
||||
nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, intervalEnd);
|
||||
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
|
||||
}
|
||||
}
|
||||
if (!vec.empty())
|
||||
ret.insert({topicName, std::move(vec)});
|
||||
}
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVector<QString> RecordReplayPlugin::getRecordTopicNames() const
|
||||
{
|
||||
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
QStringLiteral("{{ dt.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
QVector<QString> RecordReplayPlugin::getRecordTableNames() const
|
||||
{
|
||||
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
QStringLiteral("{{ dt.recTable.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
qint64 RecordReplayPlugin::getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const
|
||||
{
|
||||
QString tableName;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (topicName == QLatin1String("{{ dt.name }}"))
|
||||
tableName = QStringLiteral("{{ dt.recTable.name }}");
|
||||
{% endif %} {% endfor %}
|
||||
if (tableName.isEmpty())
|
||||
return defaultValue;
|
||||
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + tableName);
|
||||
if (!q.execAndFetch(stmt))
|
||||
return defaultValue;
|
||||
const QVariant val = q.value(0);
|
||||
return val.isNull() ? defaultValue : val.toLongLong();
|
||||
}
|
||||
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
|
||||
bool RecordReplayPlugin::read{{ dt.name }}At(NSqlQuery &q, qint64 startTime)
|
||||
{
|
||||
// read snapshot data for {{ dt.name }}
|
||||
if (!readLastSnapshotFor{{ dt.name }}(q, startTime)) return false;
|
||||
// read other samples between snapshot time and startTime
|
||||
if (!read{{ dt.name }}(q, m_snapshot{{ dt.name }}MapTime, startTime)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::write{{ dt.name }}(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
|
||||
{
|
||||
using DATA = {{ namespace }}::{{ dt.name }};
|
||||
|
||||
if (!m_dtc{{ dt.name }})
|
||||
return false;
|
||||
|
||||
qint64 minRecordSampleTime = std::numeric_limits<qint64>::max();
|
||||
|
||||
if (m_dtc{{ dt.name }}->hasSamples())
|
||||
{
|
||||
TRACE_LOG(m_logger) << "m_dtc{{ dt.name }}->hasSamples() == true ";
|
||||
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
|
||||
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
|
||||
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
|
||||
|
||||
if (!q.prepare(stmt))
|
||||
return false;
|
||||
|
||||
dds::sub::LoanedSamples<DATA> samples = m_dtc{{ dt.name }}->takeAlive();
|
||||
TRACE_LOG(m_logger) << "samples count: " << samples.length();
|
||||
for (const auto &sample : samples)
|
||||
{
|
||||
const DATA &data = sample.data();
|
||||
INFO_LOG(m_logger) << "m_dtc{{ dt.name }} {{ dt.recTable.ddsKey.cppMemberName }}: " << data.{{ dt.recTable.ddsKey.cppMemberName }};
|
||||
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
q.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.ddsToQVariant }});
|
||||
{% endfor %}
|
||||
q.bindValue(QStringLiteral(":dds_is_alive"), true);
|
||||
|
||||
if (!q.exec())
|
||||
return false;
|
||||
|
||||
// rtiddsgen 4.7 declares sample_time as int64_t (long), while the record
|
||||
// boundary is qint64 (long long); std::min requires both arguments to be
|
||||
// the same type, so narrow the generated member explicitly.
|
||||
minRecordSampleTime = std::min(minRecordSampleTime, static_cast<qint64>(data.sampleTime));
|
||||
}
|
||||
}
|
||||
|
||||
dds::sub::LoanedSamples<DATA> disposedSamples = m_dtc{{ dt.name }}->takeDisposed();
|
||||
if (disposedSamples.length() != 0)
|
||||
{
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
|
||||
QStringLiteral(" ( dds_is_alive, sample_time, {{ dt.recTable.ddsKey.dbColumnName }}) VALUES "
|
||||
" (:dds_is_alive,:sample_time,:{{ dt.recTable.ddsKey.dbColumnName }});"));
|
||||
|
||||
if (!q.prepare(stmt))
|
||||
return false;
|
||||
|
||||
DEBUG_LOG(m_logger) << "disposedSamples count: " << disposedSamples.length();
|
||||
auto dr = m_dtc{{ dt.name }}->dataReader()->dataReader();
|
||||
{{ dt.name }} data;
|
||||
for (const auto &sample : disposedSamples)
|
||||
{
|
||||
const qint64 sampleTime = sample.info().source_timestamp().to_millisecs();
|
||||
dr.key_value(data, sample.info().instance_handle());
|
||||
q.bindValue(QStringLiteral(":dds_is_alive"), false);
|
||||
q.bindValue(QStringLiteral(":sample_time"), sampleTime);
|
||||
q.bindValue(QStringLiteral(":{{ dt.recTable.ddsKey.cppMemberName }}"), {{ dt.recTable.ddsKey.ddsToQVariant }});
|
||||
|
||||
if (!q.exec())
|
||||
return false;
|
||||
|
||||
minRecordSampleTime = std::min(minRecordSampleTime, sampleTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (minRecordSampleTime != std::numeric_limits<qint64>::max())
|
||||
minRecordSampleTimes[QStringLiteral("{{ dt.name }}")] = minRecordSampleTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// read the last snapshot which is <= startTime, set m_snapshot{{ dt.name }}MapTime to the snapshot time
|
||||
bool RecordReplayPlugin::readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime)
|
||||
{
|
||||
// rebuild m_{{ dt.nameFlc }}Map content for given startTime
|
||||
clearData{{ dt.name }}();
|
||||
|
||||
const QString stmt(QStringLiteral("SELECT id, time_stamp FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
|
||||
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
|
||||
|
||||
if (!q.exec(stmt))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
|
||||
return false;
|
||||
}
|
||||
if (!q.next() || q.value(0).isNull())
|
||||
{
|
||||
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
|
||||
return true;
|
||||
}
|
||||
const qint64 idSnapshot = q.value(0).toLongLong();
|
||||
const qint64 timeSnapshot = q.value(1).toLongLong();
|
||||
|
||||
const QString stmt2(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
|
||||
|
||||
if (!q.exec(stmt2))
|
||||
return false;
|
||||
|
||||
{{ namespace }}::{{ dt.name }} data;
|
||||
while (q.next())
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
const bool bIsAlive = q.value(i++).toBool();
|
||||
{% for row in dt.recTable.viewMember %}
|
||||
{{ row.qVariantToDds }};
|
||||
{% endfor %}
|
||||
|
||||
if (bIsAlive)
|
||||
m_{{ dt.nameFlc }}Map.insert(data.sampleTime, data);
|
||||
else
|
||||
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime, data);
|
||||
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }});
|
||||
}
|
||||
m_snapshot{{ dt.name }}MapTime = timeSnapshot;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RecordReplayPlugin::read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
|
||||
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
|
||||
|
||||
if (!q.exec(stmt))
|
||||
return false;
|
||||
|
||||
{{ namespace }}::{{ dt.name }} data;
|
||||
while (q.next())
|
||||
{
|
||||
int i = 0;
|
||||
const bool bIsAlive = q.value(i++).toBool();
|
||||
|
||||
{% for row in dt.recTable.viewMember %}
|
||||
{{ row.qVariantToDds }};
|
||||
{% endfor %}
|
||||
|
||||
if (bIsAlive)
|
||||
m_{{ dt.nameFlc }}Map.insert(data.sampleTime, data);
|
||||
else
|
||||
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime, data);
|
||||
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT {{ dt.recTable.ddsKey.dbColumnName }}, {{ dt.recTable.primaryKey.dbColumnName }}, dds_is_alive FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime) +
|
||||
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) +
|
||||
QStringLiteral(" ORDER BY sample_time "));
|
||||
|
||||
DEBUG_LOG(m_logger) << "stmt: " << stmt;
|
||||
|
||||
if (!q.exec(stmt))
|
||||
return false;
|
||||
|
||||
while (q.next())
|
||||
{
|
||||
const qint64 key = q.value(0){{ dt.recTable.ddsKey.cppFromQvPost }};
|
||||
const qint64 pk = q.value(1){{ dt.recTable.primaryKey.cppFromQvPost }};
|
||||
const bool isAlive = q.value(2).toBool();
|
||||
if (isAlive)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "INSERT {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
|
||||
m_snapshot{{ dt.name }}Map[key] = pk;
|
||||
m_{{ dt.nameFlc }}Keys.insert(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "REMOVE {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
|
||||
m_snapshot{{ dt.name }}Map.erase(key);
|
||||
m_{{ dt.nameFlc }}Keys.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
// new validity time for snapshot data
|
||||
m_snapshot{{ dt.name }}MapTime = toTime;
|
||||
|
||||
DEBUG_LOG(m_logger) << "updateSnapshotKeys{{ dt.name }}(" << fromTime << ", " << toTime << ") = "
|
||||
<< m_snapshot{{ dt.name }}Map.size();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT rec_id_array FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
|
||||
" ORDER BY time_stamp DESC LIMIT 1").arg(fromTime));
|
||||
|
||||
// no previous snapshot found?
|
||||
if (!q.exec(stmt))
|
||||
return false;
|
||||
if (!q.next() || q.value(0).isNull())
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "no snapshot entry for {{ dt.name }} found";
|
||||
return true;
|
||||
}
|
||||
|
||||
m_snapshot{{ dt.name }}Map = LtsUtils::BigintArray2D::fromPgString(q.value(0).toString())();
|
||||
|
||||
DEBUG_LOG(m_logger) << "Map size {{ dt.name }}: " << m_snapshot{{ dt.name }}Map.size();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
qint64 RecordReplayPlugin::getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime));
|
||||
qint64 ret = -1;
|
||||
if (q.execAndFetch(stmt) && !q.value(0).isNull())
|
||||
ret = q.value(0).toLongLong();
|
||||
|
||||
DEBUG_LOG(m_logger) << "next min sampleTime for {{ dt.name }} is " << ret << " (> " << fromTime << ")";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool RecordReplayPlugin::publish{{ dt.name }}(qint64 desiredTime)
|
||||
{
|
||||
if (!m_dts{{ dt.name }})
|
||||
return false;
|
||||
{
|
||||
auto it = m_{{ dt.nameFlc }}Map.begin();
|
||||
while (it != m_{{ dt.nameFlc }}Map.end() && it.key() <= desiredTime)
|
||||
{
|
||||
m_dts{{ dt.name }}->write(it.value());
|
||||
it = m_{{ dt.nameFlc }}Map.erase(it);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto it = m_{{ dt.nameFlc }}DisposedMap.begin();
|
||||
while (it != m_{{ dt.nameFlc }}DisposedMap.end() && it.key() <= desiredTime)
|
||||
{
|
||||
m_dts{{ dt.name }}->remove(it.value());
|
||||
it = m_{{ dt.nameFlc }}DisposedMap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecordReplayPlugin::clearData{{ dt.name }}()
|
||||
{
|
||||
if (m_dts{{ dt.name }})
|
||||
{
|
||||
{{ dt.name }} tmpData;
|
||||
for (auto key : m_{{ dt.nameFlc }}Keys)
|
||||
{
|
||||
tmpData.{{ dt.key.name }} = key;
|
||||
m_dts{{ dt.name }}->remove(tmpData);
|
||||
}
|
||||
}
|
||||
m_{{ dt.nameFlc }}Keys.clear();
|
||||
m_{{ dt.nameFlc }}Map.clear();
|
||||
m_{{ dt.nameFlc }}DisposedMap.clear();
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT id FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
|
||||
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
|
||||
|
||||
if (!queryFrom.exec(stmt))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
|
||||
return false;
|
||||
}
|
||||
if (!queryFrom.next() || queryFrom.value(0).isNull())
|
||||
{
|
||||
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
|
||||
return true;
|
||||
}
|
||||
const qint64 idSnapshot = queryFrom.value(0).toLongLong();
|
||||
|
||||
const QString stmtSelect(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
|
||||
|
||||
if (!queryFrom.exec(stmtSelect))
|
||||
return false;
|
||||
|
||||
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
|
||||
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
|
||||
|
||||
if (!queryFrom.exec(stmt))
|
||||
return false;
|
||||
|
||||
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo)
|
||||
{
|
||||
const QString stmtInsert(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
|
||||
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
|
||||
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
|
||||
|
||||
if (!queryTo.prepare(stmtInsert))
|
||||
return false;
|
||||
|
||||
while (queryFromPrepared.next())
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
queryTo.bindValue(QStringLiteral(":dds_is_alive"), queryFromPrepared.value(i++));
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
queryTo.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), queryFromPrepared.value(i++));
|
||||
{% endfor %}
|
||||
if (!queryTo.exec())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
+637
@@ -0,0 +1,637 @@
|
||||
/**
|
||||
* RecordReplay Plugin
|
||||
* $Id: svc_record_replay_plugin.cpp.grantlee 50268 2020-03-06 07:42:09Z ehrlicher $
|
||||
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_07_assault_energy/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee $
|
||||
* $Author: ehrlicher $
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-07-21
|
||||
*/
|
||||
|
||||
|
||||
Leider gibnt es Probleme, wenn man Sequenzen von Sequenzen von Non-PODs erstellt.
|
||||
Es erbgaben sich unresolved symbols... (Compiler war zufrieden, Linker aber nicht - hmm)
|
||||
|
||||
Mit dem folgenden Konstrukt konnten ich diese "beheben", doch je nach Konstellation in der XYZ_service.xml
|
||||
ergaben sich Fehler a la "template specialication after instatiation"
|
||||
|
||||
Daher haben wir (zumindest zunächst) Abstand von dieser Erweiterung genommen.
|
||||
|
||||
...
|
||||
|
||||
{% for dt in serializeTopics.member %}
|
||||
//
|
||||
// Begin {{ dt.typeFq }}
|
||||
//
|
||||
template<>
|
||||
{{ dt.typeFq }} fromJson(const QJsonValue &val, type<{{ dt.typeFq }}>)
|
||||
{
|
||||
const QJsonObject objElement(val.toObject());
|
||||
{{ dt.typeFq }} element;
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
{ {{ row.jsonElementToDds }}; } {% endfor %}
|
||||
|
||||
return element;
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson(const {{ dt.typeFq }} &element)
|
||||
{
|
||||
QJsonObject obj;
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
{{ row.ddsToJsonElement }}; {% endfor %}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <>
|
||||
QVariant toQVariant(const {{ dt.typeFq }} &val)
|
||||
{
|
||||
return QString::fromUtf8(QJsonDocument(toJson(val).toObject()).toJson());
|
||||
}
|
||||
|
||||
template<>
|
||||
{{ dt.typeFq }} fromQVariant(const QVariant &val, type<{{ dt.typeFq }}>)
|
||||
{
|
||||
return fromJson(QJsonDocument::fromJson(val.toString().toUtf8()).object(), type<{{ dt.typeFq }}>{});
|
||||
}
|
||||
|
||||
//
|
||||
// extensions to support sequence of seqence of Non-POD :
|
||||
//
|
||||
|
||||
template<>
|
||||
QJsonValue toJson(const std::vector < {{ dt.typeFq }} > &data)
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return QJsonValue();
|
||||
QJsonArray array;
|
||||
for(size_t i = 0; i < data.size(); ++i)
|
||||
array.push_back(toJson(data.at(i)));
|
||||
|
||||
return QJsonDocument(array).object();
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector < {{ dt.typeFq }} > fromJson(const QJsonValue &val)
|
||||
{
|
||||
std::vector < {{ dt.typeFq }} > ret;
|
||||
if (val.isNull() == 0)
|
||||
return ret;
|
||||
if (val.isArray() == 0) return ret;
|
||||
const QJsonArray array = val.toArray();
|
||||
ret.reserve(array.size());
|
||||
for(const auto &ele : array)
|
||||
ret.push_back(fromJson(ele, type<{{ dt.typeFq }}>{}));
|
||||
return ret;
|
||||
}
|
||||
//
|
||||
// : extensions to support sequence of seqence of Non-POD
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// End {{ dt.typeFq }}
|
||||
//
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
|
||||
RecordReplayPlugin::RecordReplayPlugin()
|
||||
: m_exerciseId(0)
|
||||
, m_bCanHandleSchemasRead(true)
|
||||
, m_bCanHandleSchemasWrite(true)
|
||||
, m_logger(getLogger("RecordReplay"))
|
||||
{}
|
||||
|
||||
RecordReplayPlugin::~RecordReplayPlugin() = default;
|
||||
|
||||
QString RecordReplayPlugin::getTableName(const QString &tableName, bool bCanHandleSchemas) const
|
||||
{
|
||||
if (!bCanHandleSchemas)
|
||||
return tableName;
|
||||
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::initDtc(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
|
||||
int exerciseId)
|
||||
{
|
||||
m_ddsConnector = ddsConnector;
|
||||
m_exerciseId = exerciseId;
|
||||
if (!m_ddsConnector)
|
||||
return false;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC<DTC::{{ dt.name }}Impl>());
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::initDts(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
|
||||
int exerciseId,
|
||||
bool bCanHandleSchemas,
|
||||
NSqlQuery &query)
|
||||
{
|
||||
Q_UNUSED(query);
|
||||
m_ddsConnector = ddsConnector;
|
||||
m_exerciseId = exerciseId;
|
||||
m_bCanHandleSchemasRead = bCanHandleSchemas;
|
||||
m_bCanHandleSchemasWrite = bCanHandleSchemas;
|
||||
if (!m_ddsConnector)
|
||||
return false;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS<DTS::{{ dt.name }}Impl>());
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite)
|
||||
{
|
||||
m_exerciseId = exerciseId;
|
||||
m_bCanHandleSchemasRead = bCanHandleSchemasRead;
|
||||
m_bCanHandleSchemasWrite = bCanHandleSchemasWrite;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
// {{ dt.name }}
|
||||
if (!copyInitialData{{ dt.name }}(queryFrom, queryTo, fromTime)) return false;
|
||||
if (!copyData{{ dt.name }}(queryFrom, queryTo, fromTime, toTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::ddsTopicsToDatabase(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!write{{ dt.name }}(q, minRecordSampleTimes)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::setReplayStartTime(NSqlQuery &q, qint64 startTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!read{{ dt.name }}At(q, startTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!read{{ dt.name }}(q, fromTime, toTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::publishData(qint64 desiredTime)
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (!publish{{ dt.name }}(desiredTime)) return false;
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecordReplayPlugin::clearData()
|
||||
{
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
clearData{{ dt.name }}();
|
||||
{% endif %} {% endfor %}
|
||||
}
|
||||
|
||||
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> RecordReplayPlugin::getSnapshotTimes(NSqlQuery &q,
|
||||
qint64 toTime,
|
||||
qint64 interval,
|
||||
const QHash<QString, qint64> &minRecordedSampleTimes)
|
||||
{
|
||||
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> ret;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
{
|
||||
const QString &topicName(QStringLiteral("{{ dt.name }}"));
|
||||
std::vector<RecordReplayPlugin::RecSnapshot> vec;
|
||||
auto it = minRecordedSampleTimes.find(topicName);
|
||||
if (it != minRecordedSampleTimes.end())
|
||||
{
|
||||
qint64 nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, it.value()); // is 0 or -1 if no samples are available
|
||||
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
|
||||
|
||||
if (nextSampleTime != m_snapshot{{ dt.name }}MapTime)
|
||||
{
|
||||
// the current snapshot data does not match the starting time -> reread m_{{ dt.nameFlc }}Keys
|
||||
readLastSnapshotKeysFor{{ dt.name }}(q, nextSampleTime);
|
||||
//DEBUG_LOG(m_logger) << "fromTime: " << fromTime << ", m_snapshotParticipantMapTime: " << m_snapshotParticipantMapTime
|
||||
// << " - rereading snapshot times! Map now contains " << m_snapshot{{ dt.name }}Map.size() << " entries";
|
||||
}
|
||||
|
||||
while (nextSampleTime > 0 && nextSampleTime <= toTime)
|
||||
{
|
||||
const qint64 intervalStart = nextSampleTime;
|
||||
const qint64 intervalEnd = intervalStart + interval;
|
||||
updateSnapshotKeys{{ dt.name }}(q, intervalStart, intervalEnd);
|
||||
|
||||
vec.emplace_back(RecSnapshot(intervalEnd, m_snapshot{{ dt.name }}Map));
|
||||
|
||||
nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, intervalEnd);
|
||||
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
|
||||
}
|
||||
}
|
||||
if (!vec.empty())
|
||||
ret.insert({topicName, std::move(vec)});
|
||||
}
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVector<QString> RecordReplayPlugin::getRecordTopicNames() const
|
||||
{
|
||||
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
QStringLiteral("{{ dt.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
QVector<QString> RecordReplayPlugin::getRecordTableNames() const
|
||||
{
|
||||
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
QStringLiteral("{{ dt.recTable.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
qint64 RecordReplayPlugin::getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const
|
||||
{
|
||||
QString tableName;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
if (topicName == QLatin1String("{{ dt.name }}"))
|
||||
tableName = QStringLiteral("{{ dt.recTable.name }}");
|
||||
{% endif %} {% endfor %}
|
||||
if (tableName.isEmpty())
|
||||
return defaultValue;
|
||||
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + tableName);
|
||||
if (!q.execAndFetch(stmt))
|
||||
return defaultValue;
|
||||
const QVariant val = q.value(0);
|
||||
return val.isNull() ? defaultValue : val.toLongLong();
|
||||
}
|
||||
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
|
||||
bool RecordReplayPlugin::read{{ dt.name }}At(NSqlQuery &q, qint64 startTime)
|
||||
{
|
||||
// read snapshot data for {{ dt.name }}
|
||||
if (!readLastSnapshotFor{{ dt.name }}(q, startTime)) return false;
|
||||
// read other samples between snapshot time and startTime
|
||||
if (!read{{ dt.name }}(q, m_snapshot{{ dt.name }}MapTime, startTime)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::write{{ dt.name }}(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
|
||||
{
|
||||
using DATA = {{ namespace }}::{{ dt.name }};
|
||||
|
||||
if (!m_dtc{{ dt.name }})
|
||||
return false;
|
||||
|
||||
qint64 minRecordSampleTime = std::numeric_limits<qint64>::max();
|
||||
|
||||
if (m_dtc{{ dt.name }}->hasSamples())
|
||||
{
|
||||
TRACE_LOG(m_logger) << "m_dtc{{ dt.name }}->hasSamples() == true ";
|
||||
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
|
||||
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
|
||||
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
|
||||
|
||||
if (!q.prepare(stmt))
|
||||
return false;
|
||||
|
||||
dds::sub::LoanedSamples<DATA> samples = m_dtc{{ dt.name }}->takeAlive();
|
||||
TRACE_LOG(m_logger) << "samples count: " << samples.length();
|
||||
for (const auto &sample : samples)
|
||||
{
|
||||
const DATA &data = sample.data();
|
||||
INFO_LOG(m_logger) << "m_dtc{{ dt.name }} {{ dt.recTable.ddsKey.cppMemberName }}: " << data.{{ dt.recTable.ddsKey.cppMemberName }}();
|
||||
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
q.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.ddsToQVariant }});
|
||||
{% endfor %}
|
||||
q.bindValue(QStringLiteral(":dds_is_alive"), true);
|
||||
|
||||
if (!q.exec())
|
||||
return false;
|
||||
|
||||
minRecordSampleTime = std::min(minRecordSampleTime, data.sampleTime());
|
||||
}
|
||||
}
|
||||
|
||||
dds::sub::LoanedSamples<DATA> disposedSamples = m_dtc{{ dt.name }}->takeDisposed();
|
||||
if (disposedSamples.length() != 0)
|
||||
{
|
||||
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
|
||||
QStringLiteral(" ( dds_is_alive, sample_time, {{ dt.recTable.ddsKey.dbColumnName }}) VALUES "
|
||||
" (:dds_is_alive,:sample_time,:{{ dt.recTable.ddsKey.dbColumnName }});"));
|
||||
|
||||
if (!q.prepare(stmt))
|
||||
return false;
|
||||
|
||||
DEBUG_LOG(m_logger) << "disposedSamples count: " << disposedSamples.length();
|
||||
auto dr = m_dtc{{ dt.name }}->dataReader()->dataReader();
|
||||
{{ dt.name }} data;
|
||||
for (const auto &sample : disposedSamples)
|
||||
{
|
||||
const qint64 sampleTime = sample.info().source_timestamp().to_millisecs();
|
||||
dr.key_value(data, sample.info().instance_handle());
|
||||
q.bindValue(QStringLiteral(":dds_is_alive"), false);
|
||||
q.bindValue(QStringLiteral(":sample_time"), sampleTime);
|
||||
q.bindValue(QStringLiteral(":{{ dt.recTable.ddsKey.cppMemberName }}"), {{ dt.recTable.ddsKey.ddsToQVariant }});
|
||||
|
||||
if (!q.exec())
|
||||
return false;
|
||||
|
||||
minRecordSampleTime = std::min(minRecordSampleTime, sampleTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (minRecordSampleTime != std::numeric_limits<qint64>::max())
|
||||
minRecordSampleTimes[QStringLiteral("{{ dt.name }}")] = minRecordSampleTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// read the last snapshot which is <= startTime, set m_snapshot{{ dt.name }}MapTime to the snapshot time
|
||||
bool RecordReplayPlugin::readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime)
|
||||
{
|
||||
// rebuild m_{{ dt.nameFlc }}Map content for given startTime
|
||||
clearData{{ dt.name }}();
|
||||
|
||||
const QString stmt(QStringLiteral("SELECT id, time_stamp FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
|
||||
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
|
||||
|
||||
if (!q.exec(stmt))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
|
||||
return false;
|
||||
}
|
||||
if (!q.next() || q.value(0).isNull())
|
||||
{
|
||||
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
|
||||
return true;
|
||||
}
|
||||
const qint64 idSnapshot = q.value(0).toLongLong();
|
||||
const qint64 timeSnapshot = q.value(1).toLongLong();
|
||||
|
||||
const QString stmt2(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
|
||||
|
||||
if (!q.exec(stmt2))
|
||||
return false;
|
||||
|
||||
{{ namespace }}::{{ dt.name }} data;
|
||||
while (q.next())
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
const bool bIsAlive = q.value(i++).toBool();
|
||||
{% for row in dt.recTable.viewMember %}
|
||||
{{ row.qVariantToDds }};
|
||||
{% endfor %}
|
||||
|
||||
if (bIsAlive)
|
||||
m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data);
|
||||
else
|
||||
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data);
|
||||
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}());
|
||||
}
|
||||
m_snapshot{{ dt.name }}MapTime = timeSnapshot;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RecordReplayPlugin::read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
|
||||
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
|
||||
|
||||
if (!q.exec(stmt))
|
||||
return false;
|
||||
|
||||
{{ namespace }}::{{ dt.name }} data;
|
||||
while (q.next())
|
||||
{
|
||||
int i = 0;
|
||||
const bool bIsAlive = q.value(i++).toBool();
|
||||
|
||||
{% for row in dt.recTable.viewMember %}
|
||||
{{ row.qVariantToDds }};
|
||||
{% endfor %}
|
||||
|
||||
if (bIsAlive)
|
||||
m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data);
|
||||
else
|
||||
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data);
|
||||
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT {{ dt.recTable.ddsKey.dbColumnName }}, {{ dt.recTable.primaryKey.dbColumnName }}, dds_is_alive FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime) +
|
||||
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) +
|
||||
QStringLiteral(" ORDER BY sample_time "));
|
||||
|
||||
DEBUG_LOG(m_logger) << "stmt: " << stmt;
|
||||
|
||||
if (!q.exec(stmt))
|
||||
return false;
|
||||
|
||||
while (q.next())
|
||||
{
|
||||
const qint64 key = q.value(0){{ dt.recTable.ddsKey.cppFromQvPost }};
|
||||
const qint64 pk = q.value(1){{ dt.recTable.primaryKey.cppFromQvPost }};
|
||||
const bool isAlive = q.value(2).toBool();
|
||||
if (isAlive)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "INSERT {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
|
||||
m_snapshot{{ dt.name }}Map[key] = pk;
|
||||
m_{{ dt.nameFlc }}Keys.insert(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "REMOVE {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
|
||||
m_snapshot{{ dt.name }}Map.erase(key);
|
||||
m_{{ dt.nameFlc }}Keys.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
// new validity time for snapshot data
|
||||
m_snapshot{{ dt.name }}MapTime = toTime;
|
||||
|
||||
DEBUG_LOG(m_logger) << "updateSnapshotKeys{{ dt.name }}(" << fromTime << ", " << toTime << ") = "
|
||||
<< m_snapshot{{ dt.name }}Map.size();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT rec_id_array FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
|
||||
" ORDER BY time_stamp DESC LIMIT 1").arg(fromTime));
|
||||
|
||||
// no previous snapshot found?
|
||||
if (!q.exec(stmt))
|
||||
return false;
|
||||
if (!q.next() || q.value(0).isNull())
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "no snapshot entry for {{ dt.name }} found";
|
||||
return true;
|
||||
}
|
||||
|
||||
m_snapshot{{ dt.name }}Map = LtsUtils::BigintArray2D::fromPgString(q.value(0).toString())();
|
||||
|
||||
DEBUG_LOG(m_logger) << "Map size {{ dt.name }}: " << m_snapshot{{ dt.name }}Map.size();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
qint64 RecordReplayPlugin::getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime));
|
||||
qint64 ret = -1;
|
||||
if (q.execAndFetch(stmt) && !q.value(0).isNull())
|
||||
ret = q.value(0).toLongLong();
|
||||
|
||||
DEBUG_LOG(m_logger) << "next min sampleTime for {{ dt.name }} is " << ret << " (> " << fromTime << ")";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool RecordReplayPlugin::publish{{ dt.name }}(qint64 desiredTime)
|
||||
{
|
||||
if (!m_dts{{ dt.name }})
|
||||
return false;
|
||||
{
|
||||
auto it = m_{{ dt.nameFlc }}Map.begin();
|
||||
while (it != m_{{ dt.nameFlc }}Map.end() && it.key() <= desiredTime)
|
||||
{
|
||||
m_dts{{ dt.name }}->write(it.value());
|
||||
it = m_{{ dt.nameFlc }}Map.erase(it);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto it = m_{{ dt.nameFlc }}DisposedMap.begin();
|
||||
while (it != m_{{ dt.nameFlc }}DisposedMap.end() && it.key() <= desiredTime)
|
||||
{
|
||||
m_dts{{ dt.name }}->remove(it.value());
|
||||
it = m_{{ dt.nameFlc }}DisposedMap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecordReplayPlugin::clearData{{ dt.name }}()
|
||||
{
|
||||
if (m_dts{{ dt.name }})
|
||||
{
|
||||
{{ dt.name }} tmpData;
|
||||
for (auto key : m_{{ dt.nameFlc }}Keys)
|
||||
{
|
||||
tmpData.{{ dt.key.name }}(key);
|
||||
m_dts{{ dt.name }}->remove(tmpData);
|
||||
}
|
||||
}
|
||||
m_{{ dt.nameFlc }}Keys.clear();
|
||||
m_{{ dt.nameFlc }}Map.clear();
|
||||
m_{{ dt.nameFlc }}DisposedMap.clear();
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT id FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
|
||||
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
|
||||
|
||||
if (!queryFrom.exec(stmt))
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
|
||||
return false;
|
||||
}
|
||||
if (!queryFrom.next() || queryFrom.value(0).isNull())
|
||||
{
|
||||
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
|
||||
return true;
|
||||
}
|
||||
const qint64 idSnapshot = queryFrom.value(0).toLongLong();
|
||||
|
||||
const QString stmtSelect(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
|
||||
|
||||
if (!queryFrom.exec(stmtSelect))
|
||||
return false;
|
||||
|
||||
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
|
||||
{
|
||||
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
|
||||
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
|
||||
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
|
||||
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
|
||||
|
||||
if (!queryFrom.exec(stmt))
|
||||
return false;
|
||||
|
||||
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
|
||||
}
|
||||
|
||||
bool RecordReplayPlugin::copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo)
|
||||
{
|
||||
const QString stmtInsert(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
|
||||
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
|
||||
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
|
||||
|
||||
if (!queryTo.prepare(stmtInsert))
|
||||
return false;
|
||||
|
||||
while (queryFromPrepared.next())
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
queryTo.bindValue(QStringLiteral(":dds_is_alive"), queryFromPrepared.value(i++));
|
||||
{% for row in dt.recTable.cppMember %}
|
||||
queryTo.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), queryFromPrepared.value(i++));
|
||||
{% endfor %}
|
||||
if (!queryTo.exec())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* RecordReplay Plugin
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-07-21
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_record_replay_plugin.h
|
||||
#pragma once
|
||||
|
||||
#include <QMultiMap>
|
||||
#include <QVector>
|
||||
#include <set>
|
||||
#include <DDS/DDSConnector/dds_connector.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_types.h>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_dts_types.h>
|
||||
#include <{{ include_prefix }}/logger.h>
|
||||
|
||||
// Resolved from the Lts include root (see include_directories(BEFORE .../Lts)
|
||||
// in the root CMakeLists); the old <../../Common/...> spelling only worked
|
||||
// for in-source builds.
|
||||
#include <Common/PluginInterfaces/record_replay_plugin.h>
|
||||
|
||||
#include <LtsUtilsCore/pgarray.h>
|
||||
|
||||
{% if custom_record_replay %}
|
||||
#ifndef {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT
|
||||
# ifdef BUILD_STATIC_LIBRARIES
|
||||
# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT
|
||||
# else
|
||||
# if defined(MAKE_{{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_LIB)
|
||||
# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT Q_DECL_EXPORT
|
||||
# else
|
||||
# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT Q_DECL_IMPORT
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
{% endif %}
|
||||
|
||||
namespace {{ namespace }} {
|
||||
|
||||
class {% if custom_record_replay %}{{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT{% endif %}
|
||||
RecordReplayPlugin {% if not custom_record_replay %}final{% endif %}: public QObject, public RecordReplay::RecordReplayPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
{% if not custom_record_replay %}
|
||||
Q_PLUGIN_METADATA(IID "special:3:recordreplay.{{ namespace_abbr }}")
|
||||
{% endif %}
|
||||
Q_INTERFACES(RecordReplay::RecordReplayPlugin)
|
||||
public:
|
||||
RecordReplayPlugin();
|
||||
~RecordReplayPlugin();
|
||||
QString pluginName() const override { return QStringLiteral("{{ namespace_abbr }}RecordReplayPlugin"); }
|
||||
|
||||
bool initDtc(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
|
||||
int exerciseId) override;
|
||||
bool initDts(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
|
||||
int exerciseId,
|
||||
bool bCanHandleSchemas,
|
||||
NSqlQuery &query) override;
|
||||
bool initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite) override;
|
||||
bool copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) override;
|
||||
QString svcShortName() const override { return QStringLiteral("{{ namespace_abbr }}"); }
|
||||
// recording functions
|
||||
bool ddsTopicsToDatabase(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes) override;
|
||||
// publishing functions
|
||||
bool setReplayStartTime(NSqlQuery &q, qint64 startTime) override;
|
||||
bool ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime) override;
|
||||
bool publishData(qint64 desiredTime) override;
|
||||
void clearData() override;
|
||||
|
||||
// structure/functions for snapshot creation
|
||||
std::map<QString, std::vector<RecSnapshot>> getSnapshotTimes(NSqlQuery &query,
|
||||
qint64 toTime,
|
||||
qint64 interval,
|
||||
const QHash<QString, qint64> &minRecordedSampleTimes) override;
|
||||
QVector<QString> getRecordTopicNames() const override;
|
||||
QVector<QString> getRecordTableNames() const override;
|
||||
qint64 getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const override;
|
||||
|
||||
// getter for initial service startup
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
// {{ dt.name }}
|
||||
QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> &get{{ dt.name }}Map() { return m_{{ dt.nameFlc }}Map; }
|
||||
const QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> &get{{ dt.name }}Map() const { return m_{{ dt.nameFlc }}Map; }
|
||||
bool read{{ dt.name }}At(NSqlQuery &q, qint64 startTime);
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
protected:
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
// {{ dt.name }}
|
||||
bool write{{ dt.name }}(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes);
|
||||
bool read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime);
|
||||
bool readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime);
|
||||
bool updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime);
|
||||
bool readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime);
|
||||
qint64 getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime);
|
||||
bool publish{{ dt.name }}(qint64 desiredTime);
|
||||
void clearData{{ dt.name }}();
|
||||
bool copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime);
|
||||
bool copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime);
|
||||
bool copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo);
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
QString getTableName(const QString &tableName, bool bCanHandleSchemas) const;
|
||||
protected:
|
||||
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
|
||||
int m_exerciseId;
|
||||
bool m_bCanHandleSchemasRead;
|
||||
bool m_bCanHandleSchemasWrite;
|
||||
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
|
||||
// {{ dt.name }}
|
||||
std::unique_ptr<DTC::{{ dt.name }}> m_dtc{{ dt.name }};
|
||||
std::unique_ptr<DTS::{{ dt.name }}> m_dts{{ dt.name }};
|
||||
QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> m_{{ dt.nameFlc }}Map;
|
||||
QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> m_{{ dt.nameFlc }}DisposedMap;
|
||||
std::set<qint64> m_{{ dt.nameFlc }}Keys;
|
||||
std::map<int64_t,int64_t> m_snapshot{{ dt.name }}Map;
|
||||
int64_t m_snapshot{{ dt.name }}MapTime = -1; // time for which the snapshot is valid
|
||||
{% endif %} {% endfor %}
|
||||
|
||||
NGL::INGLLogger& m_logger;
|
||||
};
|
||||
|
||||
}
|
||||
{% endautoescape %}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* DDS Type Plugin to provide custom types to other applications like ddsview
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-07-08
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_topic_type_plugin.cpp
|
||||
|
||||
#include "{{ namespace_abbr }}_topic_type_plugin.h"
|
||||
#include <{{ namespace_abbr }}_dtc_types.h>
|
||||
#include <{{ namespace_abbr }}_ftc_types.h>
|
||||
#include <{{ namespace_abbr }}_fts_types.h>
|
||||
#include <DDS/FTC/generic_ftc_impl.h>
|
||||
#include <DDS/FTS/generic_fts_impl.h>
|
||||
#include <DDS/Routing/routing_plugin.h>
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
QMap<std::string, DDSGeneric::TopicDescription> TopicTypePlugin::getDataTopics() const
|
||||
{
|
||||
return
|
||||
{{% for dt in dataTopics.member %}
|
||||
{
|
||||
dds::topic::topic_type_name<{{ dt.name }}>::value(),
|
||||
{
|
||||
DDS::Helper::getShortTopicName(dds::topic::topic_type_name<{{ dt.name }}>::value()),
|
||||
dds::topic::topic_type_name<{{ dt.name }}>::value(),
|
||||
rti::topic::dynamic_type<{{ dt.name }}>::get()
|
||||
}
|
||||
}{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
QMap<std::string, DDSGeneric::TopicDescription> TopicTypePlugin::getFunctionTopics() const
|
||||
{
|
||||
return
|
||||
{{% for ft in functionTopics.member %}
|
||||
{
|
||||
FTC::TopicNames<FTC::{{ ft.name }}>::requestTopicName(),
|
||||
{
|
||||
FTC::TopicNames<FTC::{{ ft.name }}>::requestTopicName(),
|
||||
dds::topic::topic_type_name<FTC::{{ ft.name }}::RequestTopic>::value(),
|
||||
rti::topic::dynamic_type<FTC::{{ ft.name }}::RequestTopic>::get()
|
||||
}
|
||||
},
|
||||
{
|
||||
FTC::TopicNames<FTC::{{ ft.name }}>::replyTopicName(),
|
||||
{
|
||||
FTC::TopicNames<FTC::{{ ft.name }}>::replyTopicName(),
|
||||
dds::topic::topic_type_name<FTC::{{ ft.name }}::ReplyTopic>::value(),
|
||||
rti::topic::dynamic_type<FTC::{{ ft.name }}::ReplyTopic>::get()
|
||||
}
|
||||
}{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
inline NGL::INGLLogger &logger(const char *name)
|
||||
{
|
||||
return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(name);
|
||||
}
|
||||
|
||||
std::vector<RoutingService::TopicDispatcherInterface *> TopicTypePlugin::topicsToRouteToWs(IDL::PartitionType partitionType) const
|
||||
{
|
||||
if (partitionType == IDL::PartitionType::Replay)
|
||||
return
|
||||
{{% for dt in dataTopics.topicsToRouteInReplay %}
|
||||
new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %}
|
||||
{% for ft in functionTopics.topicsToRouteInReplay %}
|
||||
new RoutingService::FunctionTopicReplyDispatcher<FTS::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Reply")){% if not forloop.last %},{% endif %}{% endfor %}
|
||||
};
|
||||
|
||||
return
|
||||
{{% for dt in dataTopics.topicsToRouteToClient %}
|
||||
new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %}
|
||||
{% for ft in functionTopics.member %}
|
||||
new RoutingService::FunctionTopicReplyDispatcher<FTS::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Reply")){% if not forloop.last %},{% endif %}{% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<RoutingService::TopicDispatcherInterface *> TopicTypePlugin::topicsToRouteToSv(IDL::PartitionType partitionType) const
|
||||
{
|
||||
if (partitionType == IDL::PartitionType::Replay)
|
||||
return
|
||||
{{% for ft in functionTopics.topicsToRouteInReplay %}
|
||||
new RoutingService::FunctionTopicRequestDispatcher<FTC::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Request")){% if not forloop.last %},{% endif %}{% endfor %}
|
||||
};
|
||||
|
||||
return
|
||||
{{% for dt in dataTopics.topicsToRouteToServer %}
|
||||
new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %}
|
||||
{% for ft in functionTopics.member %}
|
||||
new RoutingService::FunctionTopicRequestDispatcher<FTC::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Request")){% if not forloop.last %},{% endif %}{% endfor %}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* DDS Type Plugin to provide custom types to other applications like ddsview
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Christian Ehrlicher, SLSE, 2016-07-08
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_topic_type_plugin.h
|
||||
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
|
||||
#include <Common/ProjectConfig/project_defines.h>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
// Resolved from the Lts include root (see include_directories(BEFORE .../Lts)
|
||||
// in the root CMakeLists); the old <../../DDS/...> spelling only worked for
|
||||
// in-source builds because moc/gcc do not resolve it relative to this file.
|
||||
#include <DDS/Generic/topic_type_plugin.h>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
class TopicTypePlugin final : public QObject, public DDSGeneric::TopicTypePlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "topic_type:3:{{ namespace_abbr }}")
|
||||
Q_INTERFACES(DDSGeneric::TopicTypePlugin)
|
||||
public:
|
||||
TopicTypePlugin() = default;
|
||||
|
||||
QMap<std::string, DDSGeneric::TopicDescription> getDataTopics() const override final;
|
||||
QMap<std::string, DDSGeneric::TopicDescription> getFunctionTopics() const override final;
|
||||
std::vector<RoutingService::TopicDispatcherInterface *> topicsToRouteToWs(IDL::PartitionType partitionType) const override final;
|
||||
std::vector<RoutingService::TopicDispatcherInterface *> topicsToRouteToSv(IDL::PartitionType partitionType) const override final;
|
||||
QString getServiceShortName() const override final { return QStringLiteral("{{ namespace_abbr_upper }}"); }
|
||||
};
|
||||
}
|
||||
|
||||
{% endautoescape %}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Convert DSS enumerations to translatable strings
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
// filename: {{ namespace_abbr }}_translation.cpp
|
||||
|
||||
#include "{{ namespace_abbr }}_translation.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
{% for idl_enum in idl_enums %}
|
||||
//
|
||||
// {{ idl_enum.name }}
|
||||
//
|
||||
QString toString({{ idl_enum.name }} state)
|
||||
{
|
||||
switch(state)
|
||||
{{% for idl_member in idl_enum.member %}
|
||||
case {{ idl_enum.name }}::{{ idl_member.key }}: return QCoreApplication::translate("{{ idl_enum.name }}", "{{ idl_member.key }}");
|
||||
{% endfor %}
|
||||
default: break;
|
||||
}
|
||||
return QCoreApplication::translate("{{ idl_enum.name }}", "Unknown (%1)").arg(static_cast<int>(state));
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
{% for dt in dataTopics.member %}{% if dt.hasTrColumns %}{% for tr in dt.trColumns %}
|
||||
// {{ dt.name }}::{{ tr.name }}
|
||||
QString tr{{ dt.name }}{{ tr.nameCc }}(const std::string &text)
|
||||
{
|
||||
return QCoreApplication::translate("{{ dt.name }}", text.c_str(), "{{ tr.name }}");
|
||||
}
|
||||
{% endfor %}{% endif %}{% endfor %}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* Convert DSS enumerations to translatable strings
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
{% autoescape off %}
|
||||
#pragma once
|
||||
|
||||
// filename: {{ namespace_abbr }}_translation.h
|
||||
|
||||
#include <string>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
|
||||
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
|
||||
|
||||
class QString;
|
||||
|
||||
namespace {{ namespace }}
|
||||
{
|
||||
// Translatable enumerations:
|
||||
{% for idl_enum in idl_enums %}
|
||||
{{ namespace_abbr_upper }}_COMMON_EXPORT QString toString({{ idl_enum.name }} state);{% endfor %}
|
||||
|
||||
// Translatable struct members:
|
||||
{% for dt in dataTopics.member %}{% if dt.hasTrColumns %}{% for tr in dt.trColumns %}
|
||||
{{ namespace_abbr_upper }}_COMMON_EXPORT QString tr{{ dt.nameCc }}{{ tr.nameCc }}(const std::string &text); // {{ dt.nameCc }}::{{ tr.name }} {% endfor %}{% endif %}{% endfor %}
|
||||
|
||||
} // namespace {{ namespace }}
|
||||
|
||||
{% endautoescape %}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QDir>
|
||||
#include <ktexttemplate/engine.h>
|
||||
#include <ktexttemplate/templateloader.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
QString templatesDir = QDir::currentPath();
|
||||
if (argc > 1)
|
||||
{
|
||||
templatesDir = QString::fromLocal8Bit(argv[1]);
|
||||
}
|
||||
|
||||
KTextTemplate::Engine engine;
|
||||
auto loader = QSharedPointer<KTextTemplate::FileSystemTemplateLoader>::create();
|
||||
loader->setTemplateDirs(QStringList() << templatesDir);
|
||||
engine.addTemplateLoader(loader);
|
||||
|
||||
QDir d(templatesDir);
|
||||
QStringList files = d.entryList(QStringList() << QStringLiteral("*.grantlee"), QDir::Files);
|
||||
if (files.isEmpty())
|
||||
{
|
||||
qDebug() << "No .grantlee templates found in" << templatesDir;
|
||||
// Treat absence as successful parse (nothing to check) to avoid failing
|
||||
// when directory is empty in different setups.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
for (const QString& f : files)
|
||||
{
|
||||
KTextTemplate::Template t;
|
||||
try
|
||||
{
|
||||
t = engine.loadByName(f);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
qDebug() << "Exception while loading" << f;
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
if (!t)
|
||||
{
|
||||
qDebug() << "Failed to load template" << f;
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
if (t->error() != KTextTemplate::NoError)
|
||||
{
|
||||
qDebug() << "Parse error in" << f << ":" << t->errorString();
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
// Render with empty context to ensure rendering path also works.
|
||||
KTextTemplate::Context ctx;
|
||||
QString out = t->render(&ctx);
|
||||
if (out.isNull())
|
||||
{
|
||||
qDebug() << "Rendering produced null for" << f;
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return ok ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# Unit tests for the CodeGen module.
|
||||
# One test executable per module, each registered with ctest.
|
||||
#
|
||||
if (NOT BUILD_TESTING)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# --- CodeGen: string helpers, data classes and Grantlee template-context
|
||||
# --- wrappers (pure logic; the KTextTemplate engine is covered by
|
||||
# --- GrantleeParserSmoke)
|
||||
if (TARGET codegen_grantlee)
|
||||
ADD_EXECUTABLE(codegen_unit_test codegen_unit_test.cpp)
|
||||
TARGET_LINK_LIBRARIES(codegen_unit_test
|
||||
codegen_grantlee
|
||||
Qt6::Core
|
||||
Qt6::Test
|
||||
)
|
||||
ADD_TEST(NAME codegen_unit_test COMMAND $<TARGET_FILE:codegen_unit_test>)
|
||||
endif()
|
||||
@@ -0,0 +1,701 @@
|
||||
//
|
||||
// Unit tests for the ngl_codegen module (Lts/Common/CodeGen): the pure-logic
|
||||
// string helpers (StructBuilder statics), the codegen_base data classes and
|
||||
// the KTextTemplate context wrappers (GrantleeWrappers).
|
||||
// No template execution is done here; the Grantlee/KTextTemplate engine
|
||||
// itself is covered by the GrantleeParserSmoke smoke test.
|
||||
//
|
||||
#include <CodeGen/GrantleeWrappers.h>
|
||||
#include <CodeGen/SqlGenerator.h>
|
||||
#include <CodeGen/StructBuilder.h>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtTest/QTest>
|
||||
|
||||
using namespace CodeGen;
|
||||
|
||||
// Minimal concrete DataTypeInfo for the IdlDataWrapper tests (the module's
|
||||
// own concrete data types are private to StructBuilder.cpp).
|
||||
class TestDataTypeInfo : public DataTypeInfo
|
||||
{
|
||||
public:
|
||||
TestDataTypeInfo(const QString& idlType, const QString& cppType, bool isPod)
|
||||
: DataTypeInfo(QStringLiteral("test"), isPod), m_idlType(idlType), m_cppType(cppType)
|
||||
{
|
||||
}
|
||||
|
||||
QString idlType() const override final
|
||||
{
|
||||
return m_idlType;
|
||||
}
|
||||
|
||||
QString ddsType() const override final
|
||||
{
|
||||
return m_cppType;
|
||||
}
|
||||
|
||||
QString sqlType() const override final
|
||||
{
|
||||
return m_cppType;
|
||||
}
|
||||
|
||||
QString cppType() const override final
|
||||
{
|
||||
return m_cppType;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_idlType;
|
||||
QString m_cppType;
|
||||
};
|
||||
|
||||
static MemberInformation makeMember(const QString& table, const QString& column)
|
||||
{
|
||||
MemberInformation mi;
|
||||
mi.ea.table = table;
|
||||
mi.ea.column = column;
|
||||
mi.db.table = table;
|
||||
mi.db.columnName = column;
|
||||
mi.cpp.memberName = column;
|
||||
return mi;
|
||||
}
|
||||
|
||||
class codegen_unit_test : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void firstCharLowercase();
|
||||
void camelCaseString();
|
||||
void camelCaseToUnderline();
|
||||
|
||||
void nameWrapperTransforms();
|
||||
void memberWrapperGetters();
|
||||
void memberWrapperVariantConversion();
|
||||
void memberWrapperDdsToJsonElement();
|
||||
void memberWrapperJsonElementToDds();
|
||||
void memberWrapperDbToJsonElement();
|
||||
void memberWrapperJsonElementToDb();
|
||||
void memberWrapperJsonPrePostFallback();
|
||||
void memberListWrapperFilters();
|
||||
|
||||
void dataTopicWrapper();
|
||||
void functionTopicWrapper();
|
||||
void idlWrappers();
|
||||
void dbStructWrapper();
|
||||
|
||||
void codegenBaseDataClasses();
|
||||
};
|
||||
|
||||
void codegen_unit_test::firstCharLowercase()
|
||||
{
|
||||
QCOMPARE(StructBuilder::firstCharLowercase(QString()), QString());
|
||||
QCOMPARE(StructBuilder::firstCharLowercase(QStringLiteral("FooBar")), QStringLiteral("fooBar"));
|
||||
QCOMPARE(StructBuilder::firstCharLowercase(QStringLiteral("abc")), QStringLiteral("abc"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::camelCaseString()
|
||||
{
|
||||
// underscore-separated -> camelCase (default keeps the first character lower case)
|
||||
QCOMPARE(StructBuilder::camelCaseString(QStringLiteral("foo_bar")), QStringLiteral("fooBar"));
|
||||
QCOMPARE(StructBuilder::camelCaseString(QStringLiteral("foo_bar"), true), QStringLiteral("FooBar"));
|
||||
QCOMPARE(StructBuilder::camelCaseString(QStringLiteral("foo")), QStringLiteral("foo"));
|
||||
QCOMPARE(StructBuilder::camelCaseString(QStringLiteral("_foo")), QStringLiteral("Foo"));
|
||||
// trailing underscore sets the "next is upper" flag but appends nothing
|
||||
QCOMPARE(StructBuilder::camelCaseString(QStringLiteral("foo_")), QStringLiteral("foo"));
|
||||
QCOMPARE(StructBuilder::camelCaseString(QString()), QString());
|
||||
}
|
||||
|
||||
void codegen_unit_test::camelCaseToUnderline()
|
||||
{
|
||||
QCOMPARE(StructBuilder::camelCaseToUnderline(QStringLiteral("FooBarBaz")), QStringLiteral("foo_bar_baz"));
|
||||
QCOMPARE(StructBuilder::camelCaseToUnderline(QStringLiteral("foo")), QStringLiteral("foo"));
|
||||
QCOMPARE(StructBuilder::camelCaseToUnderline(QStringLiteral("A")), QStringLiteral("a"));
|
||||
QCOMPARE(StructBuilder::camelCaseToUnderline(QStringLiteral("aB")), QStringLiteral("a_b"));
|
||||
// digits are kept, an upper case after a digit still splits
|
||||
QCOMPARE(StructBuilder::camelCaseToUnderline(QStringLiteral("Foo1Bar")), QStringLiteral("foo1_bar"));
|
||||
QCOMPARE(StructBuilder::camelCaseToUnderline(QString()), QString());
|
||||
}
|
||||
|
||||
void codegen_unit_test::nameWrapperTransforms()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
NameWrapper w(QStringLiteral("foo_bar"), &parent);
|
||||
QCOMPARE(w.name(), QStringLiteral("foo_bar"));
|
||||
QCOMPARE(w.nameCc(), QStringLiteral("FooBar"));
|
||||
QCOMPARE(w.nameFlc(), QStringLiteral("fooBar"));
|
||||
|
||||
NameWrapper w2(QStringLiteral("FooBar"), &parent);
|
||||
QCOMPARE(w2.nameCc(), QStringLiteral("FooBar"));
|
||||
QCOMPARE(w2.nameFlc(), QStringLiteral("fooBar"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperGetters()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("a_name"));
|
||||
mi.ea.type = QStringLiteral("varchar(255)");
|
||||
mi.db.datatype = QStringLiteral("varchar(255)");
|
||||
mi.db.columnNameView = QStringLiteral("a_name_v");
|
||||
mi.db.hasSequence = true;
|
||||
mi.cpp.structName = QStringLiteral("AStruct");
|
||||
mi.cpp.defaultValue = QStringLiteral("''");
|
||||
mi.cpp.type = QStringLiteral("QString");
|
||||
mi.cpp.ddsType = QStringLiteral("dds::core::string255");
|
||||
mi.cpp.isOptional = true;
|
||||
mi.cpp.usedForCppStruct = true;
|
||||
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.cppMemberName(), QStringLiteral("a_name"));
|
||||
QCOMPARE(w.cppMemberNameCc(), QStringLiteral("AName"));
|
||||
QCOMPARE(w.cppStructName(), QStringLiteral("AStruct"));
|
||||
QCOMPARE(w.cppType(), QStringLiteral("QString"));
|
||||
QCOMPARE(w.cppDefault(), QStringLiteral("''"));
|
||||
QCOMPARE(w.dbTableName(), QStringLiteral("tbl_a"));
|
||||
QCOMPARE(w.dbColumnName(), QStringLiteral("a_name"));
|
||||
QCOMPARE(w.dbColumnNameView(), QStringLiteral("a_name_v"));
|
||||
QCOMPARE(w.cppDdsType(), QStringLiteral("dds::core::string255"));
|
||||
QCOMPARE(w.dbHasSequence(), true);
|
||||
QCOMPARE(w.isOptional(), true);
|
||||
QCOMPARE(w.isCppStruct(), true);
|
||||
QCOMPARE(w.info().db.table, QStringLiteral("tbl_a"));
|
||||
|
||||
// view column name and dds type fall back when empty
|
||||
MemberInformation mi2 = makeMember(QStringLiteral("tbl_b"), QStringLiteral("b_id"));
|
||||
mi2.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w2(mi2, &parent);
|
||||
QCOMPARE(w2.dbColumnNameView(), QStringLiteral("b_id"));
|
||||
QCOMPARE(w2.cppDdsType(), QStringLiteral("int"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperVariantConversion()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("foo"));
|
||||
mi.cpp.type = QStringLiteral("int");
|
||||
mi.cpp.isOptional = false;
|
||||
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.ddsToQVariant(), QStringLiteral("toQVariant(data.foo)"));
|
||||
QCOMPARE(w.qVariantToDds(), QStringLiteral("data.foo = fromQVariant<int>(q.value(i++))"));
|
||||
|
||||
// optional members are wrapped in dds::core::optional<>
|
||||
mi.cpp.isOptional = true;
|
||||
mi.cpp.ddsType = QStringLiteral("MyStruct");
|
||||
MemberInformationWrapper w2(mi, &parent);
|
||||
QCOMPARE(w2.qVariantToDds(),
|
||||
QStringLiteral("data.foo = fromQVariant<dds::core::optional<MyStruct>>(q.value(i++))"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperDdsToJsonElement()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
// plain member
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("foo"));
|
||||
mi.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.ddsToJsonElement(), QStringLiteral("obj[QStringLiteral(\"foo\")] = element.foo"));
|
||||
|
||||
// optional member goes through has_value()
|
||||
mi.cpp.isOptional = true;
|
||||
MemberInformationWrapper w2(mi, &parent);
|
||||
QCOMPARE(w2.ddsToJsonElement(),
|
||||
QStringLiteral("if (element.foo.has_value()) obj[QStringLiteral(\"foo\")] = *element.foo"));
|
||||
|
||||
// empty member name falls back to the member type
|
||||
MemberInformation mi3;
|
||||
mi3.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w3(mi3, &parent);
|
||||
QCOMPARE(w3.ddsToJsonElement(), QStringLiteral("obj[QStringLiteral(\"int\")] = element;"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperJsonElementToDds()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("foo"));
|
||||
mi.cpp.type = QStringLiteral("int");
|
||||
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.jsonElementToDds(),
|
||||
QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"foo\"))); element.foo = val"));
|
||||
|
||||
mi.cpp.isOptional = true;
|
||||
MemberInformationWrapper w2(mi, &parent);
|
||||
QCOMPARE(w2.jsonElementToDds(), QStringLiteral(
|
||||
"const QJsonValue val(objElement.value(QStringLiteral(\"foo\"))); "
|
||||
"if (!val.isNull()) element.foo = val"));
|
||||
|
||||
// empty member name falls back to the member type
|
||||
MemberInformation mi3;
|
||||
mi3.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w3(mi3, &parent);
|
||||
QCOMPARE(w3.jsonElementToDds(), QStringLiteral("element = objElement.value(QStringLiteral(\"int\"))"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperDbToJsonElement()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
// plain member
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("foo"));
|
||||
mi.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.dbToJsonElement(), QStringLiteral("obj[QStringLiteral(\"foo\")] = element.foo"));
|
||||
|
||||
// optional (non-QDateTime) member is only written when it has a value
|
||||
mi.cpp.type = QStringLiteral("QString");
|
||||
mi.cpp.isOptional = true;
|
||||
MemberInformationWrapper w2(mi, &parent);
|
||||
QCOMPARE(w2.dbToJsonElement(),
|
||||
QStringLiteral("if (!element.foo.isNull()) obj[QStringLiteral(\"foo\")] = element.foo"));
|
||||
|
||||
// optional QDateTime member uses isValid()
|
||||
mi.cpp.type = QStringLiteral("QDateTime");
|
||||
MemberInformationWrapper w3(mi, &parent);
|
||||
QCOMPARE(w3.dbToJsonElement(),
|
||||
QStringLiteral("if (element.foo.isValid()) obj[QStringLiteral(\"foo\")] = element.foo"));
|
||||
|
||||
// empty member name falls back to the member type
|
||||
MemberInformation mi4;
|
||||
mi4.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w4(mi4, &parent);
|
||||
QCOMPARE(w4.dbToJsonElement(), QStringLiteral("obj[QStringLiteral(\"int\")] = element;"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperJsonElementToDb()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("foo"));
|
||||
mi.cpp.type = QStringLiteral("int");
|
||||
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.jsonElementToDb(),
|
||||
QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"foo\"))); element.foo = val"));
|
||||
|
||||
mi.cpp.isOptional = true;
|
||||
MemberInformationWrapper w2(mi, &parent);
|
||||
QCOMPARE(w2.jsonElementToDb(), QStringLiteral(
|
||||
"const QJsonValue val(objElement.value(QStringLiteral(\"foo\"))); "
|
||||
"if (!val.isUndefined()) element.foo = val"));
|
||||
|
||||
// empty member name falls back to the member type
|
||||
MemberInformation mi3;
|
||||
mi3.cpp.type = QStringLiteral("int");
|
||||
MemberInformationWrapper w3(mi3, &parent);
|
||||
QCOMPARE(w3.jsonElementToDb(), QStringLiteral("element = objElement.value(QStringLiteral(\"int\"))"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberWrapperJsonPrePostFallback()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
// json pre/post strings win over the variant pre/post strings when set
|
||||
MemberInformation mi = makeMember(QStringLiteral("tbl_a"), QStringLiteral("foo"));
|
||||
mi.cpp.toVariantPre = QStringLiteral("T(");
|
||||
mi.cpp.toVariantPost = QStringLiteral(")");
|
||||
mi.cpp.fromVariantPre2 = QStringLiteral("V(");
|
||||
mi.cpp.fromVariantPost = QStringLiteral(")");
|
||||
mi.cpp.toJsonPre = QStringLiteral("J(");
|
||||
mi.cpp.toJsonPost = QStringLiteral(")");
|
||||
mi.cpp.fromJsonPre2 = QStringLiteral("F(");
|
||||
mi.cpp.fromJsonPost = QStringLiteral(")");
|
||||
|
||||
MemberInformationWrapper w(mi, &parent);
|
||||
QCOMPARE(w.cppToQvPre(), QStringLiteral("T("));
|
||||
QCOMPARE(w.cppToQvPost(), QStringLiteral(")"));
|
||||
QCOMPARE(w.cppFromQvPre1(), QString());
|
||||
QCOMPARE(w.cppFromQvPre2(), QStringLiteral("V("));
|
||||
QCOMPARE(w.cppFromQvPost(), QStringLiteral(")"));
|
||||
QCOMPARE(w.cppToJsonPre(), QStringLiteral("J("));
|
||||
QCOMPARE(w.cppToJsonPost(), QStringLiteral(")"));
|
||||
QCOMPARE(w.cppFromJsonPre2(), QStringLiteral("F("));
|
||||
QCOMPARE(w.cppFromJsonPost(), QStringLiteral(")"));
|
||||
|
||||
// without json strings the variant strings are used
|
||||
MemberInformation mi2 = makeMember(QStringLiteral("tbl_a"), QStringLiteral("bar"));
|
||||
mi2.cpp.toVariantPre = QStringLiteral("T(");
|
||||
mi2.cpp.fromVariantPre2 = QStringLiteral("V(");
|
||||
MemberInformationWrapper w2(mi2, &parent);
|
||||
QCOMPARE(w2.cppToJsonPre(), QStringLiteral("T("));
|
||||
QCOMPARE(w2.cppToJsonPost(), QString());
|
||||
QCOMPARE(w2.cppFromJsonPre2(), QStringLiteral("V("));
|
||||
QCOMPARE(w2.cppFromJsonPost(), QString());
|
||||
}
|
||||
|
||||
void codegen_unit_test::memberListWrapperFilters()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
MemberInformation cppMi = makeMember(QStringLiteral("t"), QStringLiteral("m1"));
|
||||
cppMi.cpp.usedForCppStruct = true;
|
||||
MemberInformation jsonMi = makeMember(QStringLiteral("t"), QStringLiteral("m2"));
|
||||
jsonMi.cpp.usedForJson = true;
|
||||
MemberInformation fkMi = makeMember(QStringLiteral("t"), QStringLiteral("m3"));
|
||||
fkMi.db.isForeignKey = true;
|
||||
MemberInformation selMi = makeMember(QStringLiteral("t"), QStringLiteral("m4"));
|
||||
selMi.db.generateSelectRemove = true;
|
||||
MemberInformation viewMi = makeMember(QStringLiteral("t"), QStringLiteral("m5"));
|
||||
viewMi.db.usedForView = true;
|
||||
MemberInformation keyMi = makeMember(QStringLiteral("t"), QStringLiteral("m6"));
|
||||
keyMi.isDdsKey = true;
|
||||
MemberInformation pkMi = makeMember(QStringLiteral("t"), QStringLiteral("m7"));
|
||||
pkMi.db.isPrimaryKey = true;
|
||||
|
||||
QVector<MemberInformation> members;
|
||||
members = {cppMi, jsonMi, fkMi, selMi, viewMi, keyMi, pkMi};
|
||||
|
||||
MemberInformationListWrapper w(QStringLiteral("members"), members, &parent);
|
||||
QCOMPARE(w.member().size(), 7);
|
||||
QCOMPARE(w.cppMember().size(), 1);
|
||||
QCOMPARE(w.jsonMember().size(), 1);
|
||||
QCOMPARE(w.fkMember().size(), 1);
|
||||
QCOMPARE(w.selectRemoveMember().size(), 1);
|
||||
QCOMPARE(w.viewMember().size(), 1);
|
||||
|
||||
MemberInformationWrapper* key = w.ddsKey();
|
||||
QVERIFY(key != nullptr);
|
||||
QCOMPARE(key->cppMemberName(), QStringLiteral("m6"));
|
||||
MemberInformationWrapper* pk = w.primaryKey();
|
||||
QVERIFY(pk != nullptr);
|
||||
QCOMPARE(pk->cppMemberName(), QStringLiteral("m7"));
|
||||
|
||||
// an empty list has no key and no primary key
|
||||
MemberInformationListWrapper w2(QStringLiteral("empty"), QVector<MemberInformation>(), &parent);
|
||||
QCOMPARE(w2.member().size(), 0);
|
||||
QVERIFY(w2.ddsKey() == nullptr);
|
||||
QVERIFY(w2.primaryKey() == nullptr);
|
||||
}
|
||||
|
||||
void codegen_unit_test::dataTopicWrapper()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
CompleteTopicTypeInformation ctti;
|
||||
ctti.name = QStringLiteral("Topic");
|
||||
ctti.serviceName = QStringLiteral("Svc");
|
||||
|
||||
DataTopicWrapper w(ctti, &parent);
|
||||
QCOMPARE(w.name(), QStringLiteral("Topic"));
|
||||
QCOMPARE(w.serviceName(), QStringLiteral("Svc"));
|
||||
QCOMPARE(w.type(), QStringLiteral("Topic"));
|
||||
QCOMPARE(w.typeFq(), QStringLiteral("Svc::Topic"));
|
||||
QCOMPARE(w.dtsImpl(), true);
|
||||
QCOMPARE(w.dtcImpl(), true);
|
||||
QCOMPARE(w.routeToClient(), true);
|
||||
QCOMPARE(w.routeToServer(), false);
|
||||
QCOMPARE(w.routeInReplay(), true);
|
||||
QCOMPARE(w.isAutoSubscribe(), true);
|
||||
QCOMPARE(w.recordTopic(), false);
|
||||
QVERIFY(w.recTable().isNull());
|
||||
QVERIFY(w.key().isNull());
|
||||
QCOMPARE(w.hasTrColumns(), false);
|
||||
|
||||
// an already service-qualified name is not qualified again
|
||||
ctti.name = QStringLiteral("Svc::Topic");
|
||||
DataTopicWrapper w2(ctti, &parent);
|
||||
QCOMPARE(w2.typeFq(), QStringLiteral("Svc::Topic"));
|
||||
|
||||
// without a service name the type is returned as-is
|
||||
CompleteTopicTypeInformation ctti3;
|
||||
ctti3.name = QStringLiteral("Topic3");
|
||||
DataTopicWrapper w3(ctti3, &parent);
|
||||
QCOMPARE(w3.typeFq(), QStringLiteral("Topic3"));
|
||||
|
||||
// key column and translatable columns
|
||||
QSharedPointer<DataTypeInfo> intType(new TestDataTypeInfo(QStringLiteral("int"), QStringLiteral("int"), true));
|
||||
IdlData keyData;
|
||||
keyData.name = QStringLiteral("id");
|
||||
keyData.dataTypeInfo = intType;
|
||||
w.setKeyColumnData(keyData);
|
||||
QVERIFY(!w.key().isNull());
|
||||
QCOMPARE(w.key().value<IdlDataWrapper*>()->name(), QStringLiteral("id"));
|
||||
|
||||
IdlData trData;
|
||||
trData.name = QStringLiteral("tr1");
|
||||
trData.dataTypeInfo = intType;
|
||||
QVector<IdlData> trColumns;
|
||||
trColumns = {trData};
|
||||
w.setTrColumns(trColumns);
|
||||
QCOMPARE(w.hasTrColumns(), true);
|
||||
QCOMPARE(w.trColumns().size(), 1);
|
||||
}
|
||||
|
||||
void codegen_unit_test::functionTopicWrapper()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
FunctionTopic ft;
|
||||
ft.name = QStringLiteral("MyFunc");
|
||||
ft.requestTopicName = QStringLiteral("ReqT");
|
||||
ft.replyTopicName = QStringLiteral("RepT");
|
||||
ft.replyTopicType = QStringLiteral("Core::FunctionReply");
|
||||
ft.routeInReplay = true;
|
||||
|
||||
CompleteTopicTypeInformation req;
|
||||
req.name = QStringLiteral("ReqType");
|
||||
CompleteTopicTypeInformation rep;
|
||||
rep.name = QStringLiteral("RepType");
|
||||
|
||||
FunctionTopicWrapper w(ft, req, rep, &parent);
|
||||
QCOMPARE(w.name(), QStringLiteral("MyFunc"));
|
||||
QCOMPARE(w.onReply(), QStringLiteral("onRepT"));
|
||||
QCOMPARE(w.onRequest(), QStringLiteral("onReqT"));
|
||||
QCOMPARE(w.onError(), QStringLiteral("onMyFuncError"));
|
||||
QCOMPARE(w.isCoreFt(), true);
|
||||
QCOMPARE(w.routeInReplay(), true);
|
||||
QCOMPARE(w.reply(), QStringLiteral("RepT"));
|
||||
QCOMPARE(w.replyFlc(), QStringLiteral("repT"));
|
||||
QCOMPARE(w.replyHandler(), QStringLiteral("RepTHandler"));
|
||||
QCOMPARE(w.replyHandlerFlc(), QStringLiteral("repTHandler"));
|
||||
QCOMPARE(w.replyType(), QStringLiteral("RepType"));
|
||||
QCOMPARE(w.replyTypeFro(), QStringLiteral("Core::FunctionReplyObject"));
|
||||
QCOMPARE(w.request(), QStringLiteral("ReqT"));
|
||||
QCOMPARE(w.requestFlc(), QStringLiteral("reqT"));
|
||||
QCOMPARE(w.requestHandler(), QStringLiteral("ReqTHandler"));
|
||||
QCOMPARE(w.requestHandlerFlc(), QStringLiteral("reqTHandler"));
|
||||
QCOMPARE(w.requestType(), QStringLiteral("ReqType"));
|
||||
QCOMPARE(w.replyHandlerHash(), QString());
|
||||
|
||||
// a non-core reply type keeps its own type name
|
||||
ft.replyTopicType = QStringLiteral("SomeReply");
|
||||
FunctionTopicWrapper w2(ft, req, rep, &parent);
|
||||
QCOMPARE(w2.isCoreFt(), false);
|
||||
QCOMPARE(w2.replyTypeFro(), QStringLiteral("RepType"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::idlWrappers()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
QSharedPointer<DataTypeInfo> podType(new TestDataTypeInfo(QStringLiteral("int"), QStringLiteral("int"), true));
|
||||
QSharedPointer<DataTypeInfo> structType(new TestDataTypeInfo(QStringLiteral("Svc::MyStruct"),
|
||||
QStringLiteral("Svc::MyStruct"), false));
|
||||
|
||||
IdlData idl;
|
||||
idl.name = QStringLiteral("foo");
|
||||
idl.isKey = true;
|
||||
idl.isOptional = false;
|
||||
idl.dataTypeInfo = podType;
|
||||
|
||||
IdlDataWrapper w(idl, &parent);
|
||||
QCOMPARE(w.name(), QStringLiteral("foo"));
|
||||
QCOMPARE(w.type(), QStringLiteral("int"));
|
||||
QCOMPARE(w.cppType(), QStringLiteral("int"));
|
||||
// POD types are passed by value (no const reference)
|
||||
QCOMPARE(w.cppTypeCr(), QStringLiteral("int"));
|
||||
QCOMPARE(w.isKey(), QStringLiteral(" \t //@key"));
|
||||
QCOMPARE(w.isOptional(), QString());
|
||||
QCOMPARE(w.nameGetter(), QStringLiteral("foo"));
|
||||
|
||||
IdlData opt;
|
||||
opt.name = QStringLiteral("bar");
|
||||
opt.isOptional = true;
|
||||
opt.dataTypeInfo = structType;
|
||||
|
||||
IdlDataWrapper w2(opt, &parent);
|
||||
QCOMPARE(w2.cppTypeCr(), QStringLiteral("const Svc::MyStruct &"));
|
||||
QCOMPARE(w2.isKey(), QString());
|
||||
QCOMPARE(w2.isOptional(), QStringLiteral(" \t //@Optional"));
|
||||
// optional members are unwrapped via .get()
|
||||
QCOMPARE(w2.nameGetter(), QStringLiteral("bar.get()"));
|
||||
|
||||
// IdlStructWrapper
|
||||
CompleteTopicTypeInformation ctti;
|
||||
ctti.name = QStringLiteral("MyStruct");
|
||||
ctti.comment = QStringLiteral("struct comment");
|
||||
ctti.baseValueType = QStringLiteral("BaseType");
|
||||
IdlData m1;
|
||||
m1.name = QStringLiteral("a");
|
||||
m1.dataTypeInfo = podType;
|
||||
IdlData m2;
|
||||
m2.name = QStringLiteral("b");
|
||||
m2.dataTypeInfo = structType;
|
||||
ctti.members = {m1, m2};
|
||||
|
||||
IdlStructWrapper sw(ctti, &parent);
|
||||
QCOMPARE(sw.name(), QStringLiteral("MyStruct"));
|
||||
QCOMPARE(sw.comment(), QStringLiteral("struct comment"));
|
||||
QCOMPARE(sw.baseValueType(), QStringLiteral("BaseType"));
|
||||
// the default extensibility maps to the same string with and without RTI
|
||||
QCOMPARE(sw.extensibility(), QStringLiteral("EXTENSIBLE_EXTENSIBILITY"));
|
||||
QCOMPARE(sw.member().size(), 2);
|
||||
QCOMPARE(sw.member().value(0).value<IdlDataWrapper*>()->name(), QStringLiteral("a"));
|
||||
}
|
||||
|
||||
void codegen_unit_test::dbStructWrapper()
|
||||
{
|
||||
QObject parent;
|
||||
|
||||
QSharedPointer<TableInformation> tableA(new TableInformation());
|
||||
tableA->name = QStringLiteral("tbl_a");
|
||||
QSharedPointer<TableInformation> tableB(new TableInformation());
|
||||
tableB->name = QStringLiteral("tbl_b");
|
||||
|
||||
CompleteStructInformation csi;
|
||||
csi.structName = QStringLiteral("MyStruct");
|
||||
csi.viewName = QStringLiteral("my_view");
|
||||
csi.tables = {tableA, tableB};
|
||||
csi.primaryTable = tableA;
|
||||
|
||||
MemberInformation pkA = makeMember(QStringLiteral("tbl_a"), QStringLiteral("a_id"));
|
||||
pkA.db.isPrimaryKey = true;
|
||||
pkA.db.usedForInsert = true;
|
||||
MemberInformation nameA = makeMember(QStringLiteral("tbl_a"), QStringLiteral("a_name"));
|
||||
nameA.db.usedForInsert = true;
|
||||
MemberInformation fkB = makeMember(QStringLiteral("tbl_b"), QStringLiteral("b_a_id"));
|
||||
fkB.db.usedForInsert = true;
|
||||
fkB.fk = QSharedPointer<ForeignKeyInformation>(new ForeignKeyInformation());
|
||||
fkB.fk->destination.tableName = QStringLiteral("tbl_a");
|
||||
|
||||
csi.member = {pkA, nameA, fkB};
|
||||
|
||||
DbNotifcationInformation notif1;
|
||||
notif1.methodName = QStringLiteral("onInsertA");
|
||||
DbNotifcationInformation notif2;
|
||||
notif2.methodName = QStringLiteral("onDeleteA");
|
||||
csi.dbNotifications = {notif1, notif2};
|
||||
|
||||
DbStructWrapper w(csi, &parent);
|
||||
QCOMPARE(w.name(), QStringLiteral("MyStruct"));
|
||||
QCOMPARE(w.member().size(), 3);
|
||||
QCOMPARE(w.deleteWithoutPkAllowed(), false); // two tables
|
||||
QCOMPARE(w.viewTableName(), QStringLiteral("my_view")); // multi-table -> view name
|
||||
MemberInformationWrapper* pk = w.primaryKey();
|
||||
QVERIFY(pk != nullptr);
|
||||
QCOMPARE(pk->dbColumnName(), QStringLiteral("a_id"));
|
||||
QCOMPARE(w.dbNotifications().size(), 2);
|
||||
QCOMPARE(w.dbNotifications().value(0).toString(), QStringLiteral("onInsertA"));
|
||||
QCOMPARE(w.dbNotifications().value(1).toString(), QStringLiteral("onDeleteA"));
|
||||
|
||||
// a single-table struct uses the primary table name as view name
|
||||
CompleteStructInformation csiSingle;
|
||||
csiSingle.structName = QStringLiteral("Single");
|
||||
csiSingle.viewName = QStringLiteral("unused");
|
||||
csiSingle.tables = {tableA};
|
||||
csiSingle.primaryTable = tableA;
|
||||
MemberInformation soloPk = makeMember(QStringLiteral("tbl_a"), QStringLiteral("a_id"));
|
||||
soloPk.db.isPrimaryKey = true;
|
||||
soloPk.db.hasSequence = true;
|
||||
soloPk.db.usedForInsert = true;
|
||||
csiSingle.member = {soloPk};
|
||||
|
||||
DbStructWrapper wSingle(csiSingle, &parent);
|
||||
QCOMPARE(wSingle.deleteWithoutPkAllowed(), true);
|
||||
QCOMPARE(wSingle.viewTableName(), QStringLiteral("tbl_a"));
|
||||
|
||||
// insertStructures groups the members per table (two tables)
|
||||
QVariantList structures = w.insertStructures();
|
||||
QCOMPARE(structures.size(), 2);
|
||||
TableWrapper* twA = structures.value(0).value<TableWrapper*>();
|
||||
TableWrapper* twB = structures.value(1).value<TableWrapper*>();
|
||||
QVERIFY(twA != nullptr);
|
||||
QVERIFY(twB != nullptr);
|
||||
QCOMPARE(twA->name(), QStringLiteral("tbl_a"));
|
||||
QCOMPARE(twA->member().size(), 2);
|
||||
QVERIFY(twA->primaryKey() != nullptr);
|
||||
QCOMPARE(twA->primaryKey()->dbColumnName(), QStringLiteral("a_id"));
|
||||
// first of several tables carries the base id
|
||||
QCOMPARE(twA->baseId()->dbColumnName(), QStringLiteral("a_id"));
|
||||
// the FK member pointing to tbl_a becomes its serial member
|
||||
QCOMPARE(twA->serialMember()->dbColumnName(), QStringLiteral("b_a_id"));
|
||||
QCOMPARE(twB->name(), QStringLiteral("tbl_b"));
|
||||
QCOMPARE(twB->member().size(), 1);
|
||||
QVERIFY(twB->primaryKey() == nullptr);
|
||||
QVERIFY(twB->baseId() == nullptr);
|
||||
QVERIFY(twB->serialMember() == nullptr);
|
||||
|
||||
// insertStructures for a single table with a sequence primary key
|
||||
QVariantList single = wSingle.insertStructures();
|
||||
QCOMPARE(single.size(), 1);
|
||||
TableWrapper* twSolo = single.value(0).value<TableWrapper*>();
|
||||
QVERIFY(twSolo != nullptr);
|
||||
QCOMPARE(twSolo->name(), QStringLiteral("tbl_a"));
|
||||
QVERIFY(twSolo->primaryKey() != nullptr);
|
||||
// the sequence PK is used as the serial member
|
||||
QCOMPARE(twSolo->serialMember()->dbColumnName(), QStringLiteral("a_id"));
|
||||
QVERIFY(twSolo->baseId() == nullptr);
|
||||
}
|
||||
|
||||
void codegen_unit_test::codegenBaseDataClasses()
|
||||
{
|
||||
// StructBuilder::ServiceTypeStruct
|
||||
StructBuilder::ServiceTypeStruct sts;
|
||||
sts.service = QStringLiteral("Svc");
|
||||
sts.type = QStringLiteral("Type");
|
||||
sts.bIsSequence = true;
|
||||
QCOMPARE(sts.isExternalType(), true);
|
||||
QCOMPARE(sts.isSequence(), true);
|
||||
QCOMPARE(sts.fqType(), QStringLiteral("Svc::Type"));
|
||||
|
||||
StructBuilder::ServiceTypeStruct local;
|
||||
local.type = QStringLiteral("Plain");
|
||||
QCOMPARE(local.isExternalType(), false);
|
||||
|
||||
// SqlGenerator::SqlStatements::isEmpty only considers the DROP statements
|
||||
SqlGenerator::SqlStatements stmts;
|
||||
QCOMPARE(stmts.isEmpty(), true);
|
||||
stmts.createTables.append(QStringLiteral("CREATE TABLE t()"));
|
||||
QCOMPARE(stmts.isEmpty(), true);
|
||||
stmts.dropTables.append(QStringLiteral("DROP TABLE t"));
|
||||
QCOMPARE(stmts.isEmpty(), false);
|
||||
|
||||
// EnumInformation
|
||||
EnumInformation ei;
|
||||
QCOMPARE(ei.isValid(), false);
|
||||
ei.enumName = QStringLiteral("E");
|
||||
QCOMPARE(ei.isValid(), true);
|
||||
|
||||
// MemberInformation isEmpty / toString (CSV format)
|
||||
MemberInformation mi;
|
||||
QCOMPARE(mi.isEmpty(), true);
|
||||
mi.ea.table = QStringLiteral("t");
|
||||
mi.ea.column = QStringLiteral("c");
|
||||
mi.ea.type = QStringLiteral("int");
|
||||
mi.db.table = QStringLiteral("t");
|
||||
mi.db.columnName = QStringLiteral("c");
|
||||
mi.cpp.memberName = QStringLiteral("c");
|
||||
QCOMPARE(mi.isEmpty(), false);
|
||||
QCOMPARE(mi.toString(),
|
||||
QStringLiteral("t;c;int;t;c;;false;false;false;false;;c;false;false;;;;;;"));
|
||||
|
||||
// CompleteTopicTypeInformation
|
||||
CompleteTopicTypeInformation ctti;
|
||||
QCOMPARE(ctti.isValid(), false); // no name
|
||||
ctti.name = QStringLiteral("S");
|
||||
QCOMPARE(ctti.isValid(), false); // still no members
|
||||
ctti.baseValueType = QStringLiteral("B");
|
||||
QCOMPARE(ctti.hasBaseValue(), true);
|
||||
IdlData m;
|
||||
m.name = QStringLiteral("x");
|
||||
m.dataTypeInfo = QSharedPointer<DataTypeInfo>(new TestDataTypeInfo(QStringLiteral("int"),
|
||||
QStringLiteral("int"), true));
|
||||
ctti.members = {m};
|
||||
QCOMPARE(ctti.isValid(), true);
|
||||
|
||||
// CompleteTableInformation / CompleteStructInformation
|
||||
CompleteTableInformation cti;
|
||||
QCOMPARE(cti.isEmpty(), true);
|
||||
MemberInformation col;
|
||||
col.ea.column = QStringLiteral("c1");
|
||||
cti.columns = {col};
|
||||
QCOMPARE(cti.isEmpty(), false);
|
||||
|
||||
CompleteStructInformation csi;
|
||||
QCOMPARE(csi.isEmpty(), true);
|
||||
QSharedPointer<TableInformation> ti(new TableInformation());
|
||||
ti->name = QStringLiteral("t");
|
||||
csi.primaryTable = ti;
|
||||
QCOMPARE(csi.isEmpty(), false);
|
||||
}
|
||||
|
||||
QTEST_MAIN(codegen_unit_test)
|
||||
#include "codegen_unit_test.moc"
|
||||
@@ -0,0 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(CommonUnitTestsPlaceholder)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
option(BUILD_SMOKE_TESTS "Build QtTest smoke tests" OFF)
|
||||
option(BUILD_GTEST_SMOKE "Build GTest smoke tests" OFF)
|
||||
|
||||
add_subdirectory(mocks)
|
||||
|
||||
if(BUILD_SMOKE_TESTS)
|
||||
if(NOT TARGET Qt6::Test)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Test)
|
||||
endif()
|
||||
add_executable(coordTransTests_smoke coordTransTests_smoke.cpp)
|
||||
target_link_libraries(coordTransTests_smoke PRIVATE Qt6::Test MockCoordTrans::MockCoordTrans)
|
||||
add_test(NAME coordTransTests_smoke COMMAND coordTransTests_smoke)
|
||||
endif()
|
||||
|
||||
if(BUILD_GTEST_SMOKE)
|
||||
find_package(GTest QUIET)
|
||||
if(NOT GTest_FOUND)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG release-1.12.1
|
||||
)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif()
|
||||
add_executable(coordTransTests_gtest coordTransTests_gtest.cpp)
|
||||
target_link_libraries(coordTransTests_gtest PRIVATE GTest::gtest_main MockCoordTrans::MockCoordTrans)
|
||||
add_test(NAME coordTransTests_gtest COMMAND coordTransTests_gtest)
|
||||
endif()
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Stub GTest smoke test for coordTrans
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(CoordTransSmokeTest, StubTest) {
|
||||
EXPECT_TRUE(true);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Stub QtTest smoke test for coordTrans
|
||||
#include <QTest>
|
||||
|
||||
class CoordTransSmokeTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void stubTest() {
|
||||
QVERIFY(true);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(CoordTransSmokeTest)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
add_library(MockCoordTrans STATIC MockCoordTrans.h)
|
||||
target_include_directories(MockCoordTrans PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Export header-only semantics by providing an interface target
|
||||
add_library(MockCoordTrans::MockCoordTrans ALIAS MockCoordTrans)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// MockCoordTrans.h - Stub mock for CoordTrans functionality
|
||||
// This is a placeholder to allow builds without full CoordTrans implementation
|
||||
|
||||
#ifndef MOCK_COORD_TRANS_H
|
||||
#define MOCK_COORD_TRANS_H
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QPointF>
|
||||
|
||||
// Minimal stub to satisfy linker requirements
|
||||
namespace LtsCoordTrans {
|
||||
// Placeholder types
|
||||
struct UtmCoordinate {
|
||||
static UtmCoordinate fromMgrs(const QString&) { return UtmCoordinate(); }
|
||||
QPointF geographic() const { return QPointF(); }
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MOCK_COORD_TRANS_H
|
||||
@@ -0,0 +1,88 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
# Created by Mike Wille, SLSE, 2016-10-24
|
||||
#
|
||||
|
||||
|
||||
# LtsAppDescriptor (lib)
|
||||
# Make ${Qt6_Widgets} optional
|
||||
if(QT6_GUI_AVAILABLE AND TARGET Qt6::Widgets)
|
||||
set(Qt6_Widgets ${Qt6_Widgets})
|
||||
else()
|
||||
set(Qt6_Widgets "")
|
||||
endif()
|
||||
|
||||
|
||||
SET(LtsAppDescriptor_SRCS
|
||||
lts_app_descriptor.cpp
|
||||
lts_app_descriptor.h
|
||||
)
|
||||
|
||||
NGL_ADD_LIBRARY2(WS LtsAppDescriptor SHARED ${LtsAppDescriptor_SRCS})
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptor
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptor
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
NGL_INSTALL_TARGET(LtsAppDescriptor)
|
||||
|
||||
|
||||
|
||||
# LtsAppDescriptorChecker
|
||||
SET(LtsAppDescriptorChecker_SRCS
|
||||
checker/main.cpp
|
||||
)
|
||||
|
||||
NGL_ADD_EXECUTABLE(WS LtsAppDescriptorChecker ${LtsAppDescriptorChecker_SRCS})
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorChecker
|
||||
PRIVATE
|
||||
LtsAppDescriptor
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# LtsAppDescriptorEditor => Hat niemand benutzt, wird keiner benutzen, also wird der bis auf Weiteres auch nicht an die neue Syntax angepasst.
|
||||
#SET(LtsAppDescriptorEditor_SRCS
|
||||
# lts_app_descriptor_editor.cpp
|
||||
# lts_app_descriptor_editor.h
|
||||
#)
|
||||
|
||||
#NGL_WRAP_UI(LtsAppDescriptorEditor_SRCS
|
||||
# lts_app_descriptor_editor.ui
|
||||
#)
|
||||
|
||||
#NGL_ADD_LIBRARY2(WS NO_TRANSLATION LtsAppDescriptorEditor SHARED ${LtsAppDescriptorEditor_SRCS})
|
||||
|
||||
#NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptorEditor
|
||||
# PUBLIC
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
#)
|
||||
|
||||
if(QT6_GUI_AVAILABLE)
|
||||
#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditor
|
||||
# PUBLIC
|
||||
# Qt6::Core
|
||||
# ${Qt6_Widgets}
|
||||
# LtsAppDescriptor
|
||||
#)
|
||||
endif()
|
||||
|
||||
#NGL_INSTALL_TARGET(LtsAppDescriptorEditor) #not visible for end-user
|
||||
|
||||
## Executable:
|
||||
#NGL_ADD_EXECUTABLE(WS NO_TRANSLATION LtsAppDescriptorEditorApp main.cpp)
|
||||
#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditorApp LtsAppDescriptorEditor)
|
||||
@@ -0,0 +1,118 @@
|
||||
#include <LtsAppDescriptor/lts_app_descriptor.h>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QDirIterator>
|
||||
#include <QtCore/QFile>
|
||||
#include <QFileInfoList>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QRegularExpressionMatch>
|
||||
#include <QtCore/QTextStream>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
const QStringList args(a.arguments());
|
||||
|
||||
if (args.count() != 3)
|
||||
{
|
||||
printf("usage: %s <current source directory> <apps.json>\n", args.at(0).toStdString().c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
const QString currentSrcDir(a.arguments().at(1));
|
||||
|
||||
const QString appsJsonFilePath(QStringLiteral("%1/%2").arg(currentSrcDir, a.arguments().at(2)));
|
||||
QFile f(appsJsonFilePath);
|
||||
|
||||
if (!f.open(QFile::ReadOnly))
|
||||
{
|
||||
printf("LtsAppDescriptorChecker: cannot open apps.json at %s: %s\n", appsJsonFilePath.toStdString().c_str(),
|
||||
f.errorString().toStdString().c_str());
|
||||
return -2;
|
||||
}
|
||||
|
||||
printf("LtsAppDescriptorChecker: checking %s\n", appsJsonFilePath.toStdString().c_str());
|
||||
|
||||
QStringList lastErrors;
|
||||
QStringList dummy1;
|
||||
QString dummy2;
|
||||
LtsAppDescriptor::AppDescriptionVector adv =
|
||||
LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(f.readAll(), lastErrors, dummy1, dummy2);
|
||||
|
||||
if (!lastErrors.isEmpty())
|
||||
{
|
||||
printf("LtsAppDescriptorChecker: apps.json at %s invalid:\n%s\n", appsJsonFilePath.toStdString().c_str(),
|
||||
lastErrors.join(QStringLiteral("\r\n\r\n")).toStdString().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
QFileInfoList guiPluginFiles;
|
||||
QDirIterator it(QStringLiteral("%1/../../Plugins/").arg(currentSrcDir), QDirIterator::Subdirectories);
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
QString filename(it.next());
|
||||
QFileInfo file(filename);
|
||||
|
||||
if (file.isDir())
|
||||
continue;
|
||||
|
||||
if (file.fileName().endsWith(QStringLiteral("_gui_plugin.h"), Qt::CaseInsensitive))
|
||||
guiPluginFiles.append(file);
|
||||
}
|
||||
|
||||
QStringList filenames;
|
||||
filenames.reserve(guiPluginFiles.size());
|
||||
for (const QFileInfo& fi : std::as_const(guiPluginFiles))
|
||||
{
|
||||
filenames += fi.absoluteFilePath();
|
||||
}
|
||||
|
||||
QStringList guiPluginIIDs;
|
||||
for (const QFileInfo& fi : std::as_const(guiPluginFiles))
|
||||
{
|
||||
QFile f2(fi.absoluteFilePath());
|
||||
|
||||
if (!f2.open(QFile::ReadOnly))
|
||||
continue;
|
||||
|
||||
QTextStream ts(&f2);
|
||||
while (!ts.atEnd())
|
||||
{
|
||||
QString line(ts.readLine().trimmed().simplified());
|
||||
static QString iidIdentifier(QStringLiteral("Q_PLUGIN_METADATA"));
|
||||
|
||||
if (line.contains(iidIdentifier))
|
||||
{
|
||||
QRegularExpression re(QStringLiteral("(?<=gui:3:).*(?=\"\\))"));
|
||||
QRegularExpressionMatch match = re.match(line);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
QString matched = match.captured(0);
|
||||
|
||||
guiPluginIIDs += matched;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
for (const LtsAppDescriptor::AppDescription& appdesc : std::as_const(adv))
|
||||
{
|
||||
for (const LtsAppDescriptor::PluginInfo& pi : std::as_const(appdesc.pluginInfos))
|
||||
{
|
||||
if (!guiPluginIIDs.contains(pi.iid))
|
||||
{
|
||||
printf("LtsAppDescriptorChecker: apps.json at '%s' uses unknown IID '%s'\n",
|
||||
appsJsonFilePath.toStdString().c_str(), pi.iid.toStdString().c_str());
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !ok;
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
|
||||
|
||||
#include "lts_app_descriptor.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QtCore/QJsonValue>
|
||||
|
||||
namespace LtsAppDescriptor
|
||||
{
|
||||
static void addOptionToPlugin(PluginInfo& pluginInfo, const PluginInfo::Flag& flag)
|
||||
{
|
||||
pluginInfo.flags |= flag;
|
||||
}
|
||||
|
||||
static void addOptionToPlugin(QVector<PluginInfo>& pluginInfos, const PluginInfo::Flag& flag, const QString& pluginName)
|
||||
{
|
||||
for (PluginInfo& pi : pluginInfos)
|
||||
{
|
||||
if (pi.iid == pluginName)
|
||||
{
|
||||
addOptionToPlugin(pi, flag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PluginInfo pi;
|
||||
pi.iid = pluginName;
|
||||
pi.type = PluginInfo::PluginType::GUI;
|
||||
addOptionToPlugin(pi, flag);
|
||||
pluginInfos.append(pi);
|
||||
}
|
||||
|
||||
static bool addOptionsToPlugin(AppDescription& appDesc, const QString& optionIdentifier,
|
||||
const QStringList& ignoredPlugins, const QJsonObject& optionsObject,
|
||||
const PluginInfo::Flag flag, QStringList& lastErrors, bool atLeastOnePluginNeeded)
|
||||
{
|
||||
bool hasPlugins = false;
|
||||
auto optionsObjectIt = optionsObject.constFind(optionIdentifier);
|
||||
if (optionsObjectIt != optionsObject.constEnd())
|
||||
{
|
||||
const QJsonArray plugins((*optionsObjectIt).toObject()[QStringLiteral("Plugins")].toArray());
|
||||
|
||||
for (const auto& pluginElem : plugins)
|
||||
{
|
||||
const QString pluginName = pluginElem.toString();
|
||||
if (!ignoredPlugins.contains(pluginName))
|
||||
{
|
||||
addOptionToPlugin(appDesc.pluginInfos, flag, pluginName);
|
||||
hasPlugins = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (atLeastOnePluginNeeded && !hasPlugins)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("No plugins specified for '%1' option for app '%2'")
|
||||
.arg(optionIdentifier, appDesc.appName));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
App LtsAppDescriptor::appFromInteger(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case int(App::Login):
|
||||
case int(App::Situation):
|
||||
case int(App::ResourceManager):
|
||||
case int(App::PlanEditor):
|
||||
case int(App::BuildingViewer):
|
||||
case int(App::OC):
|
||||
case int(App::OrgChart):
|
||||
case int(App::Scripts):
|
||||
|
||||
case int(App::Snapshot):
|
||||
case int(App::SystemMonitor):
|
||||
case int(App::Timeline):
|
||||
case int(App::SymbolEditor):
|
||||
case int(App::ScriptEditorEssentialsView):
|
||||
case int(App::BaseDownloader):
|
||||
case int(App::OnlineHelp):
|
||||
case int(App::ThpSituationReplay):
|
||||
case int(App::SituationTablet):
|
||||
case int(App::SilhouetteEditor):
|
||||
case int(App::Shootback):
|
||||
case int(App::SAAs):
|
||||
return static_cast<App>(i);
|
||||
}
|
||||
Q_ASSERT_X(false, "LtsAppDescriptor::appFromInteger", qPrintable(QStringLiteral("Unknown app id: %1").arg(i)));
|
||||
return App::Login;
|
||||
}
|
||||
|
||||
AppDescriptionVector LtsAppDescriptor::getAppDescriptionVector(const QByteArray& jsonDocument, QStringList& lastErrors,
|
||||
const QStringList& ignoredPlugins,
|
||||
QString& loginPluginIid)
|
||||
{
|
||||
|
||||
AppDescriptionVector appDescriptions;
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument appsDoc(QJsonDocument::fromJson(jsonDocument, &parseError));
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
|
||||
int row = 0;
|
||||
int column = 0;
|
||||
QChar x = QLatin1Char('\n');
|
||||
|
||||
for (int i = 0; i < jsonDocument.size(); i++)
|
||||
{
|
||||
if (i == parseError.offset)
|
||||
break;
|
||||
|
||||
if (QChar::fromLatin1(jsonDocument[i]) == x)
|
||||
{
|
||||
row++;
|
||||
column = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
column++;
|
||||
}
|
||||
}
|
||||
|
||||
lastErrors.append(QCoreApplication::tr("JSON: %1<br>Row: %2, Column: %3")
|
||||
.arg(parseError.errorString())
|
||||
.arg(row + 1)
|
||||
.arg(column + 1));
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
const QJsonObject root(appsDoc.object());
|
||||
|
||||
loginPluginIid = root[QStringLiteral("LoginPlugin")].toString();
|
||||
|
||||
const QJsonArray appArray(root[QStringLiteral("Apps")].toArray());
|
||||
if (appArray.isEmpty())
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("Invalid json: Could not find 'Apps' array"));
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
for (const auto& appElem : appArray)
|
||||
{
|
||||
AppDescription appDesc;
|
||||
const QJsonObject appObject = appElem.toObject();
|
||||
|
||||
appDesc.id = appFromInteger(appObject.value(QStringLiteral("Id")).toInt(-1));
|
||||
if (appDesc.id == App::Login)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("App '%1': no App Id or invalid App Id given").arg(appDesc.appName));
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
appDesc.appName = appObject.value(QStringLiteral("AppName")).toString();
|
||||
|
||||
const QJsonValue valueActive = appObject.value(QStringLiteral("Active"));
|
||||
if (!valueActive.isUndefined())
|
||||
{
|
||||
if (valueActive.type() != QJsonValue::Type::Bool)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("App '%1': value App.Active is not a bool (%2)")
|
||||
.arg(appDesc.appName)
|
||||
.arg((int)valueActive.type()));
|
||||
return appDescriptions;
|
||||
}
|
||||
}
|
||||
appDesc.active = valueActive.toBool(false);
|
||||
|
||||
const QJsonValue valueVisible = appObject.value(QStringLiteral("Visible"));
|
||||
if (!valueVisible.isUndefined() && valueVisible.type() != QJsonValue::Type::Bool)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("App '%1': value App.Visible is not a bool (%2)")
|
||||
.arg(appDesc.appName)
|
||||
.arg((int)valueVisible.type()));
|
||||
return appDescriptions;
|
||||
}
|
||||
appDesc.visible = valueVisible.toBool(true);
|
||||
|
||||
const QJsonArray array(appObject.value(QStringLiteral("ACLs")).toArray());
|
||||
for (const QJsonValue& value : array)
|
||||
appDesc.requiredAcls.append(value.toInt());
|
||||
|
||||
const QJsonObject optionsObject(appObject.value(QStringLiteral("Options")).toObject());
|
||||
|
||||
addOptionsToPlugin(appDesc, QStringLiteral("Payloads"), ignoredPlugins, optionsObject,
|
||||
PluginInfo::Flag::PAYLOAD, lastErrors, false);
|
||||
addOptionsToPlugin(appDesc, QStringLiteral("Actions"), ignoredPlugins, optionsObject, PluginInfo::Flag::ACTIONS,
|
||||
lastErrors, true);
|
||||
addOptionsToPlugin(appDesc, QStringLiteral("Main"), ignoredPlugins, optionsObject, PluginInfo::Flag::MAIN,
|
||||
lastErrors, false);
|
||||
|
||||
appDescriptions.append(appDesc);
|
||||
}
|
||||
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
} // namespace LtsAppDescriptor
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* App Descriptor - Class to modify App Descriptor JSON Documents
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*
|
||||
* Created by Mike Wille, SLSE, 2016-10-24
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <QStringList>
|
||||
|
||||
#ifndef LTSAPPDESCRIPTOR_EXPORT
|
||||
# ifdef BUILD_STATIC_LIBRARIES
|
||||
# define LTSAPPDESCRIPTOR_EXPORT
|
||||
# else
|
||||
# if defined(MAKE_LTSAPPDESCRIPTOR_LIB)
|
||||
# define LTSAPPDESCRIPTOR_EXPORT Q_DECL_EXPORT
|
||||
# else
|
||||
# define LTSAPPDESCRIPTOR_EXPORT Q_DECL_IMPORT
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
namespace LtsAppDescriptor
|
||||
{
|
||||
|
||||
enum class App
|
||||
{
|
||||
Login = -1,
|
||||
Situation = 0,
|
||||
ResourceManager = 1,
|
||||
PlanEditor = 2,
|
||||
BuildingViewer = 3,
|
||||
OC = 4,
|
||||
OrgChart = 5,
|
||||
Scripts = 6,
|
||||
// merged into Scripts (aka. ScriptEditor) ScriptPlayer = 7,
|
||||
Snapshot = 8,
|
||||
SystemMonitor = 9,
|
||||
Timeline = 10,
|
||||
SymbolEditor = 11,
|
||||
ScriptEditorEssentialsView = 12,
|
||||
BaseDownloader = 13,
|
||||
OnlineHelp = 14,
|
||||
ThpSituationReplay = 15,
|
||||
SituationTablet = 17,
|
||||
SilhouetteEditor = 18, // for L400P2
|
||||
Shootback = 19, // for L400P2
|
||||
SAAs = 20, //Schnittstelle Adapter Adler
|
||||
};
|
||||
|
||||
struct PluginInfo
|
||||
{
|
||||
enum class PluginType
|
||||
{
|
||||
UNKNOWN,
|
||||
DATA,
|
||||
GUI,
|
||||
GUI_ELEMENT,
|
||||
};
|
||||
|
||||
enum class Flag
|
||||
{
|
||||
UNKNOWN = 1 << 0,
|
||||
ACTIONS = 1 << 1,
|
||||
MAIN = 1 << 2,
|
||||
PAYLOAD = 1 << 3,
|
||||
OPTION = 1 << 4,
|
||||
};
|
||||
|
||||
PluginType type = PluginType::UNKNOWN;
|
||||
QString iid;
|
||||
QFlags<Flag> flags;
|
||||
QStringList options;
|
||||
};
|
||||
|
||||
struct AppDescription
|
||||
{
|
||||
App id = App::Login;
|
||||
bool active = false;
|
||||
bool visible = true;
|
||||
QString appName;
|
||||
QVector<int> requiredAcls;
|
||||
QVector<PluginInfo> pluginInfos;
|
||||
|
||||
bool isValid() const { return !appName.isEmpty(); }
|
||||
};
|
||||
|
||||
typedef QVector<AppDescription> AppDescriptionVector;
|
||||
|
||||
|
||||
namespace LtsAppDescriptor // TODO: make qobject class singleton - for tr
|
||||
{
|
||||
LTSAPPDESCRIPTOR_EXPORT App appFromInteger(int i);
|
||||
LTSAPPDESCRIPTOR_EXPORT AppDescriptionVector getAppDescriptionVector(const QByteArray &jsonDocument, QStringList &lastErrors,
|
||||
const QStringList &ignoredPlugins, QString &loginPluginIid);
|
||||
}
|
||||
|
||||
} // namespace LtsAppDescriptor
|
||||
+536
@@ -0,0 +1,536 @@
|
||||
#include "lts_app_descriptor_editor.h"
|
||||
|
||||
#include "ui_lts_app_descriptor_editor.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QStringListModel>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QListView>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
LtsAppDescriptorEditor::LtsAppDescriptorEditor(QWidget* parent) : QWidget(parent), m_ui(new Ui_LtsAppDescriptionEditor)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_vector = LtsAppDescriptor::AppDescriptionVector();
|
||||
m_appIndex = QModelIndex();
|
||||
m_pluginIndex = QModelIndex();
|
||||
m_fileName = QString();
|
||||
m_changed = false;
|
||||
|
||||
connect(m_ui->lvApps, &QListView::clicked, this, &LtsAppDescriptorEditor::onAppsClicked);
|
||||
connect(m_ui->lvPlugins, &QListView::clicked, this, &LtsAppDescriptorEditor::onPluginsClicked);
|
||||
connect(m_ui->lePluginType, &QLineEdit::editingFinished, this, &LtsAppDescriptorEditor::pluginTypeChanged);
|
||||
connect(m_ui->pbAddApp, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onAppAddClicked);
|
||||
connect(m_ui->pbAddPlugin, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onPluginAddClicked);
|
||||
connect(m_ui->pbDeleteApp, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onAppDeleteClicked);
|
||||
connect(m_ui->pbDeletePlugin, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onPluginDeletedClicked);
|
||||
connect(m_ui->pbUpdate, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onUpdateClicked);
|
||||
connect(m_ui->pbNewPlugin, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onNewpluginClicked);
|
||||
connect(m_ui->pbNewApp, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onNewAppClicked);
|
||||
connect(m_ui->lePluginName, &QLineEdit::textChanged, this, &LtsAppDescriptorEditor::enableNewPlugin);
|
||||
connect(m_ui->lePluginType, &QLineEdit::textChanged, this, &LtsAppDescriptorEditor::enableNewPlugin);
|
||||
|
||||
m_ui->gbPlugins->setEnabled(false);
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
m_ui->pbDeleteApp->setEnabled(false);
|
||||
m_ui->pbAddPlugin->setEnabled(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
m_ui->pbNewApp->setEnabled(false);
|
||||
QTimer::singleShot(0, this, &LtsAppDescriptorEditor::getAppDescriptionVector);
|
||||
}
|
||||
|
||||
LtsAppDescriptorEditor::~LtsAppDescriptorEditor()
|
||||
{
|
||||
checkSave();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::getAppDescriptionVector()
|
||||
{
|
||||
m_fileName = QFileDialog::getOpenFileName(
|
||||
this, tr("Open JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"),
|
||||
tr("JSON File (*.json)"));
|
||||
if (m_fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file;
|
||||
file.setFileName(m_fileName);
|
||||
file.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QString val(QString::fromUtf8(file.readAll()));
|
||||
file.close();
|
||||
|
||||
QStringList errors;
|
||||
m_vector = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(val.toUtf8(), errors);
|
||||
|
||||
updateAppModel();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onAppsClicked(const QModelIndex& index)
|
||||
{
|
||||
m_ui->gbPlugins->setEnabled(true);
|
||||
m_ui->pbDeleteApp->setEnabled(true);
|
||||
m_ui->pbAddPlugin->setEnabled(true);
|
||||
m_ui->pbNewApp->setEnabled(true);
|
||||
m_appIndex = index;
|
||||
|
||||
QStringList pluginList;
|
||||
Q_FOREACH (const LtsAppDescriptor::PluginInfo& reqPlugin, m_vector.at(m_appIndex.row()).requiredPlugins)
|
||||
{
|
||||
LtsAppDescriptor::PluginInfo::PluginType pluginType = reqPlugin.type;
|
||||
QString type;
|
||||
if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA)
|
||||
type = QLatin1String("data");
|
||||
else if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI)
|
||||
type = QLatin1String("gui");
|
||||
else
|
||||
type = QLatin1String("unknown");
|
||||
|
||||
if (type == QLatin1String("data"))
|
||||
pluginList << QString(type + QLatin1String(": ") + reqPlugin.iid);
|
||||
else
|
||||
{
|
||||
QString strFlags;
|
||||
QFlags<LtsAppDescriptor::PluginInfo::Flag> flags = reqPlugin.flags;
|
||||
|
||||
bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS);
|
||||
if (actionsFlag)
|
||||
strFlags.append(QLatin1String("actions "));
|
||||
bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN);
|
||||
if (mainFlag)
|
||||
strFlags.append(QLatin1String("main "));
|
||||
bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD);
|
||||
if (payloadFlag)
|
||||
strFlags.append(QLatin1String("payload "));
|
||||
bool optionalFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::OPTION);
|
||||
if (optionalFlag)
|
||||
strFlags.append(QLatin1String("optional "));
|
||||
|
||||
pluginList << QString(type + QLatin1String(": ") + reqPlugin.iid + QLatin1String(" [ ") + strFlags +
|
||||
QLatin1String("]"));
|
||||
}
|
||||
}
|
||||
updatePluginModel(pluginList);
|
||||
|
||||
m_ui->chbAppActive->setChecked(m_vector.at(m_appIndex.row()).active);
|
||||
m_ui->leAppName->setText(m_vector.at(m_appIndex.row()).appName);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onPluginsClicked(const QModelIndex& index)
|
||||
{
|
||||
m_pluginIndex = index;
|
||||
m_ui->pbDeletePlugin->setEnabled(true);
|
||||
m_ui->pbUpdate->setEnabled(true);
|
||||
m_ui->pbNewPlugin->setEnabled(true);
|
||||
|
||||
const LtsAppDescriptor::PluginInfo plugin = m_vector.at(m_appIndex.row()).requiredPlugins.at(m_pluginIndex.row());
|
||||
QString pluginName = plugin.iid;
|
||||
m_ui->lePluginName->setText(pluginName);
|
||||
|
||||
LtsAppDescriptor::PluginInfo::PluginType pluginType = plugin.type;
|
||||
QString type;
|
||||
if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA)
|
||||
type = QLatin1String("data");
|
||||
else if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI)
|
||||
type = QLatin1String("gui");
|
||||
else
|
||||
type = QLatin1String("unknown");
|
||||
m_ui->lePluginType->setText(type);
|
||||
|
||||
if (type == QLatin1String("gui"))
|
||||
{
|
||||
m_ui->chbActions->setEnabled(true);
|
||||
m_ui->chbMain->setEnabled(true);
|
||||
m_ui->chbPayload->setEnabled(true);
|
||||
|
||||
QFlags<LtsAppDescriptor::PluginInfo::Flag> flags = plugin.flags;
|
||||
bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS);
|
||||
m_ui->chbActions->setChecked(actionsFlag);
|
||||
bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN);
|
||||
m_ui->chbMain->setChecked(mainFlag);
|
||||
bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD);
|
||||
m_ui->chbPayload->setChecked(payloadFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbActions->setEnabled(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbMain->setEnabled(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->chbPayload->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::pluginTypeChanged()
|
||||
{
|
||||
if (m_ui->lePluginType->text().isEmpty())
|
||||
return;
|
||||
|
||||
if (m_ui->lePluginType->text() == QLatin1String("gui"))
|
||||
{
|
||||
m_ui->chbActions->setEnabled(true);
|
||||
m_ui->chbMain->setEnabled(true);
|
||||
m_ui->chbPayload->setEnabled(true);
|
||||
}
|
||||
else if (m_ui->lePluginType->text() == QLatin1String("data"))
|
||||
{
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbActions->setEnabled(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbMain->setEnabled(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->chbPayload->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Information, tr("Error"),
|
||||
tr("Plugin Type must either be \"gui\" or \"data\"."));
|
||||
msgBox.exec();
|
||||
m_ui->lePluginType->setText(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onAppAddClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (m_ui->leAppName->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The App needs to have a name."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList appList;
|
||||
Q_FOREACH (const LtsAppDescriptor::AppDescription& appDesc, m_vector)
|
||||
{
|
||||
appList << appDesc.appName;
|
||||
}
|
||||
|
||||
if (appList.contains(m_ui->leAppName->text(), Qt::CaseInsensitive))
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"),
|
||||
tr("There already exists an app named \"%1\".").arg(m_ui->leAppName->text()));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
LtsAppDescriptor::AppDescription newApp;
|
||||
newApp.appName = m_ui->leAppName->text();
|
||||
newApp.active = m_ui->chbAppActive->isChecked();
|
||||
m_vector.append(newApp);
|
||||
|
||||
updateAppModel();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onPluginAddClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (m_ui->lePluginType->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to contain a type."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
if (m_ui->lePluginName->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to have a name."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList pluginList;
|
||||
Q_FOREACH (const LtsAppDescriptor::PluginInfo& pluginDesc, m_vector.at(m_appIndex.row()).requiredPlugins)
|
||||
{
|
||||
pluginList << pluginDesc.iid;
|
||||
}
|
||||
|
||||
if (pluginList.contains(m_ui->lePluginName->text(), Qt::CaseInsensitive))
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"),
|
||||
tr("There already exists an plugin named \"%1\".").arg(m_ui->lePluginName->text()));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
LtsAppDescriptor::PluginInfo newPlugin;
|
||||
|
||||
newPlugin.iid = m_ui->lePluginName->text();
|
||||
|
||||
if (m_ui->lePluginType->text() == QLatin1String("gui"))
|
||||
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::GUI;
|
||||
else if (m_ui->lePluginType->text() == QLatin1String("data"))
|
||||
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA;
|
||||
else
|
||||
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN;
|
||||
|
||||
if (m_ui->chbActions->isChecked())
|
||||
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS;
|
||||
if (m_ui->chbMain->isChecked())
|
||||
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN;
|
||||
if (m_ui->chbPayload->isChecked())
|
||||
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD;
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins.append(newPlugin);
|
||||
|
||||
onAppsClicked(m_appIndex);
|
||||
onNewpluginClicked();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onAppDeleteClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (!m_appIndex.isValid())
|
||||
return;
|
||||
|
||||
QMessageBox msgBox(tr("Warning"),
|
||||
tr("The App \n\"%1\" \nwill be removed. Continue?").arg(m_appIndex.data().toString()),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::Default,
|
||||
QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::No)
|
||||
return;
|
||||
|
||||
m_vector.remove(m_appIndex.row());
|
||||
|
||||
updateAppModel();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onPluginDeletedClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (!m_pluginIndex.isValid())
|
||||
return;
|
||||
|
||||
QMessageBox msgBox(tr("Warning"),
|
||||
tr("The Plugin \n\"%1\" \nwill be removed. Continue?").arg(m_pluginIndex.data().toString()),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::Default,
|
||||
QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::No)
|
||||
return;
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins.remove(m_pluginIndex.row());
|
||||
m_pluginIndex = QModelIndex();
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
|
||||
onAppsClicked(m_appIndex);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onUpdateClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (m_ui->lePluginType->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to contain a type."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
if (m_ui->lePluginName->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to have a name."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].iid = m_ui->lePluginName->text();
|
||||
|
||||
if (m_ui->lePluginType->text() == QLatin1String("gui"))
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type =
|
||||
LtsAppDescriptor::PluginInfo::PluginType::GUI;
|
||||
else if (m_ui->lePluginType->text() == QLatin1String("data"))
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type =
|
||||
LtsAppDescriptor::PluginInfo::PluginType::DATA;
|
||||
else
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type =
|
||||
LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN;
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags =
|
||||
QFlags<LtsAppDescriptor::PluginInfo::Flag>();
|
||||
|
||||
if (m_ui->chbActions->isChecked())
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |=
|
||||
LtsAppDescriptor::PluginInfo::Flag::ACTIONS;
|
||||
if (m_ui->chbMain->isChecked())
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |=
|
||||
LtsAppDescriptor::PluginInfo::Flag::MAIN;
|
||||
if (m_ui->chbPayload->isChecked())
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |=
|
||||
LtsAppDescriptor::PluginInfo::Flag::PAYLOAD;
|
||||
|
||||
onAppsClicked(m_appIndex);
|
||||
onNewpluginClicked();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onNewpluginClicked()
|
||||
{
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->chbActions->setEnabled(true);
|
||||
m_ui->chbMain->setEnabled(true);
|
||||
m_ui->chbPayload->setEnabled(true);
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
|
||||
if (m_pluginIndex.isValid())
|
||||
m_ui->lvPlugins->selectionModel()->select(m_pluginIndex, QItemSelectionModel::Deselect);
|
||||
m_pluginIndex = QModelIndex();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onNewAppClicked()
|
||||
{
|
||||
updatePluginModel(QStringList());
|
||||
|
||||
m_ui->gbPlugins->setEnabled(false);
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
m_ui->pbDeleteApp->setEnabled(false);
|
||||
m_ui->pbAddPlugin->setEnabled(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
m_ui->leAppName->setText(QString());
|
||||
m_ui->chbAppActive->setChecked(false);
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->pbNewApp->setEnabled(false);
|
||||
|
||||
if (m_appIndex.isValid())
|
||||
m_ui->lvApps->selectionModel()->select(m_appIndex, QItemSelectionModel::Deselect);
|
||||
m_appIndex = QModelIndex();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::updateAppModel()
|
||||
{
|
||||
onNewAppClicked();
|
||||
QStringList appList;
|
||||
Q_FOREACH (const LtsAppDescriptor::AppDescription& appDesc, m_vector)
|
||||
{
|
||||
appList << appDesc.appName;
|
||||
}
|
||||
|
||||
if (!m_ui->lvApps->model())
|
||||
m_ui->lvApps->setModel(new QStringListModel(this));
|
||||
QStringListModel* m = qobject_cast<QStringListModel*>(m_ui->lvApps->model());
|
||||
if (!m)
|
||||
return;
|
||||
m->setStringList(appList);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::updatePluginModel(const QStringList& list)
|
||||
{
|
||||
if (!m_ui->lvPlugins->model())
|
||||
m_ui->lvPlugins->setModel(new QStringListModel(this));
|
||||
QStringListModel* m = qobject_cast<QStringListModel*>(m_ui->lvPlugins->model());
|
||||
if (!m)
|
||||
return;
|
||||
m->setStringList(list);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::checkSave()
|
||||
{
|
||||
if (m_changed)
|
||||
{
|
||||
QMessageBox msgBox(tr("Warning"), tr("The Data has been modified.\nDo you want to save your changes?"),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::Default,
|
||||
QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::Yes)
|
||||
onSaveAsClicked();
|
||||
}
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onSaveAsClicked()
|
||||
{
|
||||
QString fileName = QFileDialog::getSaveFileName(
|
||||
this, tr("Save JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"),
|
||||
tr("JSON File (*.json)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QByteArray jsonByteArray;
|
||||
jsonByteArray = LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector);
|
||||
|
||||
QFile f(fileName);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write(jsonByteArray);
|
||||
f.close();
|
||||
|
||||
m_changed = false;
|
||||
|
||||
QMessageBox msgBox(QMessageBox::Information, tr("Success"), tr("Your file has been saved."));
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onSaveClicked()
|
||||
{
|
||||
const QByteArray jsonByteArray(LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector));
|
||||
|
||||
QFile f(m_fileName);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write(jsonByteArray);
|
||||
f.close();
|
||||
|
||||
m_changed = false;
|
||||
|
||||
QMessageBox msgBox(QMessageBox::Information, tr("Success"), tr("Your file has been saved."));
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onOpenClicked()
|
||||
{
|
||||
checkSave();
|
||||
getAppDescriptionVector();
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onNewClicked()
|
||||
{
|
||||
checkSave();
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->chbAppActive->setChecked(false);
|
||||
m_ui->leAppName->setText(QString());
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->gbPlugins->setEnabled(false);
|
||||
|
||||
m_vector = LtsAppDescriptor::AppDescriptionVector();
|
||||
|
||||
updateAppModel();
|
||||
updatePluginModel(QStringList());
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::enableNewPlugin()
|
||||
{
|
||||
m_ui->pbNewPlugin->setEnabled(true);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onQuitClicked()
|
||||
{
|
||||
QApplication::quit();
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "lts_app_descriptor.h"
|
||||
|
||||
#include <QtCore/QModelIndex>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#ifndef LTSAPPDESCRIPTOREDITOR_EXPORT
|
||||
#ifdef BUILD_STATIC_LIBRARIES
|
||||
#define LTSAPPDESCRIPTOREDITOR_EXPORT
|
||||
#else
|
||||
#if defined(MAKE_LTSAPPDESCRIPTOREDITOR_LIB)
|
||||
#define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class Ui_LtsAppDescriptionEditor;
|
||||
|
||||
class LTSAPPDESCRIPTOREDITOR_EXPORT LtsAppDescriptorEditor : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LtsAppDescriptorEditor(QWidget* parent = 0);
|
||||
~LtsAppDescriptorEditor();
|
||||
|
||||
private:
|
||||
Ui_LtsAppDescriptionEditor* m_ui;
|
||||
LtsAppDescriptor::AppDescriptionVector m_vector;
|
||||
QModelIndex m_appIndex;
|
||||
QModelIndex m_pluginIndex;
|
||||
|
||||
QString m_fileName;
|
||||
|
||||
bool m_changed;
|
||||
|
||||
void getAppDescriptionVector();
|
||||
void setupMainWindow();
|
||||
void updateAppModel();
|
||||
void updatePluginModel(const QStringList& list);
|
||||
void checkSave();
|
||||
|
||||
private slots:
|
||||
void onAppsClicked(const QModelIndex& index);
|
||||
void onPluginsClicked(const QModelIndex& index);
|
||||
void pluginTypeChanged();
|
||||
void onAppAddClicked();
|
||||
void onPluginAddClicked();
|
||||
void onAppDeleteClicked();
|
||||
void onPluginDeletedClicked();
|
||||
void onUpdateClicked();
|
||||
void onNewpluginClicked();
|
||||
void onNewAppClicked();
|
||||
void enableNewPlugin();
|
||||
|
||||
public slots:
|
||||
void onSaveClicked();
|
||||
void onSaveAsClicked();
|
||||
void onOpenClicked();
|
||||
void onNewClicked();
|
||||
void onQuitClicked();
|
||||
};
|
||||
Loaded 100 of 2235 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user