From 4c80b8cb0db3f6c487671404bab7edc057cf64c8 Mon Sep 17 00:00:00 2001 From: Aliou DIAITE Date: Mon, 3 Oct 2022 17:13:51 +0200 Subject: [PATCH] 68-164 - Activation used LDevice and deactivation unused LDevice (#166) Update documentation Remove LNode IEDName Activation used LDevice and deactivation unused LDevice (#166) Signed-off-by: Aliou DIAITE Signed-off-by: Samir Romdhani Signed-off-by: massifben <105049157+massifben@users.noreply.github.com> --- .github/workflows/automate_javadoc.yml | 68 +++ .reuse/dep5 | 18 +- LICENSES/CC-BY-4.0.txt | 156 +++++++ README.md | 19 + docs/404.html | 9 + docs/HOME.md | 206 +++++++++ docs/QUICK_START.md | 114 +++++ docs/SCD_GENERATION.md | 59 +++ docs/_config.yml | 29 ++ docs/_includes/git-logo.html | 13 + docs/_includes/head.html | 33 ++ docs/_includes/sidebar.html | 41 ++ docs/_layouts/default.html | 18 + docs/_layouts/page.html | 19 + .../CoMPAS_Technical_Charter_2020-06-07.pdf | Bin 0 -> 108434 bytes {doc => docs}/compas-sct.md | 2 +- {doc => docs}/drawio/SCD Process ver06.drawio | 0 docs/drawio/SCD_file_generation.drawio | 1 + {doc => docs}/drawio/compas-sct.drawio | 0 docs/examples/example-app/pom.xml | 108 +++++ .../compas/sct/examples/SctAppExample.java | 39 ++ .../sct/examples/SctAppExampleTest.java | 47 ++ docs/github_pages/_config.yml | 28 ++ .../images/PackageDiagram-CompasSCT.png | Bin docs/images/SequenceDiagram-CompasSCT.png | Bin 0 -> 91278 bytes docs/public/LFEnergy-slack.svg | 1 + docs/public/LFEnergy-slack.svg.license | 3 + .../apple-touch-icon-144-precomposed.png | Bin 0 -> 588 bytes ...ple-touch-icon-144-precomposed.png.license | 3 + docs/public/compas-horizontal-color.svg | 113 +++++ .../compas-horizontal-color.svg.license | 3 + docs/public/css/gitbutton.css | 85 ++++ docs/public/css/hyde.css | 255 ++++++++++ docs/public/css/poole.css | 436 ++++++++++++++++++ docs/public/css/syntax.css | 71 +++ docs/public/favicon.ico | Bin 0 -> 4286 bytes docs/public/favicon.ico.license | 3 + pom.xml | 40 +- sct-app/pom.xml | 1 - .../compas/sct/app/SclAutomationService.java | 22 + .../lfenergy/compas/sct/app/package-info.java | 8 + .../sct/commons/dto/ConnectedApDTO.java | 22 + .../compas/sct/commons/dto/ControlBlock.java | 72 ++- .../compas/sct/commons/dto/DaTypeName.java | 57 +++ .../compas/sct/commons/dto/DataSetInfo.java | 45 +- .../compas/sct/commons/dto/DataTypeName.java | 61 ++- .../compas/sct/commons/dto/DoTypeName.java | 33 ++ .../sct/commons/dto/ExtRefBindingInfo.java | 42 +- .../compas/sct/commons/dto/ExtRefInfo.java | 40 +- .../sct/commons/dto/ExtRefSignalInfo.java | 42 +- .../sct/commons/dto/ExtRefSourceInfo.java | 28 ++ .../compas/sct/commons/dto/FCDAInfo.java | 32 ++ .../sct/commons/dto/GooseControlBlock.java | 40 ++ .../compas/sct/commons/dto/HeaderDTO.java | 49 +- .../compas/sct/commons/dto/IedDTO.java | 47 +- .../compas/sct/commons/dto/LDeviceDTO.java | 54 +++ .../compas/sct/commons/dto/LNodeDTO.java | 94 +++- .../compas/sct/commons/dto/LNodeMetaData.java | 16 + .../commons/dto/LNodeMetaDataEmbedder.java | 61 ++- .../sct/commons/dto/LogicalNodeOptions.java | 19 + .../sct/commons/dto/ReportControlBlock.java | 59 ++- .../sct/commons/dto/ResumedDataTemplate.java | 125 +++++ .../sct/commons/dto/SMVControlBlock.java | 49 +- .../compas/sct/commons/dto/SclDTO.java | 42 +- .../compas/sct/commons/dto/SclReport.java | 40 ++ .../compas/sct/commons/dto/SubNetworkDTO.java | 62 ++- .../compas/sct/commons/dto/package-info.java | 10 + .../sct/commons/exception/ScdException.java | 9 + .../sct/commons/scl/LDeviceActivation.java | 122 +++++ .../sct/commons/scl/ObjectReference.java | 25 + .../sct/commons/scl/PrivateService.java | 98 ++++ .../sct/commons/scl/SclElementAdapter.java | 68 ++- .../sct/commons/scl/SclRootAdapter.java | 136 +++++- .../compas/sct/commons/scl/SclService.java | 250 ++++++++++ .../sct/commons/scl/SubstationService.java | 53 ++- .../commons/scl/com/CommunicationAdapter.java | 58 ++- .../commons/scl/com/ConnectedAPAdapter.java | 45 ++ .../commons/scl/com/SubNetworkAdapter.java | 59 ++- .../sct/commons/scl/com/package-info.java | 14 + .../scl/dtt/AbstractDataAttributeAdapter.java | 73 ++- .../scl/dtt/AbstractDataTypeAdapter.java | 30 ++ .../compas/sct/commons/scl/dtt/DAAdapter.java | 52 ++- .../sct/commons/scl/dtt/DATypeAdapter.java | 147 +++++- .../compas/sct/commons/scl/dtt/DOAdapter.java | 54 +++ .../sct/commons/scl/dtt/DOTypeAdapter.java | 174 ++++++- .../scl/dtt/DataTypeTemplateAdapter.java | 186 +++++++- .../sct/commons/scl/dtt/EnumTypeAdapter.java | 52 +++ .../sct/commons/scl/dtt/IDTTComparable.java | 17 + .../sct/commons/scl/dtt/IDataTemplate.java | 16 + .../sct/commons/scl/dtt/LNodeTypeAdapter.java | 111 ++++- .../sct/commons/scl/dtt/package-info.java | 16 + .../sct/commons/scl/header/HeaderAdapter.java | 74 ++- .../sct/commons/scl/header/package-info.java | 14 + .../commons/scl/ied/AbstractDAIAdapter.java | 63 +++ .../commons/scl/ied/AbstractLNAdapter.java | 203 +++++++- .../sct/commons/scl/ied/DAITracker.java | 47 +- .../sct/commons/scl/ied/DOIAdapter.java | 91 ++++ .../commons/scl/ied/IDataParentAdapter.java | 51 ++ .../sct/commons/scl/ied/IEDAdapter.java | 133 +++++- .../sct/commons/scl/ied/LDeviceAdapter.java | 92 ++++ .../sct/commons/scl/ied/LN0Adapter.java | 184 +++++++- .../compas/sct/commons/scl/ied/LNAdapter.java | 90 ++++ .../sct/commons/scl/ied/LNAdapterBuilder.java | 11 + .../sct/commons/scl/ied/RootSDIAdapter.java | 90 ++++ .../sct/commons/scl/ied/SDIAdapter.java | 90 +++- .../sct/commons/scl/ied/package-info.java | 16 + .../compas/sct/commons/scl/package-info.java | 15 + .../sct/commons/scl/sstation/BayAdapter.java | 54 +++ .../commons/scl/sstation/FunctionAdapter.java | 89 ++-- .../commons/scl/sstation/LNodeAdapter.java | 40 ++ .../scl/sstation/SubstationAdapter.java | 75 +++ .../scl/sstation/VoltageLevelAdapter.java | 58 +++ .../commons/scl/sstation/package-info.java | 14 + .../sct/commons/util/CommonConstants.java | 10 + .../compas/sct/commons/util/PrivateEnum.java | 4 + .../compas/sct/commons/util/STValEnum.java | 21 + .../compas/sct/commons/util/Utils.java | 32 ++ .../commons/scl/LDeviceActivationTest.java | 199 ++++++++ .../sct/commons/scl/SclServiceTest.java | 189 ++++++++ .../commons/scl/SubstationServiceTest.java | 115 ----- .../scl/com/CommunicationAdapterTest.java | 12 + .../scl/com/ConnectedAPAdapterTest.java | 21 +- .../scl/com/SubNetworkAdapterTest.java | 20 + .../sct/commons/scl/dtt/BDAAdapterTest.java | 13 + .../sct/commons/scl/dtt/DAAdapterTest.java | 13 + .../commons/scl/dtt/DATypeAdapterTest.java | 12 + .../sct/commons/scl/dtt/DOAdapterTest.java | 35 ++ .../commons/scl/dtt/DOTypeAdapterTest.java | 14 + .../scl/dtt/DataTypeTemplateAdapterTest.java | 12 + .../commons/scl/dtt/EnumTypeAdapterTest.java | 15 + .../commons/scl/dtt/LNodeTypeAdapterTest.java | 13 + .../commons/scl/header/HeaderAdapterTest.java | 21 +- .../sct/commons/scl/ied/DAITrackerTest.java | 2 + .../sct/commons/scl/ied/DOIAdapterTest.java | 27 ++ .../sct/commons/scl/ied/IEDAdapterTest.java | 19 + .../commons/scl/ied/LDeviceAdapterTest.java | 19 + .../sct/commons/scl/ied/LN0AdapterTest.java | 70 +++ .../sct/commons/scl/ied/LNAdapterTest.java | 12 + .../commons/scl/ied/RootSDIAdapterTest.java | 31 ++ .../sct/commons/scl/ied/SDIAdapterTest.java | 17 + .../scl/sstation/FunctionAdapterTest.java | 53 --- .../scl/sstation/SubstationAdapterTest.java | 21 + .../issue68_Test1_LD_STATUS_INACTIVE.scd | 224 +++++++++ .../issue68_Test2_LD_STATUS_INACTIVE.scd | 222 +++++++++ .../issue68_Test_Dai_Not_Updatable.scd | 223 +++++++++ .../issue68_Test_KO_MissingBeh.scd | 114 +++++ .../issue68_Test_KO_MissingLDevicePrivate.scd | 112 +++++ ...Test_KO_MissingLDevicePrivateAttribute.scd | 115 +++++ .../issue68_Test_KO_MissingMod.scd | 114 +++++ .../issue68_Test_LD_STATUS_ACTIVE.scd | 219 +++++++++ .../issue68_Test_LD_STATUS_UNTESTED.scd | 219 +++++++++ .../issue68_Test_Template.scd | 223 +++++++++ sct-coverage/pom.xml | 1 + sct-data/pom.xml | 1 + 154 files changed, 9282 insertions(+), 318 deletions(-) create mode 100644 .github/workflows/automate_javadoc.yml create mode 100644 LICENSES/CC-BY-4.0.txt create mode 100644 docs/404.html create mode 100644 docs/HOME.md create mode 100644 docs/QUICK_START.md create mode 100644 docs/SCD_GENERATION.md create mode 100644 docs/_config.yml create mode 100644 docs/_includes/git-logo.html create mode 100644 docs/_includes/head.html create mode 100644 docs/_includes/sidebar.html create mode 100644 docs/_layouts/default.html create mode 100644 docs/_layouts/page.html create mode 100644 docs/blob-files/CoMPAS_Technical_Charter_2020-06-07.pdf rename {doc => docs}/compas-sct.md (99%) rename {doc => docs}/drawio/SCD Process ver06.drawio (100%) create mode 100644 docs/drawio/SCD_file_generation.drawio rename {doc => docs}/drawio/compas-sct.drawio (100%) create mode 100644 docs/examples/example-app/pom.xml create mode 100644 docs/examples/example-app/src/main/java/org/lfenergy/compas/sct/examples/SctAppExample.java create mode 100644 docs/examples/example-app/src/test/java/org/lfenergy/compas/sct/examples/SctAppExampleTest.java create mode 100644 docs/github_pages/_config.yml rename {doc => docs}/images/PackageDiagram-CompasSCT.png (100%) create mode 100644 docs/images/SequenceDiagram-CompasSCT.png create mode 100644 docs/public/LFEnergy-slack.svg create mode 100644 docs/public/LFEnergy-slack.svg.license create mode 100644 docs/public/apple-touch-icon-144-precomposed.png create mode 100644 docs/public/apple-touch-icon-144-precomposed.png.license create mode 100644 docs/public/compas-horizontal-color.svg create mode 100644 docs/public/compas-horizontal-color.svg.license create mode 100644 docs/public/css/gitbutton.css create mode 100644 docs/public/css/hyde.css create mode 100644 docs/public/css/poole.css create mode 100644 docs/public/css/syntax.css create mode 100644 docs/public/favicon.ico create mode 100644 docs/public/favicon.ico.license create mode 100644 sct-app/src/main/java/org/lfenergy/compas/sct/app/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclReport.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivation.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/package-info.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/STValEnum.java create mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivationTest.java create mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapterTest.java create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd create mode 100644 sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd diff --git a/.github/workflows/automate_javadoc.yml b/.github/workflows/automate_javadoc.yml new file mode 100644 index 000000000..96b5866a0 --- /dev/null +++ b/.github/workflows/automate_javadoc.yml @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2022 RTE FRANCE +# +# SPDX-License-Identifier: Apache-2.0 +name: Continuous Deployment - JavaDocs + +on: + push: + branches: + - 'feat/javadoc' +jobs: + build: + name: Java Docs + runs-on: ubuntu-latest + env: + from_branch: feat/javadoc + target_branch: feat/117_Documentation_SCD_Generation_Process + steps: + # Use develop as base branch to generate javadoc + - name: Setup Environment + uses: actions/checkout@v2 + with: + distribution: 'zulu' + java-version: '11' + ref: ${{ env.target_branch }} + + - name: Create custom Maven Settings.xml + uses: whelk-io/maven-settings-xml-action@v20 + with: + output_file: custom_maven_settings.xml + servers: '[{ "id": "github-packages-compas", "username": "OWNER", "password": "${{ secrets.GITHUB_TOKEN }}" }]' + + - name: Configure Git + run: | + git config --global user.name '${{ secrets.CONFIG_CI_USER_NAME }}' + git config --global user.email '${{ secrets.CONFIG_CI_USER_EMAIL }}' + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY_BOT}} + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Prepare Pull Request branch + run: | + mvn -s custom_maven_settings.xml clean javadoc:aggregate -P javadoc + mkdir -p docs/javadoc + yes | cp -Rf target/site/apidocs/* docs/javadoc/ + git checkout -b ${{ env.from_branch }}-pull-request + + # Note that will fail if branch already exists. + - name: Push Git Branch + uses: ad-m/github-push-action@v0.6.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ env.from_branch }}-pull-request + + # Note that will silently fail if PR already exists. + - name: Create Pull Request + uses: repo-sync/pull-request@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + source_branch: ${{ env.from_branch }}-pull-request + destination_branch: ${{ env.target_branch }} + pr_title: "Docs: Update Java docs repository" + pr_body: "Automatically created from CI workflow" + pr_label: "documentation,javadoc" \ No newline at end of file diff --git a/.reuse/dep5 b/.reuse/dep5 index 6f9971099..25b7adda1 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -3,10 +3,18 @@ Upstream-Name: compas-sct Upstream-Contact: Mohamed SYLLA Source: https://github.com/com-pas/compas-sct -Files: doc/images/* -Copyright: 2021 RTE FRANCE +Files: docs/images/* +Copyright: 2022 RTE FRANCE License: Apache-2.0 -Files: doc/drawio/* -Copyright: 2021 RTE FRANCE -License: Apache-2.0 \ No newline at end of file +Files: docs/drawio/* +Copyright: 2022 RTE FRANCE +License: Apache-2.0 + +Files: docs/blob-files/* +Copyright: 2022 RTE FRANCE +License: Apache-2.0 + +Files: docs/javadoc/* +Copyright: 2022 RTE FRANCE +License: Apache-2.0 diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt new file mode 100644 index 000000000..13ca539f3 --- /dev/null +++ b/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,156 @@ +Creative Commons Attribution 4.0 International + + Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. + +Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. + +Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. + +Section 1 – Definitions. + + a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. + + d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. + + g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. + + i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. + +Section 2 – Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: + + A. reproduce and Share the Licensed Material, in whole or in part; and + + B. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. + + 3. Term. The term of this Public License is specified in Section 6(a). + + 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. + + 5. Downstream recipients. + + A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. + + B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. + + 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). + +b. Other rights. + + 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this Public License. + + 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. + +Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified form), You must: + + A. retain the following if it is supplied by the Licensor with the Licensed Material: + + i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + + v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; + + B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and + + C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. + + 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. + +Section 4 – Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; + + b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. +For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. + +Section 5 – Disclaimer of Warranties and Limitation of Liability. + + a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. + + b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. + + c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. + +Section 6 – Term and Termination. + + a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + + c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. + + d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. + + e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +Section 7 – Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. + +Section 8 – Interpretation. + + a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. + + c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. + + d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. + +Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/README.md b/README.md index 2258900c2..9dd15c6e0 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,28 @@ SPDX-FileCopyrightText: 2021 Alliander N.V. SPDX-License-Identifier: Apache-2.0 --> +[![Maven Build Github Action Status]()](https://github.com/com-pas/compas-sct/actions?query=workflow%3A%22Build+Project%22) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=com-pas_compas-sct&metric=alert_status)](https://sonarcloud.io/dashboard?id=com-pas_compas-sct) [![REUSE status](https://api.reuse.software/badge/github.com/com-pas/compas-sct)](https://api.reuse.software/info/github.com/com-pas/compas-sct) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5925/badge)](https://bestpractices.coreinfrastructure.org/projects/5925) +[![Slack](https://raw.githubusercontent.com/com-pas/compas-architecture/master/public/LFEnergy-slack.svg)](http://lfenergy.slack.com/) # System Configuration Tool (SCT) components +The SCT tool is a library for generating SCD (System Confifuration Description) files based on IEC 61850 standard. +The code is written with Java (POJO) and is based on Chain of Responsability pattern. +It's architecture is modular and is composed by 3 modules (sct-app, sct-commons and sct-data). +The main feature is to generate a SCD file from SSD (Substation Specification Description) and STD (System Template definition) files. ++ ***sct-app*** : is the high level part and contains a service which allows automatic generation of the SCD file. This last +calls sct-commons functions to realize features. +For further use of the SCT tool this part could be used to add end points or other monitoring tools to use efficiently the SCT. +This will allow to dockerize easily the tool for more portal and large usage. ++ ***sct-commons*** : contains implementation of basic elements of SCLin low level methods and functions (middle level methods) to realize needed operations for them in order to allow +easy manipulation of SCL files. ++ ***sct-data*** : module which propose some interfaces to be implemented in order to interact with databases. + +The main use case of the product is generation of SCD file (automatically or manually by calling low level functions). +Perspectives are given to users to implement other use cases in coherence with the standard IEC-61850 as the SCT stands for a library for now. + +For more informations about the project documentation (architecture, code documentation, etc), please refer to [Documentation](https://com-pas.github.io/compas-sct/) Interested in contributing? Please read carefully the [CONTRIBUTING guidelines](https://github.com/com-pas/contributing/blob/master/CONTRIBUTING.md). diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 000000000..e93444cee --- /dev/null +++ b/docs/404.html @@ -0,0 +1,9 @@ + +
+

404: Page not found

+

Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. Head back home to try finding it again.

+
diff --git a/docs/HOME.md b/docs/HOME.md new file mode 100644 index 000000000..fc77382dc --- /dev/null +++ b/docs/HOME.md @@ -0,0 +1,206 @@ + + + +# Contributing to CoMPAS + +First off, thanks for taking the time to contribute! + +The following is a set of guidelines for contributing to the CoMPAS project. These are mostly guidelines, sometimes rules. +Use your best judgment, and feel free to propose changes to this document in a pull request. + +#### Table Of Contents + +[Code of Conduct](#code-of-conduct) + +[License and Developer Certificate of Origin](#license-and-developer-certificate-of-origin) + +[How Can I Contribute?](#how-can-i-contribute) +* [Community refinements](#community-refinements) +* [Reporting Bugs and Suggesting Enhancements](#reporting-bugs-and-suggesting-enhancements) +* [Contributing Code](#contributing-code) +* [Tools to contribute](#tools-to-contribute) +* [Definition of Done](#definition-of-done) +* [Copyright Guidelines](#definition-of-done) +* [How-to begin](#how-to-begin) +* [Copyright and Licensing](#copyright-and-licensing) +* [Github Project Boards](#github-project-boards) + +[Github Pages](#github-pages) + + +## Code of Conduct + +This project applies the [LF Energy Code of Conduct](https://www.lfenergy.org/about/code-of-conduct/). +By participating, you are expected to uphold this code. Please report unacceptable behavior to the project's +Technical Steering Committee [CoMPAS-tsc@lists.lfenergy.org](mailto:CoMPAS-tsc@lists.lfenergy.org). + +## License and Developer Certificate of Origin + +By contributing to the CoMPAS project, you accept and agree to the following terms and conditions for your present and future contributions submitted to CoMPAS. + +All contributions to this project are licensed under the license stipulated at the corresponding sub-repository. +Except where otherwise explicitely indicated, CoMPAS is an open source project licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0/). + +The project requires the use of the [Developer Certificate of Origin (DCO)](https://developercertificate.org/). +The DCO is a legally binding statement asserting that you are you have the right to submit your contribution and to license it under the project's applicable license. + +Contributors sign-off that they adhere to the term of the DCO by adding a ``Signed-off-by`` line to commit messages. The DCO sign-off must be attached to every contribution made by every contributor. + +Here is an example ``Signed-off-by`` line, that indicates the contributor accepts the DCO: + +```` +This is my commit message. + +Signed-off-by: Name Surname +```` +You can write it manually but Git even has a -s command line option to append this automatically to your commit message: +```` +$ git commit -s -m 'This is my commit message' +```` + +Note that checks will be performed during the integration in order to require that commits in a Pull Request contain valid ``Signed-off-by`` lines. + +## How Can I Contribute? + +## Community refinements + +Every monday there is a CoMPAS Community Refinement at 10AM CET. The event is available at the CoMPAS mailing list [calendar](https://lists.lfenergy.org/g/CoMPAS/calendar) and can be subscribed to. + +### Prepared topics + +Every friday before the refinement meeting on Monday, all topics are published on the #compas channel on our [LFEnergy Slack](https://lfenergy.slack.com/). + +### Add your own topics + +Everybody can suggest topics for the refinement. To do this, join the [LFEnergy Slack](https://lfenergy.slack.com/) if not already, and put your topics in the #compas channel. You can just do this by writing a message like "I want to add something to the refinement this monday, namely..." or add a comment to the already prepared topics if available (see [Prepared topics](#prepared-topics)). + +### Reporting Bugs and Suggesting Enhancements + +Bugs and enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue in the repository where it belongs (an issue about the CIM to IEC 61850 mapping belongs in the [CIM Mapping repository](https://github.com/com-pas/compas-cim-mapping) for example) and provide the following information by filling in the template, which is either an issue or a bug. + +When an issue is created, it is automatically being added to the `To do` column of the specific repository. This means it's added, but not yet refined. Every monday at 10AM CET, there is a Community Refinement (see our mailing list [calendar](https://lists.lfenergy.org/g/CoMPAS/calendar), everybody can join) where issues are being discussed that are not refined yet. Your issue is one of them. +Once it's accepted and refined, it goes to the `Ready for development` column and it's ready to be implemented/fixed. + +Before creating bug reports or suggesting enhancement, please **perform a [cursory search](https://github.com/search?q=+is%3Aissue+user%3Acom-pas)** +to see if the problem has already been reported. You can add a search term to the upper left search bar. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. + +When in doubt, ask your question on our [LFEnergy slack](https://lfenergy.slack.com/) in the `#compas` channel. Or you can contact the team directly to talk about your ideas at [CoMPAS-dev@lists.lfenergy.org](mailto:CoMPAS@lists.lfenergy.org). + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +### Contributing Code + +Code Contribution is tracked as [GitHub Pull Requests](https://help.github.com/en/articles/about-pull-requests). +Crafting a good pull request takes time and energy, but we will help as much as we can, but be prepared to follow our iterative process. +The iterative process has several goals: + +- maintain the software quality, +- fix problems that are important to users, +- engage the community in working toward the best possible software features, +- enable a sustainable system for maintainers to review contributions. + +Please follow these steps to have your contribution considered by the maintainers: + +1. Follow all instructions in the [pull requests section](https://com-pas.github.io/contributing/PULL_REQUESTS.html) +2. Follow the [styleguides](https://com-pas.github.io/contributing/STYLEGUIDE.html) +3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing. +5. Request a GitHub review by one of the projects' Committers +6. Follow their instructions or discuss the requested changes. Please don't take criticism personally, it is normal to iterate on this step several times. +7. Repeat step 6 until the pull request is merged! + +Continuous integration is set up to run on all branches automatically and will often report problems, so don't worry about getting everything perfect on the first try +(SonarCloud Analysis is a notorious problem source). Until you add a reviewer, you can trigger as many builds as you want by amending your commits. The status checks enforce the following: + +- All tests in the test suite pass. +- Checkstyle and SonarCloud report no violations. +- The code coverage is high enough (currently about 80%). + +### Tools to contribute + +Continuous integration is setup automatically on all contributions. However, it's faster to iterate locally to fix problems than waiting for the status checks to finish. +There are many tools that can be used to do the verifications that are enforced by all status checks. The most simple and universal tool is maven, but IDE integrations +can be used to get more immediate feedback. Most of the team uses IntelliJ IDEA, but others IDEs can be used, for exemple the Eclipse IDE. + +### Definition of Done + +Before finishing a requirement, you need to check if everything is done for that particular requirement. +For that, we have a Definition of Done; the DoD decides when a requirement is really done. + +Note: A Definition of Done is not a static list. It can be modified any time, if people feel like corrections should be made. + +Current Definition of Done: +- Assumptions of requirements are met. +- Required documentation is done. +- (Software) Requirement is accepted and got a thumbs up from the Maintainer via an accepted Pull Request. +- (Software) The build succeeds without failures. +- (Software) All tests in the test suite pass. +- (Software) Code style checks report no violations. +- (Software) Code coverage is high enough (a minimum of 80% is covered). +- (Software) If applicable, the added Unit Test is written, executed and passed. +- (Software) Security analysis (vulnerability detection) doesn't spot unaddressed issues. + +### How-to begin + +Before you start your coding journey within the CoMPAS project, there are some things we have to talk about. +Some things that will make your start a little easier! +On the [developing](https://com-pas.github.io/contributing/DEVELOPING.html) page information about tooling can be found. + +#### Open Community Calls + +It's good to know that every other monday, we are having a so called Open Community Call. Everyone participating in the CoMPAS project can join +and talk about and ask question about the CoMPAS project. + +When the Open Community Calls are taking place, can be found at the [General CoMPAS mailing list calendar](https://lists.lfenergy.org/g/CoMPAS/calendar). + +The agendas can be found at the [LF Energy wiki](https://wiki.lfenergy.org/display/HOME/CoMPAS+Community+Calls). + +If you have something to add, please add it to the agenda and notify everyone on Slack! + +#### Slack channel + +One of the first important things, is to meet the community. Feel free to introduce yourself by joining the channel 'compas' on [LF Energy Slack](https://slack.lfenergy.org/)! + +The Slack channel is the first communication platform within the CoMPAS project (besides email and the Github platform), so if you need help for example you can use Slack! + +#### Documenting +A good (open source) project requires documentation. We have two places for our documentation + +##### LF Energy Wiki + +LF Energy has it's own [CoMPAS specific Wiki](https://wiki.lfenergy.org/display/HOME/CoMPAS). This is the place for documenation +about CoMPAS in general (like roadmap and the community call agendas). + +#### Architecture and technologies + +For all architecture and technology choices (for example frameworks, build tools, database choices, etcetera), +please check the source code (duh!) and our [CoMPAS Architecture Github Pages](https://com-pas.github.io/compas-architecture/). + +### Copyright and Licensing + +Copyright and license information is done on per-file basis. We use the specification of [REUSE](https://reuse.software/spec/) +to ensure that copyright information of the project is clear and can be analuzed in an automated fashion. + +Every source code repository within CoMPAS has a Github Action for checking against the REUSE specification. + +For more information, check the [Copyright Guidelines](https://com-pas.github.io/contributing/STYLEGUIDE.html#copyright-guidelines) section. + +### Github Project Boards + +For managing the CoMPAS issues created in all the separate repositories, we use the [Projects Board](https://github.com/orgs/com-pas/projects) of Github. +CoMPAS uses 2 different Project Boards: One for [Pull Requests](https://github.com/orgs/com-pas/projects/2) and one for the [Issues](https://github.com/orgs/com-pas/projects/1). + +When creating Pull Requests or Issues, it will automatically create issues or Pull Requests on the Project Boards. +This is done by the Automate Projects Github Action (take a look at the [action from the Data Service](https://github.com/com-pas/compas-core/blob/master/.github/workflows/automate-projects.yml) for example). +Changing the status of Issues / Pull Requests is also handled automatically by the Github Action. + +Issues and Pull Requests can be moved on both the Project Boards and on the boards of the specific repository itself. It synchronizes automatically. + +## Github Pages + +This site is provided as a [github pages site](https://com-pas.github.io/contributing/). +The content is maintained and edited on [Github](https://github.com/com-pas/contributing) in the directory "docs". +Contributors are only allowed to contribute by editing the content on Github and must do so by presenting their modifications as *pull-request* to the community. +The diagrams on this page are created using [DrawIO](https://github.com/jgraph/drawio-desktop/releases) +and follow [Unified Modeling Language (UML)](https://www.omg.org/spec/UML/). +The drawIO design file is available on this site: [/blob-files/CoMPAS.drawio](blob-files/CoMPAS.drawio). +Modification made to UML diagrams on this site must be made in this file and the modified file must be part of the pull request. diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md new file mode 100644 index 000000000..550e5128b --- /dev/null +++ b/docs/QUICK_START.md @@ -0,0 +1,114 @@ + + + +# CoMPAS SCT (Substation Configuration Tool) +## Quick Start +---------------- +This page will help you get started with our Services + +> CoMPAS SCT offers an SCL services related to **IEC 61850 model implementation** + +> Note: scl2007b4 and scl-extension (CoMPAS Core Modules) are fully integrated into CoMPAS SCT Project. + +#### 1. Install CoMPAS SCT +First you need to add following dependencies to the pom.xml of your repository. +```xml + + + org.lfenergy.compas + sct-commons + 0.1.0 + + + + org.lfenergy.compas + sct-app + 0.1.0 + +``` +Actually there are 4 packages available: +- compas-sct +- sct-commons +- sct-app +- sct-data + + +#### 2. Usage +Now that you have your **compas-sct** dependency set, you can start communicating with Commons SCT services. + +**sct-commons** provides a collection of services to help you build SCL files for a range of use cases. +Following services provides needed functions compliant with IEC 61850 : + +1. **SclService** +2. **SubstationService** + +Let's start with a simple **SclService** call + +```java +var scl = SclService.initScl(Optional.empty(), "1.0", "1.0"); +marshaller.marshal(scl.getCurrentElem(), System.out); +``` + +When the command completes, it prints XML output representing the basic +details for **SCL**. Its structure resembles the following: + +```xml + + + + SCD + +
+ +``` + +Nice work! You've successfully sent a request to Commons SCT service for +initialization of SCD file, you can find this example under docs/example/example-app. + +But the QuickStart doesn't end there. +After having the reader of the SCL object. +Developers could realize different operations on SCL file as importing Substation from SSD file or IED from STD file, +updating Binding information, listing all DAIs information or updating them, etc. + +#### 3. More on CoMPAS SCT operations (Automation features) +We can chain together different SCT services : +**SclAutomationService** provides a simple way to learn the Commons service. + +Start it by using existing files of type `SSD` and `STD` and +running the following : + +```java +// ssd : SSD +// std : STD +HeaderDTO headerDTO = new HeaderDTO(UUID.randomUUID(), "1.0", "1.0"); +var scl = SclAutomationService.createSCD(ssd, headerDTO, Set.of(std)); +marshaller.marshal(scl.getCurrentElem(), System.out); +``` +When the command completes, it prints XML output representing completed **SCL** file. +Its structure resembles the following: + +```xml + + + + SCD + +
+ + ... + + + ... + + + ... + + + ... + + +``` +---------------- +> SCD can be created from Commons SCT services +> (such as SclService) or by calling Adapters class (such as SclRootAdapter) or +> by using SclAutomationService. \ No newline at end of file diff --git a/docs/SCD_GENERATION.md b/docs/SCD_GENERATION.md new file mode 100644 index 000000000..0923d43e6 --- /dev/null +++ b/docs/SCD_GENERATION.md @@ -0,0 +1,59 @@ + + + +## Description of SCD file generation from SSD file and a set of STD files + +### Table of Contents + +* [Introduction](#introduction) +* [Modeling of functional specifications](#modeling-of-functional-specifications) + +### Introduction +The automatic generation of SCD file holds on 3 steps which are described below. + +#### Step 1 + +Step1-SCD_Initialisation + +For the SCD file initialisation, see diagram above: + +- Creation of a new empty SCD file with Header ID and version and revision. Then the SCD file is populated by: +- import of the SSD file associated with a Site +- import of the STD files associated with the functions for a given system version. A system version defines a qualified set of ICD. And each ICD describes an IED for an IED version. +- suppression of all Control Blocks, DataSets and all ExtRef @srcXXX attributes +- edition of LNode @iedName attribute +- activation of used LDevice and desactivation of unused LDevice according to Function/LNode contained into /Substation + + +#### Step 2 + +Step2-Binding_in_current_SCD + +For the binding of SCD, see diagram above: + +- Removal of ExtRef @iedName @ldInst..... @daname and @srcXXX attributes for all inactive ExtRef or for all Extref which have inactive source or target. +- for each active or untested ExtRef.desc with an existing Extref.iedName attribute and without compas:Flow@CriteriaAssociationID Private attribute, copy the right /IED.name into compas:Flow@ExtRefiedName and into Extref.iedName. + + +#### Step 3 + +Step3-DataSet_and_CB_creation_then_ExtRef_update_in_current_SCD + +For the process of DataSet and Control Block creation and then Extref update, see diagram above: + +- Extraction of all ExtRef attributes and properties +- keep only Extref signals which are: + - Active or untested + - AND external to their IED + - AND ExtRef binding attributes are existing and populated + - AND having DA@fc="ST" OR "MX" +- Then group the lines by ExtRef@pServT and @iedName and @ldInst and FlowKind property (and DA@fc for Report only) +- Each group of lines is used to create a new DataSet and Control Block +- Then for each ExtRef signal, create and populate ExtRef @srcXXX attributes + +### Modeling of functional specifications +#### Sequence diagram +The sequence diagram below summarize the process + +![img_2.png](images/SequenceDiagram-CompasSCT.png) + diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..4107b375d --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,29 @@ +#-- SPDX-FileCopyrightText: 2022 RTE FRANCE +# -- -- +# SPDX-License-Identifier: Apache-2.0 + +# Setup +title: CoMPAS SCT +tagline: 'CoMPAS project documentation' + +paginate: 5 + +# Custom vars +version: 0.1.0 + +github: + repo: https://github.com/com-pas/compas-sct + +github_username: com-pas +git_repo: compas-sct +git_branch: develop + +baseurl: /compas-sct +contributingUrl: /contributing + +defaults: + - + scope: + path: "HOME.md" + values: + permalink: "/" \ No newline at end of file diff --git a/docs/_includes/git-logo.html b/docs/_includes/git-logo.html new file mode 100644 index 000000000..9b0a7366c --- /dev/null +++ b/docs/_includes/git-logo.html @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/docs/_includes/head.html b/docs/_includes/head.html new file mode 100644 index 000000000..d36f3d2e2 --- /dev/null +++ b/docs/_includes/head.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + {% if page.name == "HOME.md" %} + {{ site.title }} · {{ site.tagline }} + {% else %} + {{ page.title }} · {{ site.title }} + {% endif %} + + + + + + + + + + + + + diff --git a/docs/_includes/sidebar.html b/docs/_includes/sidebar.html new file mode 100644 index 000000000..15493b8dd --- /dev/null +++ b/docs/_includes/sidebar.html @@ -0,0 +1,41 @@ + + diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 000000000..20098e961 --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,18 @@ + + + + + {% include head.html %} + + + {% include sidebar.html %} + +
+ {{ content }} +
+ + diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html new file mode 100644 index 000000000..185c0b21f --- /dev/null +++ b/docs/_layouts/page.html @@ -0,0 +1,19 @@ +--- +layout: default +--- + + + + diff --git a/docs/blob-files/CoMPAS_Technical_Charter_2020-06-07.pdf b/docs/blob-files/CoMPAS_Technical_Charter_2020-06-07.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7c2d4f0d1c2f48d00aa33eb0a4cf868ffb579115 GIT binary patch literal 108434 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ⅈX-DLwbR)y%m| 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

`, + * or to a parent if there are multiple elements to show. + */ + +.message { + margin-bottom: 1rem; + padding: 1rem; + color: #717171; + background-color: #f9f9f9; +} + + +/* + * Container + * + * Center the page content. + */ + +.container { + max-width: 38rem; + padding-left: 1rem; + padding-right: 1rem; + margin-left: auto; + margin-right: auto; +} + + +/* + * Masthead + * + * Super small header above the content for site name and short description. + */ + +.masthead { + padding-top: 1rem; + padding-bottom: 1rem; + margin-bottom: 3rem; +} +.masthead-title { + margin-top: 0; + margin-bottom: 0; + color: #505050; +} +.masthead-title a { + color: #505050; +} +.masthead-title small { + font-size: 75%; + font-weight: 400; + color: #c0c0c0; + letter-spacing: 0; +} + + +/* + * Posts and pages + * + * Each post is wrapped in `.post` and is used on default and post layouts. Each + * page is wrapped in `.page` and is only used on the page layout. + */ + +.page, +.post { + margin-bottom: 4em; +} + +/* Blog post or page title */ +.page-title, +.post-title, +.post-title a { + color: #303030; +} +.page-title, +.post-title { + margin-top: 0; +} + +/* Meta data line below post title */ +.post-date { + display: block; + margin-top: -.5rem; + margin-bottom: 1rem; + color: #9a9a9a; +} + +/* Related posts */ +.related { + padding-top: 2rem; + padding-bottom: 2rem; + border-top: 1px solid #eee; +} +.related-posts { + padding-left: 0; + list-style: none; +} +.related-posts h3 { + margin-top: 0; +} +.related-posts li small { + font-size: 75%; + color: #999; +} +.related-posts li a:hover { + color: #268bd2; + text-decoration: none; +} +.related-posts li a:hover small { + color: inherit; +} + + +/* + * Pagination + * + * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when + * there are no more previous or next posts to show. + */ + +.pagination { + overflow: hidden; /* clearfix */ + margin-left: -1rem; + margin-right: -1rem; + font-family: "PT Sans", Helvetica, Arial, sans-serif; + color: #ccc; + text-align: center; +} + +/* Pagination items can be `span`s or `a`s */ +.pagination-item { + display: block; + padding: 1rem; + border: 1px solid #eee; +} +.pagination-item:first-child { + margin-bottom: -1px; +} + +/* Only provide a hover state for linked pagination items */ +a.pagination-item:hover { + background-color: #f5f5f5; +} + +@media (min-width: 30em) { + .pagination { + margin: 3rem 0; + } + .pagination-item { + float: left; + width: 50%; + } + .pagination-item:first-child { + margin-bottom: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + .pagination-item:last-child { + margin-left: -1px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} diff --git a/docs/public/css/syntax.css b/docs/public/css/syntax.css new file mode 100644 index 000000000..0d5f837bd --- /dev/null +++ b/docs/public/css/syntax.css @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2021 Alliander N.V. + * + * SPDX-License-Identifier: CC-BY-4.0 + */ + +.highlight .hll { background-color: #ffc; } +.highlight .c { color: #999; } /* Comment */ +.highlight .err { color: #a00; background-color: #faa } /* Error */ +.highlight .k { color: #069; } /* Keyword */ +.highlight .o { color: #555 } /* Operator */ +.highlight .cm { color: #09f; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #099 } /* Comment.Preproc */ +.highlight .c1 { color: #999; } /* Comment.Single */ +.highlight .cs { color: #999; } /* Comment.Special */ +.highlight .gd { background-color: #fcc; border: 1px solid #c00 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #f00 } /* Generic.Error */ +.highlight .gh { color: #030; } /* Generic.Heading */ +.highlight .gi { background-color: #cfc; border: 1px solid #0c0 } /* Generic.Inserted */ +.highlight .go { color: #aaa } /* Generic.Output */ +.highlight .gp { color: #009; } /* Generic.Prompt */ +.highlight .gs { } /* Generic.Strong */ +.highlight .gu { color: #030; } /* Generic.Subheading */ +.highlight .gt { color: #9c6 } /* Generic.Traceback */ +.highlight .kc { color: #069; } /* Keyword.Constant */ +.highlight .kd { color: #069; } /* Keyword.Declaration */ +.highlight .kn { color: #069; } /* Keyword.Namespace */ +.highlight .kp { color: #069 } /* Keyword.Pseudo */ +.highlight .kr { color: #069; } /* Keyword.Reserved */ +.highlight .kt { color: #078; } /* Keyword.Type */ +.highlight .m { color: #f60 } /* Literal.Number */ +.highlight .s { color: #d44950 } /* Literal.String */ +.highlight .na { color: #4f9fcf } /* Name.Attribute */ +.highlight .nb { color: #366 } /* Name.Builtin */ +.highlight .nc { color: #0a8; } /* Name.Class */ +.highlight .no { color: #360 } /* Name.Constant */ +.highlight .nd { color: #99f } /* Name.Decorator */ +.highlight .ni { color: #999; } /* Name.Entity */ +.highlight .ne { color: #c00; } /* Name.Exception */ +.highlight .nf { color: #c0f } /* Name.Function */ +.highlight .nl { color: #99f } /* Name.Label */ +.highlight .nn { color: #0cf; } /* Name.Namespace */ +.highlight .nt { color: #2f6f9f; } /* Name.Tag */ +.highlight .nv { color: #033 } /* Name.Variable */ +.highlight .ow { color: #000; } /* Operator.Word */ +.highlight .w { color: #bbb } /* Text.Whitespace */ +.highlight .mf { color: #f60 } /* Literal.Number.Float */ +.highlight .mh { color: #f60 } /* Literal.Number.Hex */ +.highlight .mi { color: #f60 } /* Literal.Number.Integer */ +.highlight .mo { color: #f60 } /* Literal.Number.Oct */ +.highlight .sb { color: #c30 } /* Literal.String.Backtick */ +.highlight .sc { color: #c30 } /* Literal.String.Char */ +.highlight .sd { color: #c30; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #c30 } /* Literal.String.Double */ +.highlight .se { color: #c30; } /* Literal.String.Escape */ +.highlight .sh { color: #c30 } /* Literal.String.Heredoc */ +.highlight .si { color: #a00 } /* Literal.String.Interpol */ +.highlight .sx { color: #c30 } /* Literal.String.Other */ +.highlight .sr { color: #3aa } /* Literal.String.Regex */ +.highlight .s1 { color: #c30 } /* Literal.String.Single */ +.highlight .ss { color: #fc3 } /* Literal.String.Symbol */ +.highlight .bp { color: #366 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #033 } /* Name.Variable.Class */ +.highlight .vg { color: #033 } /* Name.Variable.Global */ +.highlight .vi { color: #033 } /* Name.Variable.Instance */ +.highlight .il { color: #f60 } /* Literal.Number.Integer.Long */ + +.css .o, +.css .o + .nt, +.css .nt + .nt { color: #999; } diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d9d7f426e8008dd3be40b7aaa68ebd577f098c0a GIT binary patch literal 4286 zcmbtXc~I2X9se$i(%3|Fl9*(WK?UP6ZDW!d$1tg8aH{UbZVS&LXzg3c#za6 z3Yf$)qb7KB$f3v$B1%?3Ty|l1+56rF_Mnj9ph=TpYAJ?=*Ux*f%Pxnh)5kpC@ArQ1 zeLtV?`Q8#q@V8_M@&8n@fM^jBtwhHZJ%!HaMZ>o{X+`f7Igb)4_tQMvVS1|bI34Uh zNrvtdsTdOF!Uw$a9ixSaaNdgq|}VqPeEe?gVZ6T6GqYDvK6CW-&0n z>&c@rbjCZdWV!>Cgz;is3Wyz)$T_-3J7+(ct z|Bqw9b&OKo$LK>JxPzw;e*9zk2Nc!*_nA2~W&qm;?4$HN_zQh8_Q2ogf&cGmxVhtg zm^MG*ANXqs`vSr5c>fwYy>j?Xk|?KesQ7?rpjhb5W2z^(Ct?(MQOsXBpZU(jGhfM4 z<}Hp3_zGf2{rPi5Ohl}o;1NCt*`S;Z-l&X~$i#18F*T;5uca!o{ipKgR_+XvD z&!s_s!9C2~lF4i~8#5YB<(Drv-`855L8rIUJ@vb3o(y?1TPxj81V0jL&vJ~)5C{0t zQ}*aht4Tdzv)Qj1jmE6Bw6rK|Ha!lzyJ53`_2klzJ$X{rdFefYZ8lqMN~JPUtJRX? zQzH4t^Z@GG%jolXZJ4d?*hqwUC+PJUWc!00{HN|>&bE`xW_L1+#o|#Y6w9G=8orOm z4Ch#x%4A zyDn~lZgyt1T5oarsO8P!{9KA7f}h!JwzamlJ_L>-2-u2Hla`_;=)n8cEl-oA6+WM> zVJ-K=vXBjgeoru)MKA1YFc?bUqw!e9H*dvS=-wgt;oC7DZ-s@0A@QO{H!1`MVo(Nd*Mi?VW$-(E1KTjz<_)LQ z86l6qz^8;*O#;3jG1G@SzkZ;IsBz!4rDEcn62bRxK^yx{FJ^Xq*O5hWU8n93;EmF1)_rYrZb4Z=RPi5 z!)zw?u*qa@&}cNzBc|3M1}l7p_ug`7>wxbN_SfljZ@OHr=>jn$L%kZQ6?c;tApbge zSdotp&2-=R_H3B_f!|&C5`1qJ@rNAz9=6wTJNgS_nWLjh;PbkJ_{-toXU{^w46T*t zo|Fi=xEZnVPvpbfKcF-oetmMWY_F(?Qs`GE{GT7qT=FboV~zp;UdQ=9et$VNm$}+< z!oRuXa(P^M{!L0Ae^TrxI=zjgz}^l$6sSv!&_j*qEButm>pp55^Omkg?6->8AFr)O z1^DlP55+m%*_0Z>M-BwwgSW0;y-HP8RUw&EA8-fbceEJD$v`e_>PNbkY$LkB;}q!w zZantKdZ4@e@}7`=fjn-oCW5vNHNRgcoeL5a&gbCHrTqOqxqFUY~>b7{@Fo-35)jb~VST{`^Hg z%-HxDv2mTabN&k0O+9&0u<4V`p}QdLKH<~%hlhu!#dU{PPwgu0FL5@18HOF=0S+ER zTiteb#b1}zZ=q;!<^41lEXp?F_W{fz`;el|xQ}-prJcyf9$)bTtW)+DvzwIRKIi9N zH*{YH+lO@6y?Zx}j*b%Qar#Wyp*gELVAVBytu5K5*4oWGooCj(>ds%3;?B8yS6BXm zN>5Q-ucu<|C_ld)+H(`!BW%fY&WxB!2**!NO{IZ>0qW@J_#OCN3*k+~K;%2*SuLnx z?Z7xc>QEJpIgz*gj^@xcFuO%Nu5VZj1@}XCA8Ot+UayzR%Omem4Gj&Hl9D1pe7qda z0X`eS`^}(?$q_s_2F|O=J{h;zH-s8?9((3jvrI$8lnyTl#wtT(<@a-A&>VMbS^(3kbx_V=$Y)lOvtlZ literal 0 HcmV?d00001 diff --git a/docs/public/favicon.ico.license b/docs/public/favicon.ico.license new file mode 100644 index 000000000..433ef15a6 --- /dev/null +++ b/docs/public/favicon.ico.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2021 Alliander N.V. + +SPDX-License-Identifier: CC-BY-4.0 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 691f13b8b..33081daef 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + ${basedir}/${aggregate.report.dir} 0.9.1 0.0.4 + 3.4.1 @@ -141,6 +142,20 @@ maven-jar-plugin 3.2.2 + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.plugin.javadoc} + + + attach-javadocs + package + + aggregate + + + + @@ -163,4 +178,27 @@ + + + javadoc + + CoMPAS SCT + all,-html,-missing,-syntax,-reference + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.plugin.javadoc} + + ${javadoc.title} + ${javadoc.lint} + --no-module-directories + + + + + + diff --git a/sct-app/pom.xml b/sct-app/pom.xml index 286393e04..b87bea880 100644 --- a/sct-app/pom.xml +++ b/sct-app/pom.xml @@ -17,7 +17,6 @@ local-SNAPSHOT SCT-APP - ${basedir}/${aggregate.report.dir} diff --git a/sct-app/src/main/java/org/lfenergy/compas/sct/app/SclAutomationService.java b/sct-app/src/main/java/org/lfenergy/compas/sct/app/SclAutomationService.java index 3abd95ebf..699994cf2 100644 --- a/sct-app/src/main/java/org/lfenergy/compas/sct/app/SclAutomationService.java +++ b/sct-app/src/main/java/org/lfenergy/compas/sct/app/SclAutomationService.java @@ -17,15 +17,37 @@ import java.util.*; +/** + * A representation of the {@link SclAutomationService SclAutomationService}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link SclAutomationService#createSCD(SCL, HeaderDTO, Set) Adds all elements under the SCL object from given SSD and STD files} + *
+ */ @Slf4j public class SclAutomationService { + /** + * Possible Subnetwork and ConnectAP names which should be used in generated SCD in order a have global coherence + * Configuration based on used framework can be used to externalize this datas + */ private static final Map, List> comMap = Map.of( Pair.of("RSPACE_PROCESS_NETWORK", SubNetworkDTO.SubnetworkType.MMS.toString()), Arrays.asList("PROCESS_AP", "TOTO_AP_GE"), Pair.of("RSPACE_ADMIN_NETWORK", SubNetworkDTO.SubnetworkType.IP.toString()), Arrays.asList("ADMIN_AP","TATA_AP_EFFACEC")); private SclAutomationService(){throw new IllegalStateException("SclAutomationService class"); } + /** + * Create a SCD file from specified parameters, it calls all functions defined in the process one by one, every step + * return a SCD file which will be used by the next step. + * @param ssd : (mandatory) file contains substation datas + * @param headerDTO : (mandatory) object which hold header datas and historys' one + * @param stds : (optional) list of STD files containing IED datas (IED, Communication and DataTypeTemplate) + * @return a SCD file encapsuled in object SclRootAdapter + * @throws ScdException + */ public static SclRootAdapter createSCD(@NonNull SCL ssd, @NonNull HeaderDTO headerDTO, Set stds) throws ScdException { SclRootAdapter scdAdapter = SclService.initScl(Optional.ofNullable(headerDTO.getId()), headerDTO.getVersion(),headerDTO.getRevision()); diff --git a/sct-app/src/main/java/org/lfenergy/compas/sct/app/package-info.java b/sct-app/src/main/java/org/lfenergy/compas/sct/app/package-info.java new file mode 100644 index 000000000..c83b8e50c --- /dev/null +++ b/sct-app/src/main/java/org/lfenergy/compas/sct/app/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

sct-app is a group of automation services

+ */ +package org.lfenergy.compas.sct.app; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ConnectedApDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ConnectedApDTO.java index 1104525a7..61385eaa7 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ConnectedApDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ConnectedApDTO.java @@ -11,6 +11,19 @@ import lombok.Setter; import org.lfenergy.compas.sct.commons.scl.com.ConnectedAPAdapter; +/** + * A representation of the model object Connected AP. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ConnectedApDTO#getApName() Ap Name}
  • + *
  • {@link ConnectedApDTO#getIedName() Ied Name}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TConnectedAP + */ @Setter @Getter @NoArgsConstructor @@ -19,11 +32,20 @@ public class ConnectedApDTO { private String iedName; private String apName; + /** + * Create ConnectedApDTO from constructor + * @param connectedAPAdapter object containing data to use + */ public ConnectedApDTO(ConnectedAPAdapter connectedAPAdapter) { this.iedName = connectedAPAdapter.getIedName(); this.apName = connectedAPAdapter.getApName(); } + /** + * Convert ConnectedAPAdapter object to dto ConnectedApDTO + * @param connectedAPAdapter object to convert + * @return dto ConnectedApDTO + */ public static ConnectedApDTO from(ConnectedAPAdapter connectedAPAdapter) { ConnectedApDTO connectedApDTO = new ConnectedApDTO(); connectedApDTO.iedName = connectedAPAdapter.getIedName(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlock.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlock.java index ca09a625a..500c9288e 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlock.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlock.java @@ -17,7 +17,26 @@ import java.util.List; import java.util.stream.Collectors; - +/** + * A representation of the model object ControlBlock. + * @param input such as {@link org.lfenergy.compas.sct.commons.dto.GooseControlBlock GooseControlBlock}, + * {@link org.lfenergy.compas.sct.commons.dto.ReportControlBlock ReportControlBlock}, + * {@link org.lfenergy.compas.sct.commons.dto.SMVControlBlock SMVControlBlock}, + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ControlBlock#getId() appID, smvID or rptID}
  • + *
  • {@link ControlBlock#getName() Name}
  • + *
  • {@link ControlBlock#getDataSetRef() dataSetRef}
  • + *
  • {@link ControlBlock#getDesc() Desc}
  • + *
  • {@link ControlBlock#getConfRev() Refers To confRev}
  • + *
  • {@link ControlBlock#getIedNames() Refers To IedNames}
  • + *
  • {@link ControlBlock#getSecurityEnable() Refers To security Enable}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TControlBlock + */ @Getter @Setter @NoArgsConstructor @@ -31,11 +50,25 @@ public abstract class ControlBlock extends LNodeMetaData protected List iedNames = new ArrayList<>(); protected TPredefinedTypeOfSecurityEnum securityEnable = TPredefinedTypeOfSecurityEnum.NONE; + /** + * Abstract method for getting classe type + * @return classe type + */ protected abstract Class getClassType(); + + /** + * Get ServiceType + * @return ServiceType enum object + */ public abstract TServiceType getServiceType(); public abstract U createControlBlock(); + /** + * Cast object to specified type + * @param obj object to cast + * @return object casted on specified type + */ public T cast(Object obj){ if (!obj.getClass().isAssignableFrom(getClassType())) { throw new UnsupportedOperationException("Cannot cast object to " + getClassType()); @@ -43,6 +76,10 @@ public T cast(Object obj){ return (T) obj; } + /** + * Validate Control block structure + * @throws ScdException + */ public void validateCB() throws ScdException { if(id == null || id.isBlank()){ @@ -60,6 +97,11 @@ public void validateCB() throws ScdException { } } + /** + * Check Control block's destination is present in SCD file + * @param sclRootAdapter main adapter containing SCD file + * @throws ScdException + */ public void validateDestination(SclRootAdapter sclRootAdapter) throws ScdException { for(TControlWithIEDName.IEDName iedName : iedNames){ IEDAdapter iedAdapter =sclRootAdapter.getIEDAdapterByName(iedName.getValue()); @@ -82,11 +124,20 @@ public void validateDestination(SclRootAdapter sclRootAdapter) throws ScdExcepti } } + /** + * Check if Security is enabled in IED (Services) + * @param iedAdapter IED adapter containing IED datas + * @throws ScdException + */ public final void validateSecurityEnabledValue(IEDAdapter iedAdapter) throws ScdException { TServices tServices = iedAdapter.getServices(); validateSecurityEnabledValue(tServices); } + /** + * Get ConfRev value + * @return confRev long value + */ protected Long getConfRev() { if(dataSetRef == null || dataSetRef.isBlank()){ return 0L; @@ -94,8 +145,18 @@ protected Long getConfRev() { return 10000L ; } + /** + * Abstract method to check validity of Security Enable state for Control blocks + * @param tServices Service object + * @throws ScdException + */ protected abstract void validateSecurityEnabledValue(TServices tServices) throws ScdException; + /** + * Create Control block iedName element from ClientLN element + * @param clientLN property ClientLN + * @return IEDName of Control + */ public static TControlWithIEDName.IEDName toIEDName(TClientLN clientLN){ TControlWithIEDName.IEDName iedName = new TControlWithIEDName.IEDName(); @@ -109,6 +170,11 @@ public static TControlWithIEDName.IEDName toIEDName(TClientLN clientLN){ return iedName; } + /** + * Get Control block settings from Service + * @param tServices Service object + * @return ServiceSettingsNoDynEnum enum value + */ public TServiceSettingsNoDynEnum getControlBlockServiceSetting(TServices tServices){ if(tServices == null) { return TServiceSettingsNoDynEnum.FIX; @@ -126,6 +192,10 @@ public TServiceSettingsNoDynEnum getControlBlockServiceSetting(TServices tServic return TServiceSettingsNoDynEnum.FIX; } + /** + * Get Control block + * @return Control block's string value from IEDNames + */ @Override public String toString() { String values = iedNames.stream().map(TControlWithIEDName.IEDName::getValue) diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DaTypeName.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DaTypeName.java index a775c1e0e..b3a240177 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DaTypeName.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DaTypeName.java @@ -17,6 +17,24 @@ import java.util.Map; import java.util.stream.Collectors; +/** + * A representation of the model object DA. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link DaTypeName#getName Name}
  • + *
  • {@link DaTypeName#getStructNames Refers To StructNames}
  • + *
  • {@link org.lfenergy.compas.scl2007b4.model.TFCEnum Refers To TFCEnum }
  • + *
  • {@link DaTypeName#getType type}
  • + *
  • {@link org.lfenergy.compas.scl2007b4.model.TPredefinedBasicTypeEnum Refers To TPredefinedBasicTypeEnum}
  • + *
  • {@link DaTypeName#isValImport Refers To valImport}
  • + *
  • {@link DaTypeName#getDaiValues Refers To DAI Values}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDA + */ @Getter @Setter @NoArgsConstructor @@ -30,14 +48,28 @@ public class DaTypeName extends DataTypeName{ private boolean valImport; private Map daiValues = new HashMap<>(); + /** + * Constructor + * @param daName input + */ public DaTypeName(String daName) { super(daName); } + /** + * Constructor + * @param name input + * @param names input + */ public DaTypeName(String name, String names) { super(name, names); } + /** + * Initializes DaTypeName + * @param dataName input + * @return DaTypeName object + */ public static DaTypeName from(DaTypeName dataName){ DaTypeName daTypeName = new DaTypeName(dataName.toString()); if(dataName.isDefined()) { @@ -51,10 +83,18 @@ public static DaTypeName from(DaTypeName dataName){ return daTypeName; } + /** + * Check valImport state + * @return boolean value of valImport + */ public boolean isValImport(){ return valImport; } + /** + * Check if DA is updatable + * @return boolean value of DA state + */ public boolean isUpdatable(){ return isValImport() && (fc == TFCEnum.CF || @@ -66,6 +106,10 @@ public boolean isUpdatable(){ ); } + /** + * Add DAI values to list of DAI values + * @param vals list of DAI values + */ public void addDaiValues(List vals) { if(vals.size() == 1){ daiValues.put(0L,vals.get(0).getValue()); @@ -74,6 +118,10 @@ public void addDaiValues(List vals) { } } + /** + * Add DAI value to Map of DAI values + * @param val DAI value + */ public void addDaiValue(TVal val) { if(!val.isSetSGroup()){ daiValues.put(0L,val.getValue()); @@ -82,6 +130,11 @@ public void addDaiValue(TVal val) { } } + /** + * Add DAI value to Map of DAI values + * @param sg Setting group value + * @param val value + */ public void addDaiValue(Long sg, String val) { if(sg == null){ daiValues.put(0L,val); @@ -90,6 +143,10 @@ public void addDaiValue(Long sg, String val) { } } + /** + * Copy DA's contain + * @param daName DA object + */ public void merge(DaTypeName daName) { if(!isDefined()) return; fc = daName.fc; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataSetInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataSetInfo.java index 6918f020c..e1a62614a 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataSetInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataSetInfo.java @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2021 RTE FRANCE // // SPDX-License-Identifier: Apache-2.0 - package org.lfenergy.compas.sct.commons.dto; import lombok.Getter; @@ -11,23 +10,44 @@ import org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +/** + * A representation of the model object Data Set. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link DataSetInfo#getName() Name}
  • + *
  • {@link DataSetInfo#getFCDAInfos() Refers to FCDA infos}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDataSet + */ @Getter @NoArgsConstructor public class DataSetInfo extends LNodeMetaDataEmbedder{ private String name; private List fcdaInfos = new ArrayList<>(); + /** + * Constructor + * @param name input + */ public DataSetInfo(String name){ super(); this.name = name; } + /** + * Convert DataSet object to DataSetInfo object + * @param tDataSet object + * @return DataSetInfo object value + */ public static DataSetInfo from(TDataSet tDataSet) { DataSetInfo dataSetInfo = new DataSetInfo(); dataSetInfo.name = tDataSet.getName(); @@ -39,23 +59,44 @@ public static DataSetInfo from(TDataSet tDataSet) { return dataSetInfo; } + /** + * Get Set of DataSet from LnAdapter + * @param lnAdapter object LnAdapter + * @return Set of DataSetInfo + */ public static Set getDataSets(AbstractLNAdapter lnAdapter){ return lnAdapter.getDataSet(null) .stream().map(DataSetInfo::from).collect(Collectors.toSet()); } + /** + * Add FCDA to FCDA list + * @param fcdaInfo object FCDAInfo containing FCDA datas + */ public void addFCDAInfo(FCDAInfo fcdaInfo){ fcdaInfos.add(fcdaInfo); } + /** + * Get FCDA list from DtaSetInfo + * @return FCDA list + */ public List getFCDAInfos(){ return Collections.unmodifiableList(fcdaInfos); } + /** + * Set DataSet name + * @param name string DataSet name + */ public void setName(String name){ this.name = name; } + /** + * Check DataSet validity + * @return validity state + */ public boolean isValid(){ if(name.length() > 32 || fcdaInfos.isEmpty()){ return false; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataTypeName.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataTypeName.java index 0a09e5e1c..c8f23e8ae 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataTypeName.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DataTypeName.java @@ -15,7 +15,32 @@ import java.util.ArrayList; import java.util.List; - +/** + * A representation of the model object Data Type Name. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link DataTypeName#getName Name}
  • + *
  • {@link DataTypeName#getStructNames Refers to middle name part of a structured data name} + *

    + * Example of instantiated subdata + *

    + *
    + *          {@code
    + *              
    + *                  
    + *                      
    + *                          
    + *                      
    + *                  
    + *              
    + *          }
    + *   
  • + *
+ * + */ @Getter @Setter @Slf4j @@ -26,6 +51,10 @@ public class DataTypeName { private List structNames = new ArrayList<>(); // [.DataName[…]] or [.DAComponentName[ ….]] + /** + * Create DataTypeName object from DataName by constructor + * @param dataName string containing DA/DO names + */ public DataTypeName(String dataName){ if(dataName == null) return; String[] tokens = dataName.split("\\."); @@ -35,6 +64,11 @@ public DataTypeName(String dataName){ } } + /** + * Create DataTypeName object from DataName by constructor + * @param name string containing DA/DO name + * @param names string containing DA/DO names + */ public DataTypeName(String name, String names){ if(name == null) return; this.name = name; @@ -43,14 +77,27 @@ public DataTypeName(String name, String names){ } } + /** + * Initializes DataTypeName from DataTypeName + * @param dataName input + * @return DataTypeName object + */ public static DataTypeName from(DataTypeName dataName){ return new DataTypeName(dataName.toString()); } + /** + * Check if DataTypeName is well defined + * @return boolean definition state of DA/DO name + */ public boolean isDefined(){ return !StringUtils.isBlank(name); } + /** + * Convert to formatted String the DataTypeName + * @return string of name and structNames comma separated + */ @Override public String toString(){ StringBuilder stringBuilder = new StringBuilder(); @@ -62,16 +109,28 @@ public String toString(){ return stringBuilder.toString(); } + /** + * Add list of DA/DO names to existing DataTypeName + * @param structName list of string DA/DO name's + */ public void addStructName(String structName) { structNames.add(structName); } + /** + * Get last name from DataTypeName (last DA/DO name from list of DA/DO names) + * @return string DA/DO name + */ @JsonIgnore public String getLast(){ int sz = structNames.size(); return sz == 0 ? name : structNames.get(sz -1); } + /** + * Add DA/DO name to DataTypeName + * @param name DA/DO name + */ public void addName(String name) { if(isDefined()){ structNames.add(name); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DoTypeName.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DoTypeName.java index c75e512f3..9c4f0c164 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DoTypeName.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/DoTypeName.java @@ -10,6 +10,20 @@ import lombok.Setter; import org.lfenergy.compas.scl2007b4.model.TPredefinedCDCEnum; +/** + * A representation of the model object DoTypeName. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link DoTypeName#getName Name}
  • + *
  • {@link DoTypeName#getStructNames Refers To getStructNames}
  • + *
  • {@link DoTypeName#getCdc Refers To CDC}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDO + */ @Getter @Setter @NoArgsConstructor @@ -18,13 +32,28 @@ public class DoTypeName extends DataTypeName { public static final String VALIDATION_REGEX = "[A-Z][0-9A-Za-z]{0,11}(\\.[a-z][0-9A-Za-z]*(\\([0-9]+\\))?)?"; private TPredefinedCDCEnum cdc; + /** + * Constructor + * @param doName input + */ public DoTypeName(String doName) { super(doName); } + + /** + * Constructor + * @param ppDoName input + * @param sdoNames input + */ public DoTypeName(String ppDoName, String sdoNames) { super(ppDoName, sdoNames); } + /** + * Initializes DoTypeName + * @param dataName input + * @return DoTypeName object + */ public static DoTypeName from(DoTypeName dataName){ DoTypeName doTypeName = new DoTypeName(dataName.toString()); if(doTypeName.isDefined()) { @@ -33,6 +62,10 @@ public static DoTypeName from(DoTypeName dataName){ return doTypeName; } + /** + * Copy DO's content + * @param doName DO object + */ public void merge(DoTypeName doName) { if(!isDefined()) return; if(cdc == null) diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefBindingInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefBindingInfo.java index 900adaf7a..6bef42b5c 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefBindingInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefBindingInfo.java @@ -16,6 +16,26 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * A representation of the model object ExtRef Binding Information. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ExtRefBindingInfo#getIedName Ied Name}
  • + *
  • {@link ExtRefBindingInfo#getLdInst Ld Inst}
  • + *
  • {@link ExtRefBindingInfo#getLnClass Ln Class}
  • + *
  • {@link ExtRefBindingInfo#getLnInst Ln Inst}
  • + *
  • {@link ExtRefBindingInfo#getPrefix Prefix}
  • + *
  • {@link ExtRefBindingInfo#getLnType Ln Type}
  • + *
  • {@link ExtRefBindingInfo#getDaName Refers To {@link org.lfenergy.compas.sct.commons.dto.DaTypeName}}
  • + *
  • {@link ExtRefBindingInfo#getDoName Refers To {@link org.lfenergy.compas.sct.commons.dto.DoTypeName}}
  • + *
  • {@link ExtRefBindingInfo#getServiceType Service Type}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TExtRef + */ @Getter @Setter @NoArgsConstructor @@ -31,6 +51,10 @@ public class ExtRefBindingInfo { private DaTypeName daName; private TServiceType serviceType; + /** + * Constructor + * @param tExtRef input + */ public ExtRefBindingInfo(TExtRef tExtRef){ iedName = tExtRef.getIedName(); ldInst = tExtRef.getLdInst(); @@ -74,6 +98,10 @@ public int hashCode() { return Objects.hash(iedName, ldInst, prefix, lnClass, lnInst, doName, daName, serviceType); } + /** + * Check validity of ExtRef binding information + * @return validity state + */ public boolean isValid() { final String validationRegex = DaTypeName.VALIDATION_REGEX; String doRef = doName == null ? "" : doName.toString(); @@ -91,6 +119,11 @@ public boolean isValid() { (TLLN0Enum.LLN_0.value().equals(lnClass) || !StringUtils.isBlank(lnInst)) ; } + /** + * Check dependency between ExtRef binding information and ExtRef + * @param tExtRef object containing ExtRef data's' + * @return dependency state + */ public boolean isWrappedIn(TExtRef tExtRef){ return Objects.equals(iedName,tExtRef.getIedName()) && Objects.equals(ldInst,tExtRef.getLdInst()) && @@ -100,6 +133,10 @@ public boolean isWrappedIn(TExtRef tExtRef){ (tExtRef.getServiceType() == null || Objects.equals(serviceType, tExtRef.getServiceType())); } + /** + * Check nullability of ExtRef binding information + * @return nullability state + */ public boolean isNull(){ return iedName == null && ldInst == null && @@ -111,7 +148,10 @@ public boolean isNull(){ serviceType == null; } - + /** + * Convert to string + * @return ExtRef binding information formatted to string + */ @Override public String toString() { return "ExtRefBindingInfo{" + diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefInfo.java index 7daec4023..185897b69 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefInfo.java @@ -16,8 +16,25 @@ import org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter; import java.util.Objects; - - +/** + * A representation of the model object ExtRef. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ExtRefInfo#getHolderIEDName() Ied Name}
  • + *
  • {@link ExtRefInfo#getHolderLDInst() Ld Inst}
  • + *
  • {@link ExtRefInfo#getHolderLnClass() Ln Class}
  • + *
  • {@link ExtRefInfo#getHolderLnInst() Ln Inst}
  • + *
  • {@link ExtRefInfo#getHolderLnPrefix() Prefix}
  • + *
  • {@link org.lfenergy.compas.sct.commons.dto.ExtRefSignalInfo Refers To SignalInfo}
  • + *
  • {@link org.lfenergy.compas.sct.commons.dto.ExtRefBindingInfo Refers To BindingInfo}
  • + *
  • {@link org.lfenergy.compas.sct.commons.dto.ExtRefSourceInfo Refers To SourceInfo}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TExtRef + */ @Getter @Setter @NoArgsConstructor @@ -27,6 +44,10 @@ public class ExtRefInfo extends LNodeMetaDataEmbedder{ private ExtRefBindingInfo bindingInfo; private ExtRefSourceInfo sourceInfo; + /** + * Constructor + * @param tExtRef input + */ public ExtRefInfo(TExtRef tExtRef) { super(); bindingInfo = new ExtRefBindingInfo(tExtRef); @@ -34,6 +55,16 @@ public ExtRefInfo(TExtRef tExtRef) { signalInfo = new ExtRefSignalInfo(tExtRef); } + /** + * Initializes ExtRefInfo + * @param tExtRef input + * @param iedName input + * @param ldInst input + * @param lnClass input + * @param lnInst input + * @param prefix input + * @return ExtRefInfo object + */ public static ExtRefInfo from(TExtRef tExtRef, String iedName, String ldInst, String lnClass, String lnInst, String prefix){ ExtRefInfo extRefInfo = new ExtRefInfo(tExtRef); @@ -46,6 +77,11 @@ public static ExtRefInfo from(TExtRef tExtRef, String iedName, String ldInst, return extRefInfo; } + /** + * Check match between FCDA and ExtRef information (for binding) + * @param tfcda FCDA data object + * @return match state + */ public boolean matchFCDA(@NonNull TFCDA tfcda){ boolean returnValue = true; if(AbstractLNAdapter.isNull(tfcda)) { diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSignalInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSignalInfo.java index bb9930045..299bf66b2 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSignalInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSignalInfo.java @@ -16,7 +16,23 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; - +/** + * A representation of the model object ExtRef Signal Information. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ExtRefSignalInfo#getDesc Desc}
  • + *
  • {@link ExtRefSignalInfo#getPLN PLN}
  • + *
  • {@link ExtRefSignalInfo#getPDO PDO}
  • + *
  • {@link ExtRefSignalInfo#getPDA PDA}
  • + *
  • {@link ExtRefSignalInfo#getPServT PServ T}
  • + *
  • {@link ExtRefSignalInfo#getIntAddr Int Addr}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TExtRef + */ @Slf4j @Getter @Setter @@ -29,6 +45,10 @@ public class ExtRefSignalInfo{ private String intAddr; private TServiceType pServT; + /** + * Constructor + * @param tExtRef input + */ public ExtRefSignalInfo(TExtRef tExtRef){ desc = tExtRef.getDesc(); if(!tExtRef.getPLN().isEmpty()) { @@ -41,6 +61,11 @@ public ExtRefSignalInfo(TExtRef tExtRef){ } + /** + * Initialize ExtRef + * @param signalInfo object containing ExtRef data's' + * @return ExtRef object + */ public static TExtRef initExtRef(ExtRefSignalInfo signalInfo) { TExtRef extRef = new TExtRef(); @@ -54,8 +79,11 @@ public static TExtRef initExtRef(ExtRefSignalInfo signalInfo) { return extRef; } - - + /** + * Check that if TExtRef object is wrappen in ExtRefSignalInfo object vice versa + * @param tExtRef object containing ExtRef data's' + * @return wrapper state + */ public boolean isWrappedIn(TExtRef tExtRef) { ExtRefSignalInfo that = new ExtRefSignalInfo(tExtRef); return Objects.equals(desc, that.desc) && @@ -66,6 +94,10 @@ public boolean isWrappedIn(TExtRef tExtRef) { pServT == that.pServT; } + /** + * Check validity of pDO and pDA values + * @return validity state + */ public boolean isValid() { if(StringUtils.isBlank(pDO) || StringUtils.isBlank(intAddr)){ return false; @@ -97,6 +129,10 @@ public boolean isValid() { return true; } + /** + * Convert ExtRefSignalInfo to string value + * @return ExtRefSignalInfo formatted to string + */ @Override public String toString() { return "ExtRefSignalInfo{" + diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSourceInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSourceInfo.java index 93d25a376..cd93ed9dc 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSourceInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ExtRefSourceInfo.java @@ -12,6 +12,22 @@ import java.util.Objects; +/** + * A representation of the model object ExtRef Source Information. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ExtRefSourceInfo#getSrcLDInst Src LD Inst}
  • + *
  • {@link ExtRefSourceInfo#getSrcLNClass Src LN Class}
  • + *
  • {@link ExtRefSourceInfo#getSrcLNInst Src LN Inst}
  • + *
  • {@link ExtRefSourceInfo#getSrcPrefix Src Prefix}
  • + *
  • {@link ExtRefSourceInfo#getSrcCBName Src CB Name}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TExtRef + */ @Setter @Getter @NoArgsConstructor @@ -22,6 +38,10 @@ public class ExtRefSourceInfo { private String srcLNInst = ""; private String srcCBName; + /** + * Constructor + * @param tExtRef input + */ public ExtRefSourceInfo(TExtRef tExtRef){ srcLDInst = tExtRef.getSrcLDInst(); srcPrefix = tExtRef.getSrcPrefix(); @@ -51,6 +71,10 @@ public int hashCode() { return Objects.hash(srcLDInst, srcPrefix, srcLNClass, srcLNInst, srcCBName); } + /** + * Checks ExtRefSourceInfo nullability + * @return nullability state + */ public boolean isNull(){ return srcCBName == null && srcLDInst == null && @@ -59,6 +83,10 @@ public boolean isNull(){ srcPrefix == null; } + /** + * Checks ExtRefSourceInfo validity + * @return validity state + */ public boolean isValid() { return !StringUtils.isBlank(srcCBName); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java index de23eb520..f156e7596 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java @@ -12,6 +12,25 @@ import org.lfenergy.compas.scl2007b4.model.TFCDA; import org.lfenergy.compas.scl2007b4.model.TFCEnum; +/** + * A representation of the model object FCDA. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link FCDAInfo#getDaName Da Name}
  • + *
  • {@link FCDAInfo#getDoName Do Name}
  • + *
  • {@link FCDAInfo#getFc Fc}
  • + *
  • {@link FCDAInfo#getIx em>Ix}
  • + *
  • {@link FCDAInfo#getLdInst Ld Inst}
  • + *
  • {@link FCDAInfo#getLnClass Ln Class}
  • + *
  • {@link FCDAInfo#getLnInst Ln Inst}
  • + *
  • {@link FCDAInfo#getPrefix Prefix}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TFCDA + */ @Getter @Setter @NoArgsConstructor @@ -28,6 +47,11 @@ public class FCDAInfo { private DaTypeName daName; //daName.[...bdaNames] private Long ix; + /** + * Constructor + * @param dataSet input + * @param tfcda input + */ public FCDAInfo(String dataSet, TFCDA tfcda) { this.dataSet = dataSet; fc = tfcda.getFc(); @@ -42,6 +66,10 @@ public FCDAInfo(String dataSet, TFCDA tfcda) { ix = tfcda.isSetIx() ? tfcda.getIx() : null; } + /** + * Gets FCDA + * @return FCDA object + */ @JsonIgnore public TFCDA getFCDA(){ TFCDA tfcda = new TFCDA(); @@ -71,6 +99,10 @@ public TFCDA getFCDA(){ return tfcda; } + /** + * Checks FCDAInfo validity + * @return validity state + */ public boolean isValid() { return doName != null && doName.isDefined(); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/GooseControlBlock.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/GooseControlBlock.java index ad683442c..bb8867df8 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/GooseControlBlock.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/GooseControlBlock.java @@ -11,6 +11,25 @@ import org.lfenergy.compas.sct.commons.exception.ScdException; +/** + * A representation of the model object GooseControlBlock. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link GooseControlBlock#getId appID}
  • + *
  • {@link GooseControlBlock#getName Name}
  • + *
  • {@link GooseControlBlock#getDataSetRef dataSetRef}
  • + *
  • {@link GooseControlBlock#getDesc Desc}
  • + *
  • {@link GooseControlBlock#getConfRev Refers To confRev}
  • + *
  • {@link GooseControlBlock#getIedNames Refers To Ied Names}
  • + *
  • {@link GooseControlBlock#getSecurityEnable Refers To security Enable}
  • + *
  • {@link GooseControlBlock#isFixedOffs Refers To fixedOffs}
  • + *
  • {@link GooseControlBlock#getProtocol Refers To Protocol}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TGSEControl + */ @Getter @Setter @NoArgsConstructor @@ -18,6 +37,10 @@ public class GooseControlBlock extends ControlBlock { private boolean fixedOffs = false; private TProtocol protocol; + /** + * Constructor + * @param tgseControl input + */ public GooseControlBlock(TGSEControl tgseControl) { super(); this.id = tgseControl.getAppID(); @@ -32,16 +55,29 @@ public GooseControlBlock(TGSEControl tgseControl) { this.protocol = tgseControl.getProtocol(); } + /** + * Gets classe type + * @return classe type + */ @Override protected Class getClassType() { return GooseControlBlock.class; } + /** + * Gets ServiceType value + * @return ServiceType enumeration value + */ @Override public TServiceType getServiceType() { return TServiceType.GOOSE; } + /** + * Checks Goose Control block security enable state from Service + * @param tServices Service object + * @throws ScdException + */ @Override protected void validateSecurityEnabledValue(TServices tServices) throws ScdException { if(tServices == null || @@ -59,6 +95,10 @@ protected void validateSecurityEnabledValue(TServices tServices) throws ScdExcep } } + /** + * Creates ControlBlock + * @return Goose Control Block object (GSEControl) value + */ @Override public TGSEControl createControlBlock() { TGSEControl tgseControl = new TGSEControl(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/HeaderDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/HeaderDTO.java index 35c206d37..2304cc9ec 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/HeaderDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/HeaderDTO.java @@ -15,6 +15,21 @@ import java.util.List; import java.util.UUID; +/** + * A representation of the model object Header. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link HeaderDTO#getId id}
  • + *
  • {@link HeaderDTO#getVersion version}
  • + *
  • {@link HeaderDTO#getRevision revision}
  • + *
  • {@link HeaderDTO#getHistoryItems Refers to History}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.THeader + */ @Getter @Setter @NoArgsConstructor @@ -25,22 +40,49 @@ public class HeaderDTO { private String revision; private List historyItems = new ArrayList<>(); + /** + * Constructor + * @param id input + * @param version input + * @param revision input + */ public HeaderDTO(UUID id, String version, String revision) { this.id = id; this.version = version; this.revision = revision; } + /** + * Creates HeaderDTO from {@link HeaderAdapter HeaderAdapter} object + * @param headerAdapter input + * @return {@link HeaderDTO HeaderDTO} + */ public static HeaderDTO from(HeaderAdapter headerAdapter) { HeaderDTO headerDTO = new HeaderDTO(); headerDTO.id = UUID.fromString(headerAdapter.getHeaderId()); headerDTO.version = headerAdapter.getHeaderVersion(); headerDTO.revision = headerAdapter.getHeaderRevision(); headerAdapter.getHistoryItems().forEach(tHItem -> headerDTO.historyItems.add(HistoryItem.from(tHItem))); - return headerDTO; } + /** + * A representation of the model object History. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link HistoryItem#getVersion version}
  • + *
  • {@link HistoryItem#getRevision revision}
  • + *
  • {@link HistoryItem#getWho who}
  • + *
  • {@link HistoryItem#getWhat what}
  • + *
  • {@link HistoryItem#getWhy why}
  • + *
  • {@link HistoryItem#getWhen when}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.THeader.History + */ @Getter @Setter public static class HistoryItem { @@ -51,6 +93,11 @@ public static class HistoryItem { private String why; private String when; + /** + * Initializes History + * @param tHitem input + * @return {@link HistoryItem HistoryItem} object + */ public static HistoryItem from(THitem tHitem){ HistoryItem historyItem = new HistoryItem(); historyItem.version = tHitem.getVersion(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/IedDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/IedDTO.java index 561bfb58e..95d624b57 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/IedDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/IedDTO.java @@ -15,7 +15,19 @@ import java.util.Set; import java.util.stream.Collectors; - +/** + * A representation of the model object IED. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link IedDTO#getName Ied Name}
  • + *
  • {@link IedDTO#getLDevices Refers to LDevice}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TIED + */ @Getter @NoArgsConstructor public class IedDTO { @@ -23,10 +35,20 @@ public class IedDTO { private Set lDevices = new HashSet<>(); + /** + * Constructor + * @param name input + */ public IedDTO(String name){ this.name = name; } + /** + * Initializes IedDTO + * @param iedAdapter input + * @param options input + * @return IedDTO object + */ public static IedDTO from(IEDAdapter iedAdapter, LogicalNodeOptions options) { IedDTO iedDTO = new IedDTO(); iedDTO.name = iedAdapter.getName(); @@ -38,18 +60,36 @@ public static IedDTO from(IEDAdapter iedAdapter, LogicalNodeOptions options) { return iedDTO; } + /** + * Sets IED name + * @param name input + */ public void setName(String name){ this.name = name; } + /** + * Gets LDevice's' DTO + * @return Set of LDeviceDTO object + */ public Set getLDevices() { return Set.of(lDevices.toArray(new LDeviceDTO[0])); } + /** + * Adds LDevice in LDevice's' list + * @param ld input + */ public void addLDevice(LDeviceDTO ld) { lDevices.add(ld); } + /** + * Initializes LDeviceDTO + * @param inst LDevice inst value + * @param ldName LDevice name value + * @return LDeviceDTO object + */ public LDeviceDTO lDeviceDTOFrom(String inst, String ldName) { return new LDeviceDTO(inst,ldName); } @@ -61,6 +101,11 @@ public LDeviceDTO addLDevice(String inst, String ldName) { return lDeviceDTO; } + /** + * Gets LDeviiceDTO by LDevice inst value + * @param ldInst LDevice inst value + * @return Optional LDevice object value + */ public Optional getLDeviceDTO(String ldInst){ return lDevices.stream() .filter(lDeviceDTO1 -> lDeviceDTO1.getLdInst().equals(ldInst)) diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LDeviceDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LDeviceDTO.java index 22873f50a..b9aa8c641 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LDeviceDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LDeviceDTO.java @@ -16,6 +16,20 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * A representation of the model object LDevice. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link LDeviceDTO#getLdName LD Name}
  • + *
  • {@link LDeviceDTO#getLdInst LD Inst}
  • + *
  • {@link LDeviceDTO#getLNodes Refers To LNode}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TIED + */ @Slf4j @Getter @NoArgsConstructor @@ -24,11 +38,23 @@ public class LDeviceDTO { private String ldName; private Set lNodes = new HashSet<>(); + /** + * Constructor + * @param inst input + * @param ldName input + */ public LDeviceDTO(String inst, String ldName) { this.ldInst = inst; this.ldName = ldName; } + + /** + * Initializes LDeviceDTO + * @param lDeviceAdapter input + * @param options input + * @return LDevice DTO object + */ public static LDeviceDTO from(LDeviceAdapter lDeviceAdapter, LogicalNodeOptions options) { log.info(Utils.entering()); LDeviceDTO lDeviceDTO = new LDeviceDTO(); @@ -45,28 +71,56 @@ public static LDeviceDTO from(LDeviceAdapter lDeviceAdapter, LogicalNodeOptions return lDeviceDTO; } + /** + * Sets LDevice Inst value + * @param ldInst input + */ public void setLdInst(String ldInst){ this.ldInst = ldInst; } + /** + * Sets LDevice name value + * @param ldName input + */ public void setLdName(String ldName){ this.ldName = ldName; } + /** + * Gets list of LNodes DTO + * @return Set of LNodeDTO object + */ public Set getLNodes() { return Set.of(lNodes.toArray(new LNodeDTO[0])); } + /** + * Adds LNode + * @param ln input + */ public void addLNode(LNodeDTO ln) { lNodes.add(ln); } + /** + * Adds LNode + * @param lnClass input + * @param inst input + * @param lnPrefix input + * @param lnType input + * @return LNodeDTO object + */ public LNodeDTO addLNode(String lnClass, String inst, String lnPrefix, String lnType) { LNodeDTO lNodeDTO = new LNodeDTO(inst,lnClass,lnPrefix, lnType); lNodes.add(lNodeDTO); return lNodeDTO; } + /** + * Adds Set of LNode DTO + * @param lns input + */ public void addAll(Set lns) { lNodes.addAll(lns); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeDTO.java index 5cd8707d4..a0358f693 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeDTO.java @@ -21,6 +21,26 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * A representation of the model object LNode. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link LNodeDTO#getNodeClass Ln Class}
  • + *
  • {@link LNodeDTO#getInst Ln Inst}
  • + *
  • {@link LNodeDTO#getNodeType Ln Type}
  • + *
  • {@link LNodeDTO#getPrefix Prefix}
  • + *
  • {@link LNodeDTO#getExtRefs Refers To ExtRef}
  • + *
  • {@link LNodeDTO#getGooseControlBlocks Refers To GooseControl Blocks}
  • + *
  • {@link LNodeDTO#getSmvControlBlocks Refers To SmvControl Blocks}
  • + *
  • {@link LNodeDTO#getReportControlBlocks Refers To ReportControl Blocks}
  • + *
  • {@link LNodeDTO#getResumedDataTemplates Refers To DataTemplates Objects}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TLNode + */ @Slf4j @Getter @@ -37,6 +57,13 @@ public class LNodeDTO { private Set datSets = new HashSet<>(); private Set resumedDataTemplates = new HashSet<>(); + /** + * Constructor + * @param inst input + * @param lnClass input + * @param lnPrefix input + * @param lnType input + */ public LNodeDTO(String inst, String lnClass, String lnPrefix, String lnType) { this.inst = inst; this.nodeClass = lnClass; @@ -44,6 +71,13 @@ public LNodeDTO(String inst, String lnClass, String lnPrefix, String lnType) { this.prefix = lnPrefix; } + /** + * Initialize LN + * @param nodeAdapter input + * @param options input + * @return LNodeDTO object + * @param LNode type (LLN0 or other LN's) + */ public static LNodeDTO from(AbstractLNAdapter nodeAdapter, LogicalNodeOptions options) { log.info(Utils.entering()); LNodeDTO lNodeDTO = new LNodeDTO(); @@ -106,23 +140,43 @@ public static LNodeDTO from(AbstractLNAdapter nodeAdapter, return lNodeDTO; } + /** + * Sets LNode Inst value + * @param inst input + */ public void setInst(String inst){ this.inst = inst; } + /** + * Sets LNode Class value + * @param lnClass input + */ public void setNodeClass(String lnClass){ this.nodeClass = lnClass; } + /** + * Sets LNode Type + * @param lnType + */ public void setNodeType(String lnType){ this.nodeType = lnType; } + /** + * Sets LNode Prefix value + * @param prefix input + */ public void setPrefix(String prefix){ this.prefix = prefix; } - + /** + * Extracts LNode ExtRef informations + * @param lnAdapter input + * @return LNodeDTO object + */ public static LNodeDTO extractExtRefInfo(LNAdapter lnAdapter) { String lnClass = lnAdapter.getLNClass() == null ? "" : lnAdapter.getLNClass(); LNodeDTO lNodeDTO = new LNodeDTO(lnAdapter.getLNInst(),lnClass,lnAdapter.getPrefix(), lnAdapter.getLnType()); @@ -135,14 +189,27 @@ public static LNodeDTO extractExtRefInfo(LNAdapter lnAdapter) { return lNodeDTO; } + /** + * Adds ExtRef Info to LNode ExtRefs + * @param extRef input + */ public void addExtRefInfo(ExtRefInfo extRef) { extRefs.add(extRef); } + /** + * Adds list of ExtRef Info into LNode + * @param extRefs input + */ public void addAllExtRefInfo(List extRefs) { this.extRefs.addAll(extRefs); } + /** + * Adds Control Block to LNode Control Blocks + * @param controlBlock input + * @param Control Block type (GooseControlBlock, SMVControlBlock, ReportControlBlock) + */ public void addControlBlock(ControlBlock controlBlock) { if (GooseControlBlock.class.equals(controlBlock.getClassType())) { gooseControlBlocks.add((GooseControlBlock) controlBlock); @@ -157,26 +224,51 @@ public void addControlBlock(ControlBlock controlBloc } } + /** + * Adds lis of Control Block to LNode Control Blocks + * @param controlBlockInfoList input + * @param Control Block type (GooseControlBlock, SMVControlBlock, ReportControlBlock) + */ public void addAllControlBlocks(List< ControlBlock > controlBlockInfoList){ controlBlockInfoList.forEach(this::addControlBlock); } + /** + * Adds list of DataSet to LNode + * @param dataSetList input + */ public void addAllDatSets(List dataSetList) { this.datSets.addAll(dataSetList); } + /** + * Adds DataTypeTemplate's sumarised data + * @param dtt input + */ public void addResumedDataTemplate(ResumedDataTemplate dtt) { resumedDataTemplates.add(dtt); } + /** + * Adds list of DataTypeTemplate's sumarised data + * @param dtt input + */ public void addAllResumedDataTemplate(List dtt) { this.resumedDataTemplates.addAll(dtt); } + /** + * Gets DataTypeTemplate's sumarised data + * @return Set of ResumedDataTemplate object + */ public Set getResumedDataTemplates(){ return Set.of(resumedDataTemplates.toArray(new ResumedDataTemplate[0])); } + /** + * Adds DataSet information to LNode + * @param dataSetInfo input + */ public void addDataSet(DataSetInfo dataSetInfo) { this.datSets.add(dataSetInfo); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaData.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaData.java index c21e8a91d..c59cc0227 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaData.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaData.java @@ -11,6 +11,17 @@ import org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter; import org.lfenergy.compas.sct.commons.scl.ied.LDeviceAdapter; +/** + * A representation of common attributes that defines LDName, LNName. + *
    + *
  • {@link LNodeMetaData#getIedName Ied Name}
  • + *
  • {@link LNodeMetaData#getLdInst Ld Inst}
  • + *
  • {@link LNodeMetaData#getLnClass Ln Class}
  • + *
  • {@link LNodeMetaData#getLnInst Ln Inst}
  • + *
  • {@link LNodeMetaData#getLnPrefix Prefix}
  • + *
+ * @see org.lfenergy.compas.sct.commons.scl.ObjectReference + */ @Getter @Setter @NoArgsConstructor @@ -21,6 +32,11 @@ public class LNodeMetaData { private String lnInst; private String lnPrefix; + /** + * Initializes LNode meta data's' + * @param tAbstractLNAdapter input + * @return LNodeMetaData object + */ public static LNodeMetaData from(@NonNull AbstractLNAdapter tAbstractLNAdapter) { LNodeMetaData metaData = new LNodeMetaData(); metaData.lnClass = tAbstractLNAdapter.getLNClass(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaDataEmbedder.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaDataEmbedder.java index d352f7580..33b6c1cd4 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaDataEmbedder.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LNodeMetaDataEmbedder.java @@ -6,54 +6,111 @@ import lombok.AllArgsConstructor; - +/** + * A representation of the model object LNodeMetaDataEmbedder. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link LNodeMetaDataEmbedder#metaData Refers to {@link org.lfenergy.compas.sct.commons.dto.LNodeMetaData LNodeMetaData}}
  • + *
+ */ @AllArgsConstructor public class LNodeMetaDataEmbedder { protected LNodeMetaData metaData ; + /** + * Default constructor + */ LNodeMetaDataEmbedder(){ this.metaData = new LNodeMetaData(); } - + /** + * Sets Meta Data's information + * @param metaData input + */ public void setMetaData(LNodeMetaData metaData){ this.metaData = metaData; } + /** + * Gets IED name's vale + * @return string IED name + */ public String getHolderIEDName(){ return metaData.getIedName(); } + /** + * Gets LDevice Inst's value + * @return String LDevice Inst + */ public String getHolderLDInst(){ return metaData.getLdInst(); } + + /** + * Gets LNode Class value + * @return String LNode Class + */ public String getHolderLnClass(){ return metaData.getLnClass(); } + + /** + * Gets LNode Inst's value + * @return String LNode Inst's value + */ public String getHolderLnInst(){ return metaData.getLnInst(); } + + /** + * Gets LNode Prefix's value + * @return String LNode Prefix's value + */ public String getHolderLnPrefix(){ return metaData.getLnPrefix(); } + /** + * Sets IED name's value + * @param iedName input + */ public void setHolderIEDName(String iedName){ metaData.setIedName(iedName); } + /** + * Sets LDevice Inst's value + * @param ldInst input + */ public void setHolderLDInst(String ldInst){ metaData.setLdInst(ldInst); } + /** + * Sets LNode Class's value + * @param lnClass input + */ public void setHolderLnClass(String lnClass){ metaData.setLnClass(lnClass); } + /** + * Sets LNode Inst's value + * @param lnInst input + */ public void setHolderLnInst(String lnInst){ metaData.setLnInst(lnInst); } + /** + * Sets LNode Prefix's value + * @param prefix input + */ public void setHolderLnPrefix(String prefix){ metaData.setLnPrefix(prefix); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LogicalNodeOptions.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LogicalNodeOptions.java index 22644b06e..50406d529 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LogicalNodeOptions.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/LogicalNodeOptions.java @@ -8,6 +8,18 @@ import lombok.NoArgsConstructor; import lombok.Setter; +/** + * A representation of the model object LogicalNodeOptions. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link LogicalNodeOptions#withExtRef withExtRef}
  • + *
  • {@link LogicalNodeOptions#withResumedDtt withResumedDtt}
  • + *
  • {@link LogicalNodeOptions#withDatSet withDatSet}
  • + *
  • {@link LogicalNodeOptions#withCB withCB}
  • + *
+ */ @Getter @Setter @NoArgsConstructor @@ -17,6 +29,13 @@ public class LogicalNodeOptions { private boolean withCB = false; private boolean withDatSet = false; + /** + * Constructor + * @param withExtRef input + * @param withResumedDtt input + * @param withCB input + * @param withDatSet input + */ public LogicalNodeOptions(boolean withExtRef, boolean withResumedDtt, boolean withCB, boolean withDatSet) { this.withExtRef = withExtRef; this.withResumedDtt = withResumedDtt; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ReportControlBlock.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ReportControlBlock.java index 7075a5dc0..9481c77ae 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ReportControlBlock.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ReportControlBlock.java @@ -7,16 +7,35 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.lfenergy.compas.scl2007b4.model.TReportControl; -import org.lfenergy.compas.scl2007b4.model.TRptEnabled; -import org.lfenergy.compas.scl2007b4.model.TServiceType; -import org.lfenergy.compas.scl2007b4.model.TServices; -import org.lfenergy.compas.scl2007b4.model.TTrgOps; +import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.exception.ScdException; import java.util.stream.Collectors; +/** + * A representation of the model object ReportControlBlock. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ReportControlBlock#getId rptID}
  • + *
  • {@link ReportControlBlock#getName Name}
  • + *
  • {@link ReportControlBlock#getDataSetRef dataSetRef}
  • + *
  • {@link ReportControlBlock#getDesc Desc}
  • + *
  • {@link ReportControlBlock#getConfRev Refers To confRev}
  • + *
  • {@link ReportControlBlock#getIedNames Refers To IedNames}
  • + *
  • {@link ReportControlBlock#getSecurityEnable Refers To securityEnable}
  • + *
  • {@link ReportControlBlock#getTrgOps Refers To trgOps}
  • + *
  • {@link ReportControlBlock#getIntgPd Refers To intgPd}
  • + *
  • {@link ReportControlBlock#getRptEnabled Refers To rptEnabled}
  • + *
  • {@link ReportControlBlock#isBuffered Refers To buffered}
  • + *
  • {@link ReportControlBlock#getBufTime Refers To bufTime}
  • + *
  • {@link ReportControlBlock#isIndexed Refers To indexed}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TReportControl + */ @Getter @Setter @NoArgsConstructor @@ -31,7 +50,10 @@ public class ReportControlBlock extends ControlBlock { private boolean indexed = true; - + /** + * Constructor + * @param reportControl input + */ public ReportControlBlock(TReportControl reportControl) { super(); this.id = reportControl.getRptID(); @@ -53,26 +75,47 @@ public ReportControlBlock(TReportControl reportControl) { } } + /** + * Gets Class type value for ReportControlBlock + * @return ReportControlBlock.class + */ @Override protected Class getClassType() { return ReportControlBlock.class; } + /** + * Get Service Type value for ReportControlBlock + * @return Report + */ @Override public TServiceType getServiceType() { return TServiceType.REPORT; } + /** + * Gets ConfRev value for ReportControlBlock + * @return 1L + */ @Override protected Long getConfRev() { return 1L; } + /** + * Validates Security Enabled parameter value (do nothing) + * @param tServices Service object + * @throws ScdException + */ @Override protected void validateSecurityEnabledValue(TServices tServices) throws ScdException { //doNothing } + /** + * Creates Report Control Block + * @return TReportControl object + */ @Override public TReportControl createControlBlock() { TReportControl reportControl = new TReportControl(); @@ -93,6 +136,10 @@ public TReportControl createControlBlock() { return reportControl; } + /** + * Validates Report Control Block + * @throws ScdException + */ @Override public void validateCB() throws ScdException { super.validateCB(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ResumedDataTemplate.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ResumedDataTemplate.java index a52514f76..c0022cb1d 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ResumedDataTemplate.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ResumedDataTemplate.java @@ -12,6 +12,20 @@ import java.util.List; +/** + * A representation of the model object ResumedDataTemplate. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ResumedDataTemplate#lnInst LN Inst}
  • + *
  • {@link ResumedDataTemplate#lnClass LN Class}
  • + *
  • {@link ResumedDataTemplate#lnType LN Type}
  • + *
  • {@link ResumedDataTemplate#prefix Prefix}
  • + *
  • {@link org.lfenergy.compas.sct.commons.dto.DoTypeName Refers To DoTypeName}
  • + *
  • {@link org.lfenergy.compas.sct.commons.dto.DaTypeName Refers To DaTypeName}
  • + *
+ */ @Setter @Getter @AllArgsConstructor @@ -31,6 +45,11 @@ public class ResumedDataTemplate { @NonNull private DaTypeName daName = new DaTypeName(""); + /** + * Copies sumarized DataTypeTemplate informations to another one + * @param dtt input + * @return Updated ResumedDataTemplate object + */ public static ResumedDataTemplate copyFrom(ResumedDataTemplate dtt){ ResumedDataTemplate resumedDataTemplate = new ResumedDataTemplate(); resumedDataTemplate.prefix = dtt.prefix; @@ -43,6 +62,10 @@ public static ResumedDataTemplate copyFrom(ResumedDataTemplate dtt){ return resumedDataTemplate; } + /** + * Checks if DA/DO is updatable + * @return Updatable state + */ public boolean isUpdatable(){ return daName.isDefined() && daName.isUpdatable(); } @@ -53,6 +76,10 @@ public String getObjRef(String iedName, String ldInst){ return iedName + ldInst + "/" + getLNRef(); } + /** + * Gets LNode reference informations + * @return String LNode information concatenated + */ @JsonIgnore public String getLNRef(){ StringBuilder stringBuilder = new StringBuilder(); @@ -71,25 +98,46 @@ public String getLNRef(){ return stringBuilder.toString(); } + /** + * Gets Data Attributes value + * @return String Data Attributes reference by concatenated DO reference and DA reference + */ @JsonIgnore public String getDataAttributes(){ return getDoRef() + "." + getDaRef(); } + + /** + * Gets DO reference value + * @return String DO (Data Object) reference value + */ @JsonIgnore public String getDoRef(){ return isDoNameDefined() ? doName.toString() : ""; } + /** + * Gets DA (Data Attribut) reference value + * @return DA reference value + */ @JsonIgnore public String getDaRef(){ return isDaNameDefined() ? daName.toString() : ""; } + /** + * Gets FC (Functional Constraints) reference value + * @return + */ @JsonIgnore public TFCEnum getFc(){ return daName.isDefined() ? daName.getFc() : null; } + /** + * Sets DA/DO FC's value + * @param fc input + */ @JsonIgnore public void setFc(TFCEnum fc){ if(isDaNameDefined()){ @@ -99,11 +147,19 @@ public void setFc(TFCEnum fc){ } } + /** + * Gets DA/DO CDC's value + * @return CDC enum value + */ @JsonIgnore public TPredefinedCDCEnum getCdc(){ return daName.isDefined() ? doName.getCdc() : null; } + /** + * Sets DA/DO CDC's value + * @param cdc input + */ @JsonIgnore public void setCdc(TPredefinedCDCEnum cdc){ if(isDoNameDefined()){ @@ -113,18 +169,30 @@ public void setCdc(TPredefinedCDCEnum cdc){ } } + /** + * Gets SDO names' + * @return List of SDO name + */ @JsonIgnore public List getSdoNames(){ if(!isDoNameDefined()) return new ArrayList<>(); return List.of(doName.getStructNames().toArray(new String[0])); } + /** + * GEts BDA names' + * @return List of BDA name + */ @JsonIgnore public List getBdaNames(){ if(!isDaNameDefined()) return new ArrayList<>(); return List.of(daName.getStructNames().toArray(new String[0])); } + /** + * Adds DO Structure name + * @param structName input + */ public void addDoStructName(String structName){ if(isDoNameDefined()) { doName.addStructName(structName); @@ -133,6 +201,10 @@ public void addDoStructName(String structName){ } } + /** + * Adds DA Structure name + * @param structName input + */ public void addDaStructName(String structName){ if(isDaNameDefined()) { daName.addStructName(structName); @@ -141,19 +213,35 @@ public void addDaStructName(String structName){ } } + /** + * Checks if DO name is defined + * @return definition state + */ public boolean isDoNameDefined() { return doName != null && doName.isDefined(); } + /** + * Checks if DA name is defined + * @return definition state + */ public boolean isDaNameDefined() { return daName != null && daName.isDefined(); } + /** + * Gets DA Basic Type value + * @return Basic Type enum value + */ @JsonIgnore public TPredefinedBasicTypeEnum getBType(){ return daName != null ? daName.getBType() : null; } + /** + * Sets DA type + * @param type input + */ @JsonIgnore public void setType(String type){ if(isDaNameDefined()){ @@ -163,10 +251,19 @@ public void setType(String type){ } } + /** + * Gets DA type + * @return string DA type + */ @JsonIgnore public String getType(){ return daName != null ? daName.getType() : null; } + + /** + * Sets DA Basic Type value + * @param bType input + */ @JsonIgnore public void setBType(String bType){ if(isDaNameDefined()){ @@ -176,24 +273,41 @@ public void setBType(String bType){ } } + /** + * Set DO name + * @param doName input + */ public void setDoName(DoTypeName doName){ if(doName != null) { this.doName = DoTypeName.from(doName); } } + /** + * Sets DA name + * @param daName input + */ public void setDaName(DaTypeName daName){ if(daName != null) { this.daName = DaTypeName.from(daName); } } + /** + * Adds DAI values to DA + * @param values input + */ @JsonIgnore public void setDaiValues(List values) { if(isDaNameDefined()){ daName.addDaiValues(values); } } + + /** + * Set DA ValImport value + * @param valImport input + */ @JsonIgnore public void setValImport(boolean valImport) { if(isDaNameDefined()){ @@ -201,7 +315,18 @@ public void setValImport(boolean valImport) { } } + /** + * Checks ValImport value + * @return ValImport value + */ public boolean isValImport(){ return daName.isValImport(); } + + public ResumedDataTemplate setVal(String daiValue) { + TVal newDaiVal = new TVal(); + newDaiVal.setValue(daiValue); + this.setDaiValues(List.of(newDaiVal)); + return this; + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SMVControlBlock.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SMVControlBlock.java index 13abc6425..ab9eedcc8 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SMVControlBlock.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SMVControlBlock.java @@ -13,6 +13,29 @@ import java.util.Collections; +/** + * A representation of the model object SMVControlBlock. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link SMVControlBlock#getId smvID}
  • + *
  • {@link SMVControlBlock#getName Name}
  • + *
  • {@link SMVControlBlock#getDataSetRef dataSetRef}
  • + *
  • {@link SMVControlBlock#getDesc Desc}
  • + *
  • {@link SMVControlBlock#getConfRev Refers To confRev}
  • + *
  • {@link SMVControlBlock#getIedNames Refers To IedNames}
  • + *
  • {@link SMVControlBlock#getSecurityEnable Refers To securityEnable}
  • + *
  • {@link SMVControlBlock#getSmvOpts Refers To smvOpts}
  • + *
  • {@link SMVControlBlock#getProtocol Refers To protocol}
  • + *
  • {@link SMVControlBlock#isMulticast Refers To multicast}
  • + *
  • {@link SMVControlBlock#getSmpRate Refers To smpRate}
  • + *
  • {@link SMVControlBlock#getNofASDU Refers To nofASDU}
  • + *
  • {@link SMVControlBlock#getSmpMod Refers To smpMod}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TSampledValueControl + */ @Getter @Setter @NoArgsConstructor @@ -25,7 +48,10 @@ public class SMVControlBlock extends ControlBlock{ private Long nofASDU; private TSmpMod smpMod = TSmpMod.SMP_PER_PERIOD; - + /** + * Constructor + * @param tSampledValueControl input + */ public SMVControlBlock(TSampledValueControl tSampledValueControl) { super(); this.id = tSampledValueControl.getSmvID(); @@ -43,16 +69,29 @@ public SMVControlBlock(TSampledValueControl tSampledValueControl) { securityEnable = tSampledValueControl.getSecurityEnable(); } + /** + * Gets SMV Control Block Class Type value + * @return SMVControlBlock.class + */ @Override protected Class getClassType() { return SMVControlBlock.class; } + /** + * Gets SMV Control Block Service Type value + * @return SMV + */ @Override public TServiceType getServiceType() { return TServiceType.SMV; } + /** + * Validates Security Enabled parameter value + * @param tServices Service object + * @throws ScdException + */ @Override protected void validateSecurityEnabledValue(TServices tServices) throws ScdException { if(tServices == null || @@ -70,6 +109,10 @@ protected void validateSecurityEnabledValue(TServices tServices) throws ScdExcep } } + /** + * Validates SMV Control Block + * @throws ScdException + */ @Override public void validateCB() throws ScdException { super.validateCB(); @@ -86,6 +129,10 @@ public void validateCB() throws ScdException { } } + /** + * Creates SMV Control Block + * @return TSampledValueControl object + */ @Override public TSampledValueControl createControlBlock() { diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclDTO.java index 196834819..98a76fae6 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclDTO.java @@ -9,10 +9,25 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; -import org.lfenergy.compas.sct.commons.scl.header.HeaderAdapter; import java.util.UUID; + +/** + * A representation of the model object SCL. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link SclDTO#getId Id}
  • + *
  • {@link SclDTO#getVersion Version}
  • + *
  • {@link SclDTO#getRevision Revision}
  • + *
  • {@link SclDTO#getHeader Refers To Header}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.SCL + */ @Getter @Setter @NoArgsConstructor @@ -23,10 +38,19 @@ public class SclDTO { protected short release = SclRootAdapter.RELEASE; protected HeaderDTO header; + /** + * Constructor + * @param id input + */ public SclDTO(UUID id) { this.id = id; } + /** + * Initializes SclDTO + * @param sclRootAdapter input + * @return SclDTO object value + */ public static SclDTO from(SclRootAdapter sclRootAdapter) { SclDTO sclDTO = new SclDTO(); sclDTO.version = sclRootAdapter.getSclVersion(); @@ -36,6 +60,10 @@ public static SclDTO from(SclRootAdapter sclRootAdapter) { return sclDTO; } + /** + * Gets History Who parameter value + * @return string who value + */ @JsonIgnore public String getWho() { if(header != null && !header.getHistoryItems().isEmpty()){ @@ -44,6 +72,10 @@ public String getWho() { return ""; } + /** + * Gets History What value + * @return string what value + */ @JsonIgnore public String getWhat() { if(header != null && !header.getHistoryItems().isEmpty()){ @@ -52,6 +84,10 @@ public String getWhat() { return ""; } + /** + * Gets History Why value + * @return string why value + */ @JsonIgnore public String getWhy() { if(header != null && !header.getHistoryItems().isEmpty()){ @@ -60,6 +96,10 @@ public String getWhy() { return ""; } + /** + * Gets History When value + * @return string when value + */ @JsonIgnore public String getWhen() { if(header != null && !header.getHistoryItems().isEmpty()){ diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclReport.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclReport.java new file mode 100644 index 000000000..7a62b4bf0 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SclReport.java @@ -0,0 +1,40 @@ +/* + * // SPDX-FileCopyrightText: 2022 RTE FRANCE + * // + * // SPDX-License-Identifier: Apache-2.0 + */ + +package org.lfenergy.compas.sct.commons.dto; + +import lombok.*; +import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +@Builder +public class SclReport { + + private SclRootAdapter sclRootAdapter; + + private List errorDescriptionList = new ArrayList<>(); + + public boolean isSuccess() { + return errorDescriptionList.isEmpty(); + } + + @Getter + @Setter + @AllArgsConstructor + @ToString + @EqualsAndHashCode + @Builder + public static class ErrorDescription{ + private String xpath; + private String message; + } +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SubNetworkDTO.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SubNetworkDTO.java index 4dc060eb1..148448461 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SubNetworkDTO.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/SubNetworkDTO.java @@ -19,7 +19,20 @@ import java.util.Set; import java.util.stream.Collectors; - +/** + * A representation of the model object Sub Network. + * + *

+ * The following features are supported: + *

+ *
    + *
  • {@link SubNetworkDTO#getType Type}
  • + *
  • {@link SubNetworkDTO#getName Name}
  • + *
  • {@link SubNetworkDTO#getConnectedAPs Refers To Connected AP}
  • + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TSubNetwork + */ @Getter @NoArgsConstructor public class SubNetworkDTO { @@ -28,11 +41,21 @@ public class SubNetworkDTO { private SubnetworkType type; private Set connectedAPs = new HashSet<>(); + /** + * Constructor + * @param name input + * @param type input + */ public SubNetworkDTO(String name, String type) { this.name = name; this.type = SubnetworkType.fromValue(type); } + /** + * Initializes SubNetworkDTO + * @param subNetworkAdapter input + * @return SubNetworkDTO object value + */ public static SubNetworkDTO from(SubNetworkAdapter subNetworkAdapter) { SubNetworkDTO subNetworkDTO = new SubNetworkDTO(); subNetworkDTO.name = subNetworkAdapter.getName(); @@ -45,27 +68,50 @@ public static SubNetworkDTO from(SubNetworkAdapter subNetworkAdapter) { return subNetworkDTO; } + /** + * Gets list of ConnectedApDTO of SubNetwork + * @return Set of ConnectedApDTO object + */ public Set getConnectedAPs() { return Set.of(connectedAPs.toArray(new ConnectedApDTO[0])); } + /** + * Gets SubNetwork type + * @return string SubNetwork type + */ public String getType(){ return this.type.value; } + /** + * Add ConnectedApDTO to SubNetwork list of ConnectedAPs' + * @param cap input + */ public void addConnectedAP(ConnectedApDTO cap) { connectedAPs.add(cap); } + /** + * Sets SubNetwork name + * @param sName input + */ public void setName(String sName){ name = sName; } + + /** + * Sets SubNetwork Type + * @param type input + */ public void setType(String type) { this.type = SubnetworkType.fromValue(type); } - + /** + * Subnetwork Type enum + */ public enum SubnetworkType { IP("IP"), // 0 MMS("8-MMS"), // 1 @@ -98,6 +144,13 @@ public static SubnetworkType fromValue(String text) { } } + /** + * Create default SubnetworkType in Communication node of SCL file + * @param iedName name of existing IED in SCL + * @param comAdapter Communication node Adapter object value + * @param comMap possible name of SubnetworkTypes and corresponding ConnectedAPs + * @return + */ public static Set createDefaultSubnetwork(String iedName, CommunicationAdapter comAdapter, Map, List> comMap){ Set subNetworkDTOS = new HashSet<>(); comMap.forEach((subnetworkNameType, apNames) -> { @@ -112,6 +165,11 @@ public static Set createDefaultSubnetwork(String iedName, Communi return subNetworkDTOS; } + /** + * Gets ConnectedAP name's from Communication node + * @param comAdapter Communication node object value + * @return + */ private static List getStdConnectedApNames(CommunicationAdapter comAdapter){ return comAdapter.getSubNetworkAdapters().stream() .map(SubNetworkAdapter::getConnectedAPAdapters) diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/package-info.java new file mode 100644 index 000000000..ee24e919f --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/package-info.java @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

sct.commons.dto is a group of DTO utils for operating on + * {@link org.lfenergy.compas.scl2007b4.model.SCL SCL} services + *

+ */ +package org.lfenergy.compas.sct.commons.dto; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/exception/ScdException.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/exception/ScdException.java index 5dd039631..b122e4143 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/exception/ScdException.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/exception/ScdException.java @@ -8,10 +8,19 @@ * Thrown when SCD is inconsistent */ public class ScdException extends RuntimeException { + /** + * Constructor + * @param message input (message to display) + */ public ScdException(String message) { super(message); } + /** + * Constructor + * @param message input (message to display) + * @param cause input (cause message to display) + */ public ScdException(String message, Throwable cause) { super(message, cause); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivation.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivation.java new file mode 100644 index 000000000..59dc6db7e --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivation.java @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.scl; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.tuple.Pair; +import org.lfenergy.compas.scl2007b4.model.TCompasLDeviceStatus; +import org.lfenergy.compas.sct.commons.util.STValEnum; + +import java.util.List; +import java.util.Set; + +/** + * Common class for all states that define if LDevice should be activated or not + * regardless of the CompasLDeviceStatus Private, Enum Values of DO 'Beh' and if it's referenced in Substation...LNode or not + */ +@Getter +@Setter +public class LDeviceActivation { + + private final List> iedNameLdInstList; + private boolean isUpdatable; + private String newVal; + private String errorMessage; + + public LDeviceActivation(List> iedNameLdInstList) { + this.iedNameLdInstList = iedNameLdInstList; + } + + /** + * checks whether LDevice status is authorized to be activated or Not + * @param iedName Ied name value which LDevice appear + * @param ldInst LDevice inst value + * @param compasLDeviceStatus Private value + * @param enumValues enum values + */ + public void checkLDeviceActivationStatus(String iedName, String ldInst, TCompasLDeviceStatus compasLDeviceStatus, Set enumValues) { + if (!enumValues.contains(STValEnum.ON.value) && !enumValues.contains(STValEnum.OFF.value)) { + errorMessage = "The LDevice cannot be activated or desactivated because its BehaviourKind Enum contains NOT 'on' AND NOT 'off'."; + } + if (!enumValues.contains(STValEnum.ON.value) && enumValues.contains(STValEnum.OFF.value)) { + if (isDeclaredInSubstation(iedName, ldInst)) { + errorMessage = "The LDevice cannot be set to 'on' but has been selected into SSD."; + } else { + isUpdatable = true; + newVal = STValEnum.OFF.value; + } + } + if(compasLDeviceStatus.equals(TCompasLDeviceStatus.ACTIVE) || + compasLDeviceStatus.equals(TCompasLDeviceStatus.UNTESTED)){ + checkAuthorisationToActivateLDevice(iedName, ldInst, enumValues); + } + if(compasLDeviceStatus.equals(TCompasLDeviceStatus.INACTIVE)){ + checkAuthorisationToDeactivateLDevice(iedName, ldInst, enumValues); + } + } + + /** + * checks whether LDevice status is authorized to be activated when CompasLDeviceStatus Private is ACTIVE or UNTESTED + * @param iedName Ied name value which contains LDevice + * @param ldInst LDevice inst value + * @param enumValues enum values + */ + private void checkAuthorisationToActivateLDevice(String iedName, String ldInst, Set enumValues) { + if (!enumValues.contains(STValEnum.OFF.value) && enumValues.contains(STValEnum.ON.value)) { + if (isDeclaredInSubstation(iedName, ldInst)) { + isUpdatable = true; + newVal = STValEnum.ON.value; + } else { + errorMessage = "The LDevice cannot be set to 'off' but has not been selected into SSD."; + } + } + if (enumValues.contains(STValEnum.ON.value) && enumValues.contains(STValEnum.OFF.value)) { + isUpdatable = true; + if (isDeclaredInSubstation(iedName, ldInst)) { + newVal = STValEnum.ON.value; + } else { + newVal = STValEnum.OFF.value; + } + } + + } + + /** + * checks whether LDevice Status is authorized to be deactivated when CompasLDeviceStatus Private is INACTIVE + * @param iedName Ied name value which contains LDevice + * @param ldInst LDevice inst value + * @param enumValues enum values + */ + private void checkAuthorisationToDeactivateLDevice(String iedName, String ldInst, Set enumValues) { + if (!enumValues.contains(STValEnum.OFF.value) && enumValues.contains(STValEnum.ON.value)) { + if (isDeclaredInSubstation(iedName, ldInst)) { + errorMessage = "The LDevice is not qualified into STD but has been selected into SSD."; + } else { + errorMessage = "The LDevice cannot be set to 'off' but has not been selected into SSD."; + } + } + if (enumValues.contains(STValEnum.ON.value) && enumValues.contains(STValEnum.OFF.value)) { + if (isDeclaredInSubstation(iedName, ldInst)) { + errorMessage = "The LDevice is not qualified into STD but has been selected into SSD."; + } else { + isUpdatable = true; + newVal = STValEnum.OFF.value; + } + } + } + + /** + * checks whether a pair of IED name and LDevice inst are referenced in Substation...LNode list + * @param iedName Ied name value + * @param ldInst LDevice inst value + * @return Returns whether a pair of IED name and LDevice inst are referenced in Substation...LNode list + */ + private boolean isDeclaredInSubstation(String iedName, String ldInst){ + return iedNameLdInstList.contains(Pair.of(iedName, ldInst)); + } + + +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ObjectReference.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ObjectReference.java index b452b079f..e37e9cbe4 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ObjectReference.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ObjectReference.java @@ -8,6 +8,31 @@ import org.apache.commons.lang3.StringUtils; +/** + * A representation of the model object + * {@link ObjectReference ObjectReference}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Principal functions
  2. + *
      + *
    • {@link ObjectReference#getReference() Returns LDName : "name" attribute of IEDName element + "inst" attribute of LDevice element }
    • + *
    • {@link ObjectReference#getLdName() Returns LNName : "prefix" + "lnClass" + "lnInst" of LN element }
    • + *
    • {@link ObjectReference#getLNodeName() Returns DataName }
    • + *
    • {@link ObjectReference#getDataAttributes() Returns DataName[.DataName[…]].DataAttributeName[.DAComponentName[ ….]] }
    • + *
    + *
+ *
+ *
+ *      ObjectReference: LDName/LNName.DataName[.DataName[…]].DataAttributeName[.DAComponentName[ ….]]
+ *      LDName = "name" attribute of IEDName element + "inst" attribute of LDevice element
+ *      LNName = "prefix" + "lnClass" + "lnInst"
+ *  
+ * @see org.lfenergy.compas.sct.commons.scl.ied.LNAdapter + * @see org.lfenergy.compas.sct.commons.scl.ied.LN0Adapter + * @see org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter + */ @Getter public class ObjectReference { private static final String MALFORMED_OBJ_REF = "Malformed ObjRef : %s" ; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/PrivateService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/PrivateService.java index 3a8aa96de..af6b633d8 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/PrivateService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/PrivateService.java @@ -18,6 +18,24 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +/** + * A representation of the {@link PrivateService PrivateService}. + *

+ * The following features are supported: + *

+ *
    + *
  1. {@link PrivateService#getCompasPrivate(TPrivate, Class) + * Returns the value of the TPrivate reference object By class type}
  2. + * + *
  3. {@link PrivateService#getCompasPrivates(TBaseElement, Class) + * Returns the value of the TPrivate containment reference list from given TBaseElement By class type}
  4. + * + *
  5. {@link PrivateService#getCompasPrivates(List, Class) + * Returns the value of the TPrivate containment reference list from given TPrivate elements By class type} + *
  6. + *
+ * @see org.lfenergy.compas.scl2007b4.model.TPrivate + */ @Slf4j public final class PrivateService { @@ -27,6 +45,14 @@ private PrivateService() { private static final ObjectFactory objectFactory = new ObjectFactory(); + /** + * Converts each item of given list of Private to list of Private element of type compasClass + * @param tPrivates list of private to convert + * @param compasClass type in which Privates should be + * @return list of formatted Private + * @param Inference parameter stands for wanted type of Privates + * @throws ScdException throws when inconsistency between types + */ public static List getCompasPrivates(List tPrivates, Class compasClass) throws ScdException { PrivateEnum privateEnum = PrivateEnum.fromClass(compasClass); List compasElements = tPrivates.stream().filter(tPrivate -> privateEnum.getPrivateType().equals(tPrivate.getType())) @@ -48,6 +74,14 @@ public static List getCompasPrivates(List tPrivates, Class c return result; } + /** + * Converts all Private of given TBaseElement of type compasClass given as parameter + * @param baseElement TBaseElement contenting Privates + * @param compasClass type in which Privates should be given + * @return list of formatted Private + * @param Inference parameter stands for wanted type of Privates + * @throws ScdException throws when inconsistency between types + */ public static List getCompasPrivates(TBaseElement baseElement, Class compasClass) throws ScdException { if (!baseElement.isSetPrivate()) { return Collections.emptyList(); @@ -55,6 +89,14 @@ public static List getCompasPrivates(TBaseElement baseElement, Class c return getCompasPrivates(baseElement.getPrivate(), compasClass); } + /** + * Converts Private of type compasClass given as parameter + * @param tPrivate Private to check if is in wanted type + * @param compasClass type in which Privates should be given + * @return optional of formatted Private + * @param Inference parameter stands for wanted type of Private + * @throws ScdException throws when inconsistency between types + */ public static Optional getCompasPrivate(TPrivate tPrivate, Class compasClass) throws ScdException { List compasPrivates = getCompasPrivates(Collections.singletonList(tPrivate), compasClass); if (compasPrivates.size() > 1) { @@ -67,10 +109,21 @@ public static Optional getCompasPrivate(TPrivate tPrivate, Class compa return Optional.of(compasPrivates.get(0)); } + /** + * Gets Private CompasICDHeader from Private + * @param tPrivate Private contenting object to get as CompasICDHeader + * @return content of th Private as optional of TCompasICDHeader object + * @throws ScdException throws when inconsistency between types + */ public static Optional getCompasICDHeader(TPrivate tPrivate) throws ScdException { return getCompasPrivate(tPrivate, TCompasICDHeader.class); } + /** + * Removes specified Private from TBaseElement Privates + * @param baseElement BaseElement contenting Privates + * @param privateEnum enum type of Private to remove + */ public static void removePrivates(TBaseElement baseElement, @NonNull PrivateEnum privateEnum) { if (baseElement.isSetPrivate()) { baseElement.getPrivate().removeIf(tPrivate -> privateEnum.getPrivateType().equals(tPrivate.getType())); @@ -80,38 +133,83 @@ public static void removePrivates(TBaseElement baseElement, @NonNull PrivateEnum } } + /** + * Create Private of given type as parameter + * @param compasBay type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasBay compasBay) { return createPrivate(objectFactory.createBay(compasBay)); } + /** + * Create Private of given type as parameter + * @param compasCriteria type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasCriteria compasCriteria) { return createPrivate(objectFactory.createCriteria(compasCriteria)); } + /** + * Create Private of given type as parameter + * @param compasFlow type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasFlow compasFlow) { return createPrivate(objectFactory.createFlow(compasFlow)); } + /** + * Create Private of given type as parameter + * @param compasFunction type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasFunction compasFunction) { return createPrivate(objectFactory.createFunction(compasFunction)); } + /** + * Create Private of given type as parameter + * @param compasICDHeader type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasICDHeader compasICDHeader) { return createPrivate(objectFactory.createICDHeader(compasICDHeader)); } + /** + * Create Private of given type as parameter + * @param compasLDevice type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasLDevice compasLDevice) { return createPrivate(objectFactory.createLDevice(compasLDevice)); } + /** + * Create Private of given type as parameter + * @param compasSclFileType type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasSclFileType compasSclFileType) { return createPrivate(objectFactory.createSclFileType(compasSclFileType)); } + /** + * Create Private of given type as parameter + * @param compasSystemVersion type of Private to create + * @return created Private + */ public static TPrivate createPrivate(TCompasSystemVersion compasSystemVersion) { return createPrivate(objectFactory.createSystemVersion(compasSystemVersion)); } + /** + * Create Private of given type as parameter + * @param jaxbElement content of Private to create + * @return created Private + */ private static TPrivate createPrivate(JAXBElement jaxbElement) { PrivateEnum privateEnum = PrivateEnum.fromClass(jaxbElement.getDeclaredType()); TPrivate tPrivate = new TPrivate(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclElementAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclElementAdapter.java index ad7a44cee..1939dc07a 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclElementAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclElementAdapter.java @@ -7,17 +7,49 @@ import lombok.Getter; import org.lfenergy.compas.scl2007b4.model.TBaseElement; import org.lfenergy.compas.scl2007b4.model.TPrivate; - - +/** + * A representation of the model object + * {@link SclElementAdapter SclElementAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link SclElementAdapter#getParentAdapter Returns the value of the SclElementAdapter parent reference object}
    • + *
    • {@link SclElementAdapter#getCurrentElem Returns the value of the SclElementAdapter current reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link SclElementAdapter#addPrivate Add TPrivate under object}
    • + *
    • {@link SclElementAdapter#getXPath() Returns XPath }
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link SclElementAdapter#amRootElement() Check relation between SCL parent element and child}
    • + *
    • {@link SclElementAdapter#amChildElementRef() Check whether SCL parent element implement SCL child element}
    • + *
    + *
+ * + */ @Getter public abstract class SclElementAdapter

{ protected P parentAdapter; protected T currentElem; + /** + * Constructor + * @param parentAdapter Parent container reference + */ protected SclElementAdapter(P parentAdapter) { this.parentAdapter = parentAdapter; } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected SclElementAdapter(P parentAdapter, T currentElem) { if(currentElem == null){ throw new IllegalArgumentException("The SCL element to adapt must be defined"); @@ -27,14 +59,28 @@ protected SclElementAdapter(P parentAdapter, T currentElem) { setCurrentElem(currentElem); } - + /** + * Check if node is root in SCL + * @return true if root node of SCL and false if not + */ protected boolean amRootElement(){ return parentAdapter == null; } + /** + * Returns XPath path to current element + * @return message as undefined + */ + protected abstract String elementXPath(); + protected void customInit() { // do nothing } + + /** + * Sets current element + * @param currentElem new value of current element + */ public final void setCurrentElem(T currentElem){ this.currentElem = currentElem; if(!amRootElement() && !amChildElementRef()){ @@ -42,8 +88,16 @@ public final void setCurrentElem(T currentElem){ } } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ protected abstract boolean amChildElementRef(); + /** + * Adds Private to current element + * @param tPrivate Private to add + */ public void addPrivate(TPrivate tPrivate){ if (currentElem instanceof TBaseElement){ ((TBaseElement) currentElem).getPrivate().add(tPrivate); @@ -52,13 +106,13 @@ public void addPrivate(TPrivate tPrivate){ } } + /** + * Gets XPath path to current element from parent element + * @return path to current element + */ public String getXPath(){ String parentXpath = (parentAdapter != null) ? parentAdapter.getXPath() : ""; return parentXpath + "/" + elementXPath(); } - protected String elementXPath(){ - return String.format("undefined(%s)", currentElem.getClass().getSimpleName()); - } - } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java index c2f7807eb..4371d0abb 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java @@ -18,8 +18,41 @@ import org.lfenergy.compas.sct.commons.scl.sstation.SubstationAdapter; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.SCL SCL}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link SclRootAdapter#getHeaderAdapter Returns the value of the HeaderAdapter reference object}
    • + *
    • {@link SclRootAdapter#getSubstationAdapter(String) Returns the value of the SubstationAdapter reference object By nme}
    • + *
    • {@link SclRootAdapter#getCommunicationAdapter(boolean) Returns or Adds the value of the CommunicationAdapter reference object}
    • + *
    • {@link SclRootAdapter#getIEDAdapters() Returns the value of the IEDAdapter containment reference list}
    • + *
    • {@link SclRootAdapter#getIEDAdapterByName(String) Returns the value of the IEDAdapter reference object By name}
    • + *
    • {@link SclRootAdapter#getDataTypeTemplateAdapter() Returns the value of the DataTypeTemplateAdapter containment reference list}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link SclRootAdapter#addPrivate Add Private under this object}
    • + *
    • {@link SclRootAdapter#addHeader(String, String, String) Adds Header describing the children under this object}
    • + *
    • {@link SclRootAdapter#addIED(SCL, String) Adds IED describing the children under this object}
    • + *
    • {@link SclRootAdapter#addPrivate(TPrivate) Add given TPrivate describing the children under this object}
    • + *
    • {@link SclRootAdapter#getSclRevision() Returns the value of the revision attribute}
    • + *
    • {@link SclRootAdapter#getSclRelease() Returns the value of the release attribute}
    • + *
    • {@link SclRootAdapter#getSclVersion() Returns the value of the version attribute}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link SclRootAdapter#checkObjRef(String) Check the value of ObjectReference object}
    • + *
    + *
+ */ @Getter @Setter @Slf4j @@ -29,7 +62,13 @@ public class SclRootAdapter extends SclElementAdapter { public static final String REVISION = "B"; public static final String VERSION = "2007"; - + /** + * Constructor + * @param hId SCL Header ID + * @param hVersion SCL Header Version + * @param hRevision SCL Header Revision + * @throws ScdException throws when inconsistenc in SCL file + */ public SclRootAdapter(String hId, String hVersion, String hRevision) throws ScdException { super(null); currentElem = new SCL(); @@ -39,7 +78,10 @@ public SclRootAdapter(String hId, String hVersion, String hRevision) throws ScdE addHeader(hId,hVersion,hRevision); } - + /** + * Constructor + * @param scd SCL for which adapter is created + */ public SclRootAdapter(SCL scd) { super(null,scd); if(scd.getHeader() == null){ @@ -47,33 +89,78 @@ public SclRootAdapter(SCL scd) { } } - + /** + * Check if node is child of the reference node + * @return true + */ @Override protected boolean amChildElementRef() { return true; } + /** + * Returns XPath path to current element + * @return "SCL> + */ @Override protected String elementXPath() { return "SCL"; } + /** + * Gets SCL Release value + * @return Release value + */ public Short getSclRelease(){ return currentElem.getRelease(); } + /** + * Gets SCL Revision value + * @return Revision value + */ public String getSclRevision(){ return currentElem.getRevision(); } + /** + * Gets SCL Version value + * @return Version value + */ public String getSclVersion(){ return currentElem.getVersion(); } + /** + * Gets Substation from SCL root node by Substation name + * @param ssName name of wanted Substation + * @return SubstationAdapter object + * @throws ScdException throws when unknown Substation + */ public SubstationAdapter getSubstationAdapter(String ssName) throws ScdException { return new SubstationAdapter(this,ssName); } + /** + * Gets the first Substation from SCL root node + * @return SubstationAdapter object + * @throws ScdException throws when unknown Substation + */ + public SubstationAdapter getSubstationAdapter() throws ScdException { + TSubstation tSubstation = currentElem.getSubstation() + .stream() + .findFirst() + .orElseThrow(() -> new ScdException("No Substation in SCL file")); + return new SubstationAdapter(this, tSubstation); + } + + /** + * Add Header to SCL root node + * @param hId SCL Header ID + * @param hVersion SCL Header Version + * @param hRevision SCL Header Revision + * @throws ScdException throws when header already exists in SCL + */ protected void addHeader(@NonNull String hId, @NonNull String hVersion, @NonNull String hRevision) throws ScdException { if(currentElem.getHeader() != null){ @@ -88,6 +175,13 @@ protected void addHeader(@NonNull String hId, @NonNull String hVersion, @NonNull currentElem.setHeader(tHeader); } + /** + * Adds IED and updates DataTypeTemplate of current SCL + * @param icd ICD containing IED to add and related DataTypeTemplate + * @param iedName name of IED to add in SCL + * @return IEDAdapter as added IED + * @throws ScdException throws when inconsistency between IED to add and SCL file content + */ public IEDAdapter addIED(SCL icd, String iedName) throws ScdException { if(icd.getIED().isEmpty()){ throw new ScdException("No IED to import from ICD file"); @@ -113,16 +207,29 @@ public IEDAdapter addIED(SCL icd, String iedName) throws ScdException { return getIEDAdapterByName(iedName); } + /** + * Checks if IED is present in SCL + * @param iedName name of IED to find in SCL + * @return Boolean value of check result + */ private boolean hasIED(String iedName) { return currentElem.getIED() .stream() .anyMatch(tied -> tied.getName().equals(iedName)); } + /** + * Gets Header from current SCL + * @return HeaderAdapter object as Header of SCL + */ public HeaderAdapter getHeaderAdapter() { return new HeaderAdapter(this,currentElem.getHeader()); } + /** + * Gets DataTypeTemplates from current SCL + * @return DataTypeTemplateAdapter object as DataTypeTemplates of SCL + */ public DataTypeTemplateAdapter getDataTypeTemplateAdapter(){ if(currentElem.getDataTypeTemplates() == null){ currentElem.setDataTypeTemplates(new TDataTypeTemplates()); @@ -130,11 +237,23 @@ public DataTypeTemplateAdapter getDataTypeTemplateAdapter(){ return new DataTypeTemplateAdapter(this, currentElem.getDataTypeTemplates()); } + /** + * Gets IED by name from SCL + * @param iedName name of IED to find in SCL + * @return IEDAdapter object as IED of SCL + * @throws ScdException throws when unknown IED + */ public IEDAdapter getIEDAdapterByName(String iedName) throws ScdException { // ; Unmarshaller return new IEDAdapter(this,iedName); } + /** + * Gets Communication from SCL + * @param createIfNotExists true create Communication node if not exist, false do not create communication + * @return CommunicationAdapter object as IED of SCL + * @throws ScdException throws when no Communication in SCL and createIfNotExists == false + */ public CommunicationAdapter getCommunicationAdapter(boolean createIfNotExists) throws ScdException { if(currentElem.getCommunication() == null && !createIfNotExists){ throw new ScdException("SCD has no communication tag"); @@ -144,12 +263,22 @@ public CommunicationAdapter getCommunicationAdapter(boolean createIfNotExists) t return new CommunicationAdapter(this,currentElem.getCommunication()); } + /** + * Gets all IEDs from SCL + * @return list of IEDAdapter object as IEDs of SCL + */ public List getIEDAdapters() { return currentElem.getIED().stream() .map(tied -> new IEDAdapter(this,tied)) .collect(Collectors.toList()); } + /** + * Checks if given reference matches with at least one IED of SCL + * @param val reference to check + * @return IEDAdapter object as IED of SCL + * @throws ScdException throws when no IED matches + */ public IEDAdapter checkObjRef(String val) throws ScdException { ObjectReference objRef = new ObjectReference(val); for(TIED tied : currentElem.getIED()){ @@ -160,4 +289,5 @@ public IEDAdapter checkObjRef(String val) throws ScdException { } throw new ScdException("Invalid ObjRef: " + val); } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclService.java index 068b8f6e3..1c8f4246f 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclService.java @@ -18,6 +18,7 @@ import org.lfenergy.compas.sct.commons.scl.dtt.LNodeTypeAdapter; import org.lfenergy.compas.sct.commons.scl.header.HeaderAdapter; import org.lfenergy.compas.sct.commons.scl.ied.*; +import org.lfenergy.compas.sct.commons.scl.sstation.SubstationAdapter; import org.lfenergy.compas.sct.commons.util.Utils; import java.util.*; @@ -27,6 +28,53 @@ import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; import static org.lfenergy.compas.sct.commons.util.PrivateEnum.COMPAS_ICDHEADER; +/** + * A representation of the {@link SclService SclService}. + *

+ * The following features are supported: + *

+ *
    + *
  • Initialization functions
  • + *
      + *
    1. {@link SclService#initScl(Optional, String, String) Initialize the SCL object}
    2. + *
    3. {@link SclService#addHistoryItem(SCL, String, String, String) Adds History object under THeader reference object}
    4. + *
    5. {@link SclService#updateHeader(SCL, HeaderDTO) Update Header reference object}
    6. + *
    + *
  • IED features
  • + *
      + *
    1. {@link SclService#addIED(SCL, String, SCL) Adds the IED object}
    2. + *
    + *
  • Communication features
  • + *
      + *
    1. {@link SclService#getSubnetwork(SCL) Returns list of SubNetworkDTO }
    2. + *
    3. {@link SclService#addSubnetworks(SCL, Set, Optional) Adds the Subnetwork elements under TCommunication reference object}
    4. + *
    + *
  • ExtRef features
  • + *
      + *
    1. {@link SclService#getExtRefInfo Returns list of ExtRefInfo }
    2. + *
    3. {@link SclService#getExtRefBinders Returns list of ExtRefBindingInfo }
    4. + *
    5. {@link SclService#updateExtRefBinders(SCL, ExtRefInfo) Update the TExtRef reference object for given ExtRefBindingInfo model}
    6. + *
    7. {@link SclService#getExtRefSourceInfo Returns list of ExtRefSourceInfo }
    8. + *
    9. {@link SclService#updateExtRefSource(SCL, ExtRefInfo) Update the TExtRef reference object for given ExtRefSourceInfo model}
    10. + *
    + *
  • DAI features
  • + *
      + *
    1. {@link SclService#getDAI Returns list of ResumedDataTemplate }
    2. + *
    3. {@link SclService#updateDAI(SCL, String, String, ResumedDataTemplate) + * Update the TDAI reference object for given iedName, ldInst and ResumedDataTemplate model}
    4. + *
    + *
  • EnumType features
  • + *
      + *
    1. {@link SclService#getEnumTypeElements(SCL, String) Returns Map (ord, enumVal) of TEnumType reference object}
    2. + *
    + * + *
+ * @see org.lfenergy.compas.sct.commons.scl.header.HeaderAdapter + * @see org.lfenergy.compas.sct.commons.scl.sstation.SubstationAdapter + * @see org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter + * @see org.lfenergy.compas.sct.commons.scl.com.CommunicationAdapter + * @see org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter + */ @Slf4j public class SclService { @@ -38,6 +86,14 @@ private SclService() { throw new IllegalStateException("SclService class"); } + /** + * Initialise SCD file with Header and Private SCLFileType + * @param hId optional SCL Header ID, if empty random UUID will be created + * @param hVersion SCL Header Version + * @param hRevision SCL Header Revision + * @return SclRootAdapter object as SCD file + * @throws ScdException throws when inconsistenc in SCL file + */ public static SclRootAdapter initScl(Optional hId, String hVersion, String hRevision) throws ScdException { UUID headerId = hId.orElseGet(UUID::randomUUID); SclRootAdapter scdAdapter = new SclRootAdapter(headerId.toString(), hVersion, hRevision); @@ -45,6 +101,14 @@ public static SclRootAdapter initScl(Optional hId, String hVersion, String return scdAdapter; } + /** + * Adds new HistoryItem in SCL file + * @param scd SCL file in which new History should be added + * @param who Who realize the action + * @param what What kind of action is realized + * @param why Why this action is done + * @return SclRootAdapter object as SCD file + */ public static SclRootAdapter addHistoryItem(SCL scd, String who, String what, String why) { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); HeaderAdapter headerAdapter = sclRootAdapter.getHeaderAdapter(); @@ -52,6 +116,12 @@ public static SclRootAdapter addHistoryItem(SCL scd, String who, String what, St return sclRootAdapter; } + /** + * Updates Header of SCL file + * @param scd SCL file in which Header should be updated + * @param headerDTO Header new values + * @return SclRootAdapter object as SCD file + */ public static SclRootAdapter updateHeader(@NonNull SCL scd, @NonNull HeaderDTO headerDTO) { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); HeaderAdapter headerAdapter = sclRootAdapter.getHeaderAdapter(); @@ -80,11 +150,28 @@ public static SclRootAdapter updateHeader(@NonNull SCL scd, @NonNull HeaderDTO h return sclRootAdapter; } + /** + * Adds IED in SCL file (Related DataTypeTemplate of SCL is updated also) + * @param scd SCL file in which IED should be added + * @param iedName name of IED to add in SCL + * @param icd ICD containing IED to add and related DataTypeTemplate + * @return IEDAdapter as added IED + * @throws ScdException throws when inconsistency between IED to add and SCL file content + */ public static IEDAdapter addIED(SCL scd, String iedName, SCL icd) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); return sclRootAdapter.addIED(icd, iedName); } + /** + * Adds new SubNetworks in SCL file from ICD file + * @param scd SCL file in which SubNetworks should be added + * @param subNetworks list of SubNetworks DTO contenting SubNetwork and ConnectedAp parameter names + * @param icd ICD file from which SubNetworks functional data are copied from + * @return optional of CommunicationAdapter object as Communication node of SCL file. + * Calling getParentAdapter() will give SCL file + * @throws ScdException throws when no Communication in SCL and createIfNotExists == false + */ public static Optional addSubnetworks(SCL scd, Set subNetworks, Optional icd) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); CommunicationAdapter communicationAdapter; @@ -113,6 +200,12 @@ public static Optional addSubnetworks(SCL scd, SetSubNetworkDTO from SCL + * @throws ScdException throws when no Communication in SCL and createIfNotExists == false + */ public static List getSubnetwork(SCL scd) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); CommunicationAdapter communicationAdapter = sclRootAdapter.getCommunicationAdapter(false); @@ -122,6 +215,14 @@ public static List getSubnetwork(SCL scd) throws ScdException { .collect(Collectors.toList()); } + /** + * Gets all ExtRef from specific IED/LDevice in SCL file + * @param scd SCL file in which ExtRefs should be found + * @param iedName name of IED in which LDevice is localized + * @param ldInst LdInst of LDevice in which all ExtRefs should be found + * @return list of ExtRefInfo from specified parameter SCL/IED/LDevice + * @throws ScdException throws when unknown specified IED or LDevice + */ public static List getExtRefInfo(SCL scd, String iedName, String ldInst) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); @@ -133,6 +234,18 @@ public static List getExtRefInfo(SCL scd, String iedName, String ldI return lDeviceAdapter.getExtRefInfo(); } + /** + * Gets all possible ExtRefs to bind in SCL file with given ExtRef (signalInfo) in SCL file + * @param scd SCL file in which ExtRefs should be found + * @param iedName name of IED in which LDevice is localized + * @param ldInst ldInst of LDevice in which LN is localized + * @param lnClass lnClass of LN in which ExtRef signal to find binders is localized + * @param lnInst lnInst of LN in which ExtRef signal to find binders is localized + * @param prefix prefix of LN in which ExtRef signal to find binders is localized + * @param signalInfo ExtRef signal for which we should find possible binders in SCL file binders + * @return list of ExtRefBindingInfo object (containing binding data for each LNode in current SCL file) + * @throws ScdException throws when ExtRef contains inconsistency data + */ public static List getExtRefBinders(SCL scd, String iedName, String ldInst, String lnClass, String lnInst, String prefix, ExtRefSignalInfo signalInfo) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); @@ -160,6 +273,12 @@ public static List getExtRefBinders(SCL scd, String iedName, return potentialBinders; } + /** + * Updates ExtRef binding data related to given ExtRef (extRefInfo) in given SCL file + * @param scd SCL file in which ExtRef to update should be found + * @param extRefInfo ExtRef signal for which we should find possible binders in SCL file + * @throws ScdException throws when mandatory data of ExtRef are missing + */ public static void updateExtRefBinders(SCL scd, ExtRefInfo extRefInfo) throws ScdException { if (extRefInfo.getBindingInfo() == null || extRefInfo.getSignalInfo() == null) { throw new ScdException("ExtRef Signal and/or Binding information are missing"); @@ -187,6 +306,13 @@ public static void updateExtRefBinders(SCL scd, ExtRefInfo extRefInfo) throws Sc abstractLNAdapter.updateExtRefBinders(extRefInfo); } + /** + * Gets all Control Blocks related to extRefInfo in given SCL file + * @param scd SCL file in which ControlBlocks should be found + * @param extRefInfo ExtRef signal for which we should find related ControlBlocks + * @return list of ControlBlock object as ControlBlocks of LNode specified in extRefInfo + * @throws ScdException throws when mandatory data of ExtRef are missing + */ public static List> getExtRefSourceInfo(SCL scd, ExtRefInfo extRefInfo) throws ScdException { ExtRefSignalInfo signalInfo = extRefInfo.getSignalInfo(); @@ -237,6 +363,13 @@ public static List> getExtRefSourceInfo(SCL scd, ExtRefInfo extR return srcLnAdapter.getControlSetByExtRefInfo(extRefInfo); } + /** + * Updates ExtRef source binding data's based on given data in extRefInfo + * @param scd SCL file in which ExtRef source data's to update should be found + * @param extRefInfo new data for ExtRef source binding data + * @return TExtRef object as update ExtRef with new source binding data + * @throws ScdException throws when mandatory data of ExtRef are missing + */ public static TExtRef updateExtRefSource(SCL scd, ExtRefInfo extRefInfo) throws ScdException { String iedName = extRefInfo.getHolderIEDName(); String ldInst = extRefInfo.getHolderLDInst(); @@ -275,6 +408,17 @@ public static TExtRef updateExtRefSource(SCL scd, ExtRefInfo extRefInfo) throws return anLNAdapter.updateExtRefSource(extRefInfo); } + /** + * Gets a list of summarized DataTypeTemplate for DataAttribute DA (updatable or not) related to the one given + * in rDtt + * @param scd SCL file in which DataTypeTemplate of DAIs should be found + * @param iedName name of IED in which DAs are localized + * @param ldInst ldInst of LDevice in which DAIs are localized + * @param rDtt reference summarized DataTypeTemplate related to IED DAIs + * @param updatable true to retrieve DataTypeTemplate's related to only updatable DAIs, false to retrieve all + * @return List of resumed DataTypeTemplate for DataAttribute (updatable or not) + * @throws ScdException SCD illegal arguments exception, missing mandatory data + */ public static Set getDAI(SCL scd, String iedName, String ldInst, ResumedDataTemplate rDtt, boolean updatable) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); @@ -287,6 +431,15 @@ public static Set getDAI(SCL scd, String iedName, String ld return lDeviceAdapter.getDAI(rDtt, updatable); } + /** + * Updates DAI based on given data in rDtt + * @param scd SCL file in which DataTypeTemplate of DAI should be found + * @param iedName name of IED in which DAI is localized + * @param ldInst ldInst of LDevice in which DAI is localized + * @param rDtt reference summarized DataTypeTemplate related to DAI to update + * @throws ScdException when inconsistency are found in th SCL's + * DataTypeTemplate. Which should normally not happens. + */ public static void updateDAI(SCL scd, String iedName, String ldInst, ResumedDataTemplate rDtt) throws ScdException { long startTime = System.nanoTime(); log.info(Utils.entering()); @@ -323,6 +476,13 @@ public static void updateDAI(SCL scd, String iedName, String ldInst, ResumedData log.info(Utils.leaving(startTime)); } + /** + * Gets EnumTypes values of ID idEnum from DataTypeTemplate of SCL file + * @param scd SCL file in which EnumType should be found + * @param idEnum ID of EnumType for which values are retrieved + * @return list of couple EnumType value and it's order + * @throws ScdException throws when unkonown EnumType + */ public static Set> getEnumTypeElements(SCL scd, String idEnum) throws ScdException { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); DataTypeTemplateAdapter dataTypeTemplateAdapter = sclRootAdapter.getDataTypeTemplateAdapter(); @@ -333,6 +493,32 @@ public static Set> getEnumTypeElements(SCL scd, String idE .collect(Collectors.toSet()); } + /** + * Imports IEDs, DataTypeTemplates and Communication nodes of STD files into SCL (SCD) file + * STD : System Template Definition + * To import STD into SCD, this step are realized + *
    + *
  • Check SCD and STD compatibilities by checking if there is at least one ICD_SYSTEM_VERSION_UUID in + * LNode/Private CompasICDHeader of SCL/Substation/.. not present in IED/Private CompasICDHeader of STD
  • + *
  • List all LNode/Private COMPAS-ICDHeader of SCL/Substation/.. and remove duplicated one with same iedName in order to + * ovoid repetition in actions
  • + *
  • For each Private.ICDSystemVersionUUID and Private.iedName in Substation/ of SCL find corresponding STD File
  • + *
  • import /IED /DataTypeTemplate from that STD file and update IEDName of /IED in SCD file
  • + *
  • import connectedAP and rename ConnectedAP/@iedName in Communication node in SCD file
  • + *
+ * @param scdRootAdapter adapter object related to SCL file in which content of STD files are imported + * @param stds list of STD files contenting datas to import into SCD + * @param comMap couple of Subnetwork name and possible corresponding ConnectAP names + * @return updated SCD file as SclRootAdapter + * @throws ScdException throws when inconsistency between Substation of SCL content and gien STD files as : + *
    + *
  • ICD_SYSTEM_VERSION_UUID in IED/Private of STD is not present in COMPAS-ICDHeader in Substation/../LNode of SCL
  • + *
  • There are several STD files corresponding to ICD_SYSTEM_VERSION_UUID of COMPAS-ICDHeader in Substation/../LNode of SCL
  • + *
  • There is no STD file found corresponding to COMPAS-ICDHeader in Substation/../LNode of SCL
  • + *
  • COMPAS-ICDHeader is not the same in Substation/../LNode of SCL and in IED/Private of STD
  • + *
  • COMPAS_ICDHEADER in Substation/../LNode of SCL not found in IED/Private of STD
  • + *
+ */ public static SclRootAdapter importSTDElementsInSCD(@NonNull SclRootAdapter scdRootAdapter, Set stds, Map, List> comMap) throws ScdException { @@ -369,6 +555,13 @@ public static SclRootAdapter importSTDElementsInSCD(@NonNull SclRootAdapter scdR return scdRootAdapter; } + /** + * Checks SCD and STD compatibilities by checking if there is at least one ICD_SYSTEM_VERSION_UUID in + * Substation/../LNode/Private COMPAS-ICDHeader of SCL not present in IED/Private COMPAS-ICDHeader of STD + * @param mapICDSystemVersionUuidAndSTDFile map of ICD_SYSTEM_VERSION_UUID and list of corresponding STD + * @throws ScdException throws when there are several STD files corresponding to ICD_SYSTEM_VERSION_UUID + * from Substation/../LNode/Private COMPAS-ICDHeader of SCL + */ private static void checkSTDCorrespondanceWithLNodeCompasICDHeader(Map>> mapICDSystemVersionUuidAndSTDFile) throws ScdException { for (Pair> pairOfPrivateAndSTDs : mapICDSystemVersionUuidAndSTDFile.values()) { if (pairOfPrivateAndSTDs.getRight().size() != 1) { @@ -378,6 +571,12 @@ private static void checkSTDCorrespondanceWithLNodeCompasICDHeader(Map optionalCompasICDHeader = PrivateService.getCompasICDHeader(key); return HEADER_ID + " = " + optionalCompasICDHeader.map(TCompasICDHeader::getHeaderId).orElse(null) + @@ -386,6 +585,11 @@ private static String stdCheckFormatExceptionMessage(TPrivate key) throws ScdExc "and " + ICD_SYSTEM_VERSION_UUID + " = " + optionalCompasICDHeader.map(TCompasICDHeader::getICDSystemVersionUUID).orElse(null); } + /** + * Creates map of IEDName and related Private for all Privates COMPAS-ICDHeader in /Substation of SCL + * @param scdRootAdapter SCL file in which Private should be found + * @return map of Private and its IEDName parameter + */ private static Map createMapIEDNameAndPrivate(SclRootAdapter scdRootAdapter) { return scdRootAdapter.getCurrentElem().getSubstation().get(0).getVoltageLevel().stream() .map(TVoltageLevel::getBay).flatMap(Collection::stream) @@ -398,6 +602,12 @@ private static Map createMapIEDNameAndPrivate(SclRootAdapter s .collect(Collectors.toMap(tPrivate -> PrivateService.getCompasICDHeader(tPrivate).get().getIEDName(), Function.identity())); } + /** + * Sorts in map of ICD_SYSTEM_VERSION_UUID and related Private coupled with all corresponding STD for all given STD + * @param stds list of STD to short + * @return map of ICD_SYSTEM_VERSION_UUID attribute in IED/Private:COMPAS-ICDHeader and related Private coupled with + * all corresponding STD + */ private static Map>> createMapICDSystemVersionUuidAndSTDFile(Set stds) { Map>> stringSCLMap = new HashMap<>(); stds.forEach(std -> std.getIED().forEach(ied -> ied.getPrivate().forEach(tp -> @@ -411,6 +621,13 @@ private static Map>> createMapICDSystemVersionU return stringSCLMap; } + /** + * Compares if two Private:COMPAS-ICDHeader have all attributes equal except IEDNane, BayLabel and IEDinstance + * @param iedPrivate Private of IED from STD to compare + * @param scdPrivate Private of LNode fro SCD to compare + * @return Boolean value of check result + * @throws ScdException throws when Private is not COMPAS_ICDHEADER one + */ private static boolean comparePrivateCompasICDHeaders(TPrivate iedPrivate, TPrivate scdPrivate) throws ScdException { TCompasICDHeader iedCompasICDHeader = PrivateService.getCompasICDHeader(iedPrivate).orElseThrow( () -> new ScdException(COMPAS_ICDHEADER + "not found in IED Private ")); @@ -428,6 +645,12 @@ private static boolean comparePrivateCompasICDHeaders(TPrivate iedPrivate, TPriv && iedCompasICDHeader.getHeaderVersion().equals(scdCompasICDHeader.getHeaderVersion()); } + /** + * Copy Private COMPAS_ICDHEADER from LNode of SCD into Private COMPAS_ICDHEADER from IED of STD + * @param stdPrivate Private of IED from STD in which to copy new data + * @param lNodePrivate Private of IED from STD from which new data are taken + * @throws ScdException throws when Private is not COMPAS_ICDHEADER one + */ private static void copyCompasICDHeaderFromLNodePrivateIntoSTDPrivate(TPrivate stdPrivate, TPrivate lNodePrivate) throws ScdException { TCompasICDHeader lNodeCompasICDHeader = PrivateService.getCompasICDHeader(lNodePrivate).orElseThrow( () -> new ScdException(COMPAS_ICDHEADER + " not found in LNode Private ")); @@ -435,6 +658,10 @@ private static void copyCompasICDHeaderFromLNodePrivateIntoSTDPrivate(TPrivate s stdPrivate.getContent().add(objectFactory.createICDHeader(lNodeCompasICDHeader)); } + /** + * Removes all ControlBlocks and DataSets for all LNs in SCL + * @param scl SCL file for which ControlBlocks and DataSets should be deleted + */ public static void removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(final SCL scl) { SclRootAdapter sclRootAdapter = new SclRootAdapter(scl); List lDeviceAdapters = sclRootAdapter.getIEDAdapters().stream() @@ -454,4 +681,27 @@ public static void removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(final S .forEach(LNAdapter::removeAllControlBlocksAndDatasets); } + /** + * Activate used LDevice and Deactivate unused LDevice in {@link TLNode TLNode } + * @param scd SCL file for which LDevice should be activated or deactivated + * @return SclReport Object that contain SCL file and set of errors + */ + public static SclReport updateLDeviceStatus(SCL scd) { + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + SubstationAdapter substationAdapter = sclRootAdapter.getSubstationAdapter(); + final List> iedNameLdInstList = substationAdapter.getIedAndLDeviceNamesForLN0FromLNode(); + List errors = sclRootAdapter.getIEDAdapters().stream() + .map(IEDAdapter::getLDeviceAdapters) + .flatMap(Collection::stream) + .map(LDeviceAdapter::getLN0Adapter) + .map(ln0Adapter -> ln0Adapter.checkAndUpdateLDeviceStatus(iedNameLdInstList)) + .reduce(new ArrayList<>(),(sclReportErrors, partialSclReportErrors) -> { + sclReportErrors.addAll(partialSclReportErrors); + return sclReportErrors; + }); + SclReport sclReport = new SclReport(); + sclReport.getErrorDescriptionList().addAll(errors); + sclReport.setSclRootAdapter(sclRootAdapter); + return sclReport; + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SubstationService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SubstationService.java index 3b27c0afe..8316d957a 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SubstationService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SubstationService.java @@ -11,18 +11,44 @@ import org.lfenergy.compas.scl2007b4.model.TSubstation; import org.lfenergy.compas.scl2007b4.model.TVoltageLevel; import org.lfenergy.compas.sct.commons.exception.ScdException; -import org.lfenergy.compas.sct.commons.scl.sstation.BayAdapter; -import org.lfenergy.compas.sct.commons.scl.sstation.FunctionAdapter; import org.lfenergy.compas.sct.commons.scl.sstation.SubstationAdapter; import org.lfenergy.compas.sct.commons.scl.sstation.VoltageLevelAdapter; +/** + * A representation of the {@link SubstationService SubstationService}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link SubstationService#addSubstation(SCL, SCL) Adds the Substation object from given SCL object}
  • + *
  • {@link SubstationService#updateVoltageLevel(SubstationAdapter, TVoltageLevel) Adds the TVoltageLevel element under TSubstation reference object}
  • + *
  • {@link SubstationService#updateBay(VoltageLevelAdapter, TBay) Adds the TBay element under TVoltageLevel reference object}
  • + *
+ * @see org.lfenergy.compas.sct.commons.scl.sstation.SubstationAdapter + * @see org.lfenergy.compas.sct.commons.scl.sstation.VoltageLevelAdapter + * @see org.lfenergy.compas.sct.commons.scl.sstation.BayAdapter + * @see org.lfenergy.compas.sct.commons.scl.sstation.FunctionAdapter + * @see org.lfenergy.compas.sct.commons.scl.sstation.LNodeAdapter + * @see org.lfenergy.compas.sct.commons.scl.PrivateService + */ @Slf4j public final class SubstationService { + /** + * Private Controlller, should not be instanced + */ private SubstationService() { throw new UnsupportedOperationException("This service class cannot be instantiated"); } + /** + * Adds or Updates Substation section in SCL + * @param scd SCL file in which Substation should be added/updated + * @param ssd SCL file from which Substation should be copied + * @return SclRootAdapter object as SCD file + * @throws ScdException throws when SCD contents already another Substation, or with different name, or contents + * more than one Substation + */ public static SclRootAdapter addSubstation(@NonNull SCL scd, @NonNull SCL ssd) throws ScdException { SclRootAdapter scdRootAdapter = new SclRootAdapter(scd); SclRootAdapter ssdRootAdapter = new SclRootAdapter(ssd); @@ -52,6 +78,12 @@ public static SclRootAdapter addSubstation(@NonNull SCL scd, @NonNull SCL ssd) t return scdRootAdapter; } + /** + * Creates new VoltageLevel section or updates VoltageLevel contents + * @param scdSubstationAdapter Substation in which VoltageLevel should be created/updated + * @param vl VoltageLevel to create/update + * @throws ScdException throws when unable to create new VoltageLevel section which is not already present in Substation + */ private static void updateVoltageLevel(@NonNull SubstationAdapter scdSubstationAdapter, TVoltageLevel vl) throws ScdException { if (scdSubstationAdapter.getVoltageLevelAdapter(vl.getName()).isPresent()) { VoltageLevelAdapter scdVoltageLevelAdapter = scdSubstationAdapter.getVoltageLevelAdapter(vl.getName()) @@ -64,6 +96,11 @@ private static void updateVoltageLevel(@NonNull SubstationAdapter scdSubstationA } } + /** + * Adds new Bay in VoltageLevel or if already exist removes and replaces it + * @param scdVoltageLevelAdapter VoltageLevel in which Bay should be created/updated + * @param tBay Bay to add + */ private static void updateBay(@NonNull VoltageLevelAdapter scdVoltageLevelAdapter, TBay tBay) { if (scdVoltageLevelAdapter.getBayAdapter(tBay.getName()).isPresent()) { scdVoltageLevelAdapter.getCurrentElem().getBay() @@ -74,16 +111,4 @@ private static void updateBay(@NonNull VoltageLevelAdapter scdVoltageLevelAdapte } } - public static void updateLNodeIEDNames(SCL scd) throws ScdException { - if (!scd.isSetSubstation()) { - return; - } - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - - scd.getSubstation().stream().map(tSubstation -> new SubstationAdapter(sclRootAdapter, tSubstation)) - .flatMap(SubstationAdapter::streamVoltageLevelAdapters) - .flatMap(VoltageLevelAdapter::streamBayAdapters) - .flatMap(BayAdapter::streamFunctionAdapters) - .forEach(FunctionAdapter::updateLNodeIedNames); - } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapter.java index e4b5caf42..0bd5a4661 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapter.java @@ -16,22 +16,69 @@ import java.util.Optional; import java.util.stream.Collectors; - +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TCommunication Communication}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link CommunicationAdapter#addSubnetwork add Subnetwork under this object}
  • + *
  • {@link CommunicationAdapter#addPrivate Add TPrivate under this object}
  • + *
+ * + * @see org.lfenergy.compas.sct.commons.scl.SclRootAdapter + * @see org.lfenergy.compas.scl2007b4.model.TCommunication + */ public class CommunicationAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + */ public CommunicationAdapter(SclRootAdapter parentAdapter) { super(parentAdapter); } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public CommunicationAdapter(SclRootAdapter parentAdapter, TCommunication currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override public boolean amChildElementRef() { return currentElem == parentAdapter.getCurrentElem().getCommunication(); } + @Override + protected String elementXPath() { + return "Communication"; + } + + /** + * Add Subnetwork node in Communication one. + * For that : + *
    + *
  • coherence is checked first one AccessPoint's name between given + * * data and IED/Services/AccessPoint
  • + *
  • If good, Subnetworks are created
  • + *
  • And then AccessPoint are created two in the Subnetwork
  • + *
. + * @param snName Subnetwork name + * @param snType Subnetwork type + * @param iedName IED name + * @param apName AccessPoint name + * @return SubNetworkAdapter Current reference + * @throws ScdException + */ public SubNetworkAdapter addSubnetwork(String snName, String snType, String iedName, String apName) throws ScdException { @@ -53,6 +100,11 @@ public SubNetworkAdapter addSubnetwork(String snName, String snType, return opSubNetworkAdapter.get(); } + /** + * Gets Subnetwork by name from Communication in an adapter wrapper + * @param snName Subnetwork name + * @return Optional SubNetworkAdapter object + */ public Optional getSubnetworkByName(String snName) { return currentElem.getSubNetwork() .stream() @@ -61,6 +113,10 @@ public Optional getSubnetworkByName(String snName) { .map(tSubNetwork -> new SubNetworkAdapter(this, tSubNetwork)); } + /** + * Gets all Subnetworks from Communication node in an adapter wrapper + * @return the value of the SubNetworkAdapter containment reference list. + */ public List getSubNetworkAdapters() { return currentElem.getSubNetwork() .stream() diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java index e62b5962e..3f4a076c3 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java @@ -7,28 +7,73 @@ import org.lfenergy.compas.scl2007b4.model.SCL; import org.lfenergy.compas.scl2007b4.model.TConnectedAP; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.Optional; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TConnectedAP ConnectedAP}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link ConnectedAPAdapter#addPrivate Add TPrivate under this object}
  • + *
  • {@link ConnectedAPAdapter#copyAddressAndPhysConnFromIcd copy Address And PhysConn From Icd}
  • + *
+ * + * @see org.lfenergy.compas.sct.commons.scl.com.SubNetworkAdapter + * @see org.lfenergy.compas.scl2007b4.model.TConnectedAP + */ public class ConnectedAPAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public ConnectedAPAdapter(SubNetworkAdapter parentAdapter, TConnectedAP currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getConnectedAP().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("ConnectedAP[%s and %s]", + Utils.xpathAttributeFilter("apName", currentElem.isSetApName() ? currentElem.getApName() : null), + Utils.xpathAttributeFilter("iedName", currentElem.isSetIedName() ? currentElem.getApName() : null)); + } + + /** + * Returns the value of the iedName attribute. + * @return the value of the iedName attribute. + */ public String getIedName() { return currentElem.getIedName(); } + /** + * Returns the value of the apName attribute. + * @return the value of the apName attribute. + */ public String getApName() { return currentElem.getApName(); } + /** + * Copies Address and PhysicalConnection nodes from ICD file + * @param icd ICD file + * @see Issue !76 + * Copies Address and PhysicalConnection nodes from ICD file + */ public void copyAddressAndPhysConnFromIcd(Optional icd) { if (icd.isPresent() && icd.get().getCommunication() != null) { icd.stream() diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java index 4f06df43a..efe7a9838 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java @@ -9,28 +9,60 @@ import org.lfenergy.compas.scl2007b4.model.TSubNetwork; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TSubNetwork SubNetwork}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link SubNetworkAdapter#addPrivate Add TPrivate under this object}
  • + *
  • {@link SubNetworkAdapter#addConnectedAP add ConnectedAP under this object}
  • + *
+ * + * @see org.lfenergy.compas.sct.commons.scl.com.CommunicationAdapter + * @see org.lfenergy.compas.scl2007b4.model.TSubNetwork + */ public class SubNetworkAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public SubNetworkAdapter(CommunicationAdapter parentAdapter, @NonNull TSubNetwork currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSubNetwork().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("SubNetwork[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + /** - * Create a Connected Access Point for this subnetwork. + * Create a Connected Access Point for this subnetwork.
+ *

* Note : this method doesn't check the validity on neither the IED name nor the access point name. - * @param iedName - * @param apName - * @return + *

+ * @param iedName input + * @param apName input + * @return the ConnectedAPAdapter object */ public ConnectedAPAdapter addConnectedAP(@NonNull String iedName, @NonNull String apName) { TConnectedAP tConnectedAP = currentElem.getConnectedAP().stream() @@ -48,14 +80,26 @@ public ConnectedAPAdapter addConnectedAP(@NonNull String iedName, @NonNull Strin return new ConnectedAPAdapter(this,tConnectedAP); } + /** + * Returns the value of the name attribute. + * @return the value of the name attribute. + */ public String getName() { return currentElem.getName(); } + /** + * Returns the value of the type attribute. + * @return the value of the type attribute. + */ public String getType() { return currentElem.getType(); } + /** + * Returns the value of the ConnectedAPAdapter containment reference list. + * @return the value of the ConnectedAPAdapter containment reference list. + */ public List getConnectedAPAdapters() { return currentElem.getConnectedAP() .stream() @@ -63,6 +107,13 @@ public List getConnectedAPAdapters() { .collect(Collectors.toList()); } + /** + * Gets ConnectedAP from Subnetwork + * @param iedName IED name + * @param apName AccessPoint name + * @return the ConnectedAPAdapter object + * @throws ScdException + */ public ConnectedAPAdapter getConnectedAPAdapter(String iedName, String apName) throws ScdException { return currentElem.getConnectedAP() .stream() diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/package-info.java new file mode 100644 index 000000000..fbf637d8e --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/package-info.java @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

scl.com is a group of services for operating on + * {@link org.lfenergy.compas.scl2007b4.model.TCommunication Communication} object + *

+ * @see org.lfenergy.compas.scl2007b4.model.TCommunication + * @see org.lfenergy.compas.scl2007b4.model.TSubNetwork + * @see org.lfenergy.compas.scl2007b4.model.TConnectedAP + * @see Issue !76 + */ +package org.lfenergy.compas.sct.commons.scl.com; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataAttributeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataAttributeAdapter.java index e0d745d32..fb5eccc72 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataAttributeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataAttributeAdapter.java @@ -13,6 +13,34 @@ import java.util.Objects; import java.util.Optional; + +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.dtt.AbstractDataAttributeAdapter AbstractDataAttributeAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link AbstractDataAttributeAdapter#getDataTypeTemplateAdapter get DataTypeTemplateAdapter}
    • + *
    • {@link AbstractDataAttributeAdapter#getDATypeAdapter get DATypeAdapter}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link AbstractDataAttributeAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link AbstractDataAttributeAdapter#hasSameContentAs Compare Two SCL element}
    • + *
    • {@link AbstractDataAttributeAdapter#check Check structData from DaTypeName}
    • + *
    + *
+ * @see org.lfenergy.compas.sct.commons.scl.SclElementAdapter + * @see org.lfenergy.compas.scl2007b4.model.TAbstractDataAttribute + * @see org.lfenergy.compas.sct.commons.scl.dtt.IDataTemplate + * @see org.lfenergy.compas.sct.commons.scl.dtt.IDTTComparable + */ @Getter public abstract class AbstractDataAttributeAdapter

extends SclElementAdapter @@ -20,21 +48,44 @@ public abstract class AbstractDataAttributeAdapter

getDATypeAdapter() { if(isTail()){ return Optional.empty(); @@ -42,7 +93,11 @@ public Optional getDATypeAdapter() { return getDataTypeTemplateAdapter().getDATypeAdapterById(getType()); } - + /** + * Cheeks if DataAttributes have the same contents + * @param data input + * @return Equality state + */ public boolean hasSameContentAs(T data) { if(!Objects.equals(getName(),data.getName()) || !Objects.equals(getBType(),data.getBType()) @@ -94,12 +149,20 @@ public boolean hasSameContentAs(T data) { return true; } + /** + * Gets DataTypeTemplateAdapter + * @return DataTypeTemplateAdapter object + */ @Override public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return ((IDataTemplate)parentAdapter).getDataTypeTemplateAdapter(); } - + /** + * Updates DA Type Name + * @param daTypeName DA Type Name to update + * @throws ScdException + */ public void check(DaTypeName daTypeName) throws ScdException { if(getBType() == TPredefinedBasicTypeEnum.ENUM){ @@ -125,8 +188,10 @@ public void check(DaTypeName daTypeName) throws ScdException { daTypeName.setValImport(currentElem.isValImport()); } - - + /** + * Checks valImport state + * @return boolean value of valImport + */ public boolean isValImport() { return currentElem.isValImport(); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataTypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataTypeAdapter.java index cfb720497..63574765c 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataTypeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/AbstractDataTypeAdapter.java @@ -6,9 +6,39 @@ import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.dtt.AbstractDataTypeAdapter DataTemplate}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link AbstractDataTypeAdapter#getDataTypeTemplateAdapter get DataTypeTemplateAdapter}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link AbstractDataTypeAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link AbstractDataTypeAdapter#hasSameContentAs Compare Two SCL element}
    • + *
    + *
+ * @see org.lfenergy.compas.sct.commons.scl.SclElementAdapter + * @see org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter + * @see org.lfenergy.compas.sct.commons.scl.dtt.IDataTemplate + * @see org.lfenergy.compas.sct.commons.scl.dtt.IDTTComparable + */ public abstract class AbstractDataTypeAdapter extends SclElementAdapter implements IDataTemplate, IDTTComparable{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected AbstractDataTypeAdapter(DataTypeTemplateAdapter parentAdapter, T currentElem) { super(parentAdapter, currentElem); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapter.java index 55fdbb348..a07d04571 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapter.java @@ -8,20 +8,68 @@ import org.lfenergy.compas.scl2007b4.model.TDA; import org.lfenergy.compas.sct.commons.dto.DaTypeName; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.util.Utils; +/** + * A representation of the model object {@link org.lfenergy.compas.scl2007b4.model.TDA DA}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DAAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    • {@link DAAdapter#getDATypeAdapter() Returns the value of the DATypeAdapter reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DAAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link DAAdapter#getType() Returns the value of the type attribute}
    • + *
    • {@link DAAdapter#getName Returns the value of the type name}
    • + *
    • {@link DAAdapter#getBType Returns the value of the bType attribute}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link DAAdapter#hasSameContentAs Compare Two TAbstractDataAttribute }
    • + *
    + *
+ * @see org.lfenergy.compas.scl2007b4.model.TAbstractDataAttribute + * @see org.lfenergy.compas.scl2007b4.model.TDA + * @see org.lfenergy.compas.scl2007b4.model.TBDA + * @see org.lfenergy.compas.scl2007b4.model.TSDO + */ @Getter public class DAAdapter extends AbstractDataAttributeAdapter implements IDataTemplate, IDTTComparable { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected DAAdapter(DOTypeAdapter parentAdapter, TDA currentElem) { super(parentAdapter, currentElem); } - - + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSDOOrDA().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DA[name=%s and type=%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null), + Utils.xpathAttributeFilter("type", currentElem.isSetType() ? currentElem.getType() : null)); + } + + /** + * Updates DA Type Name + * @param daTypeName DA Type Name to update + * @throws ScdException + */ @Override public void check(DaTypeName daTypeName) throws ScdException { super.check(daTypeName); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapter.java index cfe8ce5e2..67428e237 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapter.java @@ -14,6 +14,7 @@ import org.lfenergy.compas.sct.commons.dto.DaTypeName; import org.lfenergy.compas.sct.commons.dto.ResumedDataTemplate; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.ArrayList; import java.util.List; @@ -21,13 +22,53 @@ import java.util.Optional; import java.util.stream.Collectors; +/** + * A representation of the model object {@link org.lfenergy.compas.scl2007b4.model.TDAType DAType}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DATypeAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    • {@link DATypeAdapter#getBdaAdapterByName Returns the value of the BDAAdapter reference object By BDA name}
    • + *
    • {@link DATypeAdapter#getBdaAdapters Returns the value of the BDAAdapters containment reference list}
    • + *
    • {@link DATypeAdapter#getDATypeAdapterByBdaName Returns the value of the DATypeAdapter reference object By BDA name/em>}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DATypeAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link DATypeAdapter#getBDAByName Returns the value of the TBDA reference object By name}
    • + *
    • {@link DATypeAdapter#getResumedDTTByDaName Returns List Of ResumedDataTemplate By DaTypeName }
    • + *
    • {@link DATypeAdapter#getResumedDTTs Returns List Of ResumedDataTemplate By Custom filter}
    • + *
    • {@link DATypeAdapter#completeResumedDTT Returns Completed list Of ResumedDataTemplate }
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link DATypeAdapter#hasSameContentAs Compare Two TDA}
    • + *
    • {@link DATypeAdapter#check Check structData from DaTypeName}
    • + *
    • {@link DATypeAdapter#containsStructBdaWithDATypeId Check whether TDA contain TBDA with Struct Btype By Id}
    • + *
    • {@link DATypeAdapter#containsBDAWithEnumTypeID Check whether TDAType contain contain TEnumType By Id}
    • + *
    + *
+ */ @Slf4j public class DATypeAdapter extends AbstractDataTypeAdapter{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public DATypeAdapter(DataTypeTemplateAdapter parentAdapter, TDAType currentElem) { super(parentAdapter, currentElem); } + /** + * Completes recursively given summarized DataTypeTemplate information from BDAs + * @param rDtt summarized DataTypeTemplate to complete + * @return list of completed (updated) summarized DataTypeTemplate + */ public List completeResumedDTT(ResumedDataTemplate rDtt) { List result = new ArrayList<>(); for(BDAAdapter bdaAdapter : getBdaAdapters()){ @@ -105,11 +146,25 @@ public Optional getResumedDTTByDaName(DaTypeName daTypeName } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getDAType().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DAType[%s]", + Utils.xpathAttributeFilter("id", currentElem.isSetId() ? currentElem.getId() : null)); + } + + /** + * Gets all BDAs from current DAType + * @return list of linked BDA as BDAAdapter object + */ public List getBdaAdapters(){ return currentElem.getBDA() .stream() @@ -117,16 +172,25 @@ public List getBdaAdapters(){ .collect(Collectors.toList()); } - public Optional getBDAByName(String sdoName) { + /** + * Gets BDA by name + * @param bdaName BDA name + * @return Optional TBDA object + */ + public Optional getBDAByName(String bdaName) { for(TBDA tbda : currentElem.getBDA()){ - if(tbda.getName().equals(sdoName)){ + if(tbda.getName().equals(bdaName)){ return Optional.of(tbda); } } return Optional.empty(); } - + /** + * Checks if current DAType contains BDA with specific EnumType + * @param enumTypeId ID of EnumType in BDA to check + * @return Boolean value of check result + */ public boolean containsBDAWithEnumTypeID(String enumTypeId) { return currentElem.getBDA() .stream() @@ -136,6 +200,11 @@ public boolean containsBDAWithEnumTypeID(String enumTypeId) { ); } + /** + * Checks if current DAType contains StructBDA + * @param daTypeId ID of DAType (which type is Struct) + * @return Boolean value of check result + */ public Boolean containsStructBdaWithDATypeId(String daTypeId) { return currentElem.getBDA() .stream() @@ -145,6 +214,11 @@ public Boolean containsStructBdaWithDATypeId(String daTypeId) { ); } + /** + * Compares current DAType and given DAType + * @param inputDAType DAType to compare with + * @return Boolean value of comparison result + */ @Override public boolean hasSameContentAs(TDAType inputDAType) { if(!DataTypeTemplateAdapter.hasSamePrivates(currentElem,inputDAType) || @@ -175,6 +249,11 @@ public boolean hasSameContentAs(TDAType inputDAType) { return true; } + /** + * Check if DaTypeName is correct and coherent with this DATypeAdapter + * @param daTypeName string containing all BDA/DA names to check + * @throws ScdException throws when DaTypeName structured names is not well-ordered + */ public void check(DaTypeName daTypeName) throws ScdException { int sz= daTypeName.getStructNames().size(); String strBDAs = StringUtils.join(daTypeName.getStructNames()); @@ -232,6 +311,11 @@ public List getResumedDTTs(ResumedDataTemplate rootRDTT, Re return resultRDTTs; } + /** + * Gets DATypeAdapter by BDA name + * @param name BDA name + * @return Optional of DATypeAdapter object + */ public Optional getDATypeAdapterByBdaName(String name) { Optional opBda = getBDAByName(name); if(opBda.isPresent()){ @@ -240,12 +324,20 @@ public Optional getDATypeAdapterByBdaName(String name) { return Optional.empty(); } - + /** + * Gets linked DataTypeTemplateAdapter as parent + * @return DataTypeTemplateAdapter object + */ @Override public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return parentAdapter; } + /** + * Gets BDAAdapter by name + * @param name BDAAdapter name + * @return Optiobnal of BDAAdapter object + */ public Optional getBdaAdapterByName(String name) { Optional opBda = getBDAByName(name); if(opBda.isPresent()){ @@ -254,19 +346,62 @@ public Optional getBdaAdapterByName(String name) { return Optional.empty(); } - - + /** + * A representation of the model object {@link org.lfenergy.compas.scl2007b4.model.TBDA BDA}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link BDAAdapter#getDataTypeTemplateAdapter get DataTypeTemplateAdapter}
    • + *
    • {@link BDAAdapter#getBdaAdapterByName get BdaAdapter By Name}
    • + *
    • {@link BDAAdapter#getBdaAdapters get getBdaAdapters}
    • + *
    • {@link BDAAdapter#getDATypeAdapterByBdaName get DATypeAdapter By TBDA Name}
    • + *
    + *
  3. Functions
  4. + *
      + *
    • {@link BDAAdapter#addPrivate add Private}
    • + *
    • {@link BDAAdapter#getBDAByName get TBDA By Name}
    • + *
    • {@link BDAAdapter#getResumedDTTByDaName get ResumedDTT By DaTypeName}
    • + *
    • {@link BDAAdapter#getResumedDTTs get ResumedDTTs By Custom filter}
    • + *
    • {@link BDAAdapter#completeResumedDTT Construct and Complete ResumedDTTs}
    • + *
    + *
  5. Check rules
  6. + *
      + *
    • {@link BDAAdapter#hasSameContentAs Compare Two TBDA}
    • + *
    • {@link BDAAdapter#check Check structData from DaTypeName}
    • + *
    • {@link BDAAdapter#containsStructBdaWithDATypeId Check whether TBDA contain TBDA with Struct Btype By Id}
    • + *
    • {@link BDAAdapter#containsBDAWithEnumTypeID Check whether TBDA contain contain TEnumType By Id}
    • + *
    + *
+ */ @Getter public static class BDAAdapter extends AbstractDataAttributeAdapter{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected BDAAdapter(DATypeAdapter parentAdapter, TBDA currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getBDA().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("BDA[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapter.java index b07157329..131781765 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapter.java @@ -6,28 +6,82 @@ import org.lfenergy.compas.scl2007b4.model.TDO; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.Optional; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TDO DO}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DOAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    • {@link DOAdapter#getDoTypeAdapter() Returns the value of the DoTypeAdapter reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DOAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link DOAdapter#getType Returns the value of the type attribute}
    • + *
    + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TUnNaming + * @see org.lfenergy.compas.scl2007b4.model.TDA + * @see org.lfenergy.compas.scl2007b4.model.TBDA + * @see org.lfenergy.compas.scl2007b4.model.TSDO + */ public class DOAdapter extends SclElementAdapter implements IDataTemplate{ + + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currElement Current reference + */ protected DOAdapter(LNodeTypeAdapter parentAdapter, TDO currElement) { super(parentAdapter,currElement); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getDO().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DO[%s and %s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null), + Utils.xpathAttributeFilter("type", currentElem.isSetType() ? currentElem.getType() : null)); + } + + /** + * Gets linked DataTypeTemplateAdapter as parent + * @return DataTypeTemplateAdapter object + */ @Override public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return parentAdapter.getDataTypeTemplateAdapter(); } + /** + * Gets DOTypeAdapter from parent DataTypeTemplate + * @return Optional of DOTypeAdapter object + */ public Optional getDoTypeAdapter() { return getDataTypeTemplateAdapter().getDOTypeAdapterById(currentElem.getType()); } + /** + * Gets DO element type + * @return type of DO + */ public String getType() { return currentElem.getType(); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapter.java index e242b66a9..a2e675e29 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapter.java @@ -11,18 +11,66 @@ import org.lfenergy.compas.sct.commons.dto.DoTypeName; import org.lfenergy.compas.sct.commons.dto.ResumedDataTemplate; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.*; import java.util.stream.Collectors; - +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TDOType DOType}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DOTypeAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    • {@link DOTypeAdapter#getDAAdapterByName Returns the value of the DAAdapter reference object By DA name }
    • + *
    • {@link DOTypeAdapter#getDOTypeAdapterBySdoName Returns the value of the DOTypeAdapter reference object By SDO name }
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DOTypeAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link DOTypeAdapter#getDAByName Returns the value of the TDA reference object By name}
    • + *
    • {@link DOTypeAdapter#getResumedDTTByDaName Returns ResumedDTT By DaTypeName }
    • + *
    • {@link DOTypeAdapter#getResumedDTTByDoName Returns ResumedDTT By DoTypeName }
    • + *
    • {@link DOTypeAdapter#getResumedDTTs Returns List Of ResumedDTT By Custom filter}
    • + *
    • {@link DOTypeAdapter#getResumedDTTsOfDA Returns List Of ResumedDTT By DA Object}
    • + *
    • {@link DOTypeAdapter#getSdoOrDAs Returns List of TSDO or TDA }
    • + *
    • {@link DOTypeAdapter#getCdc Returns the value of the cdc attribute}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link DOTypeAdapter#hasSameContent Compare two TSDO or Two TDA}
    • + *
    • {@link DOTypeAdapter#hasSameContentAs Compare Two TDOType}
    • + *
    • {@link DOTypeAdapter#checkAndCompleteStructData Check and Complete structData from DoTypeName}
    • + *
    • {@link DOTypeAdapter#containsDAStructWithDATypeId Check whether TDOType contain TDA with Struct Btype By Id}
    • + *
    • {@link DOTypeAdapter#containsSDOWithDOTypeId Check whether TDOType contain TSDO By Id}
    • + *
    • {@link DOTypeAdapter#containsDAWithDAName Check whether TDOType contain TDA By Name}
    • + *
    • {@link DOTypeAdapter#containsDAWithEnumTypeId Check whether TDOType contain TEnumType By Id}
    • + *
    + *
+ */ @Slf4j public class DOTypeAdapter extends AbstractDataTypeAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public DOTypeAdapter(DataTypeTemplateAdapter parentAdapter, TDOType currentElem) { super(parentAdapter, currentElem); } + /** + * complete the input resumed data type template from DataTypeTemplate filtered out by the given DoTypeName + * @param doTypeName the Data object, eventually with DOs + * @param idx index of the DOs in the given DoTypeName + * @param rDtt Resumed Data Template to complete + * @return completed Resumed Data Template or null if the filter constrains are not met + */ public List getResumedDTTByDoName(DoTypeName doTypeName, int idx, ResumedDataTemplate rDtt) { int sz = doTypeName.getStructNames().size(); @@ -71,6 +119,13 @@ public List getResumedDTTByDoName(DoTypeName doTypeName, in return result; } + /** + * Completes recursively summarize Data Type Templates (rdtt) from DOType to specified DaTypeName. + * @param daTypeName the Data object, eventually with DA + * @param rDtt reference Resumed Data Type Template to complete + * @return optional of ResumedDataTemplate object + * @throws ScdException + */ public Optional getResumedDTTByDaName(DaTypeName daTypeName, ResumedDataTemplate rDtt) throws ScdException { if(!rDtt.getDoName().isDefined()) { @@ -120,11 +175,27 @@ public Optional getResumedDTTByDaName(DaTypeName daTypeName return Optional.empty(); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getDOType().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DOType[%s]", + Utils.xpathAttributeFilter("id", currentElem.isSetId() ? currentElem.getId() : null)); + } + + + /** + * Checks if current DOType contains DA + * @param da DA name + * @return Boolean value of check result + */ public boolean containsDAWithDAName(String da){ return currentElem.getSDOOrDA() .stream() @@ -134,6 +205,12 @@ public boolean containsDAWithDAName(String da){ tda -> tda.getName().equals(da) ); } + + /** + * Checks if current DOType contains DA with specific EnumType + * @param enumTypeId ID of EnumType in DA to check + * @return Boolean value of check result + */ public boolean containsDAWithEnumTypeId(String enumTypeId) { return currentElem.getSDOOrDA() .stream() @@ -183,7 +260,14 @@ Pair findPathDoType2DA(String daName) throws ScdException return Pair.of(currSDO.getName(),doTypeAdapter); } - + /** + * Find path from a SDO to DA (defined by names) + * @param sdoName SDO from which find a path + * @param daName DA for which find a path to + * @return pair of DO/SDO and DoType. DO/SDO references the DOType + * @throws ScdException when inconsistency are found in th SCL's + * DataTypeTemplate (unknown reference for example). Which should normally not happens. + */ Pair findPathSDO2DA(String sdoName, String daName) throws ScdException { String errMsg = String.format("No coherence or path between DO/SDO(%s) and DA(%s)", sdoName,daName); Optional opSdo = getSDOByName(sdoName); @@ -201,6 +285,11 @@ Pair findPathSDO2DA(String sdoName, String daName) throws return doTypeAdapter.findPathDoType2DA(daName); } + /** + * Checks if current DOType contains DAStruct + * @param daTypeId ID of DAType (which type is Struct) + * @return Boolean value of check result + */ public boolean containsDAStructWithDATypeId(String daTypeId) { return currentElem.getSDOOrDA() .stream() @@ -212,6 +301,11 @@ public boolean containsDAStructWithDATypeId(String daTypeId) { ); } + /** + * Compares current DOType and given DOType + * @param tdoType DOType to compare with + * @return Boolean value of comparison result + */ @Override public boolean hasSameContentAs(TDOType tdoType) { if(!DataTypeTemplateAdapter.hasSamePrivates(currentElem,tdoType)){ @@ -242,6 +336,12 @@ public boolean hasSameContentAs(TDOType tdoType) { return true; } + /** + * Compares two SDOs + * @param thisSdo SDO to compare + * @param inSdo SDO to compare + * @return Boolean value of comparison result + */ protected boolean hasSameContent(TSDO thisSdo, TSDO inSdo) { return Objects.equals(thisSdo.getName(),inSdo.getName()) && Objects.equals(thisSdo.getType(),inSdo.getType()) @@ -249,11 +349,22 @@ protected boolean hasSameContent(TSDO thisSdo, TSDO inSdo) { } + /** + * Compares DA from current DOType and given DA + * @param thisTDA DA from current DOType to compare + * @param inTDA DA to compare with + * @return Boolean value of comparison result + */ protected boolean hasSameContent(TDA thisTDA, TDA inTDA) { DAAdapter daAdapter = new DAAdapter(this,thisTDA); return daAdapter.hasSameContentAs(inTDA); } + /** + * Checks if current DOType contains SDO with specific DOTYpe ID + * @param doTypeId ID of DOType in SDO to check + * @return Boolean value of check result + */ public boolean containsSDOWithDOTypeId(String doTypeId) { List sdoList = new ArrayList<>(); for( TUnNaming unNaming : currentElem.getSDOOrDA()){ @@ -264,6 +375,13 @@ public boolean containsSDOWithDOTypeId(String doTypeId) { return sdoList.stream().anyMatch(sdo -> sdo.getType().equals(doTypeId)); } + /** + * Checks current DOType structure coherence and completes given DoTypeName with CDC value + * @param doTypeName DoTypeName to check and complete + * @return pair of last DO name in DOType. current DOTypeAdapter + * @throws ScdException when inconsistency are found in th SCL's + * DataTypeTemplate (unknown reference for example). Which should normally not happens. + */ public Optional> checkAndCompleteStructData(DoTypeName doTypeName) throws ScdException { int sz = doTypeName.getStructNames().size(); if(sz == 0){ @@ -286,6 +404,11 @@ public Optional> checkAndCompleteStructData(DoTypeNam return Optional.of(Pair.of(doTypeName.getStructNames().get(sz-1),doTypeAdapter)); } + /** + * Gets from current DOType specific SDO + * @param sdoName name of SDO to return + * @return optional of TSDO object or empty if unknown SDO name + */ public Optional getSDOByName(String sdoName) { for(TUnNaming tUnNaming : currentElem.getSDOOrDA()){ if(tUnNaming.getClass() == TSDO.class && ((TSDO)tUnNaming).getName().equals(sdoName)){ @@ -295,6 +418,11 @@ public Optional getSDOByName(String sdoName) { return Optional.empty(); } + /** + * Gets from current DOType specific DA + * @param name name of DA to return + * @return optional of TDA object or empty if unknown DA name + */ public Optional getDAByName(String name) { for(TUnNaming tUnNaming : currentElem.getSDOOrDA()){ if(tUnNaming.getClass() == TDA.class && ((TDA)tUnNaming).getName().equals(name)){ @@ -304,6 +432,10 @@ public Optional getDAByName(String name) { return Optional.empty(); } + /** + * Gets from current DOType CDC enum value + * @return TPredefinedCDCEnum corresponding value + */ public TPredefinedCDCEnum getCdc() { return currentElem.getCdc(); } @@ -336,6 +468,18 @@ public List getResumedDTTs(ResumedDataTemplate rootRDTT, Re return resultRDTTs; } + /** + * return a list of summarized Resumed Data Type Templates beginning from given DA/BDA. + *
    + *
  • If DA the list will contain only one summarized Resumed Data Type Templates
  • + *
  • If BDA list will contain all summarized Resumed Data Type Templates for each DA in BDA
  • + *
+ * @apiNote This method doesn't check relationship between DO/SDO and DA. Check should be done by caller + * @param rootRDTT reference Resumed Data Type Template used to build the list + * @param filter filter for DA/BDA + * @param da DA containing information to summarize + * @return list of completed Resumed Data Type Templates beginning from this DoType. + */ private List getResumedDTTsOfDA(ResumedDataTemplate rootRDTT, ResumedDataTemplate filter, TDA da){ if(excludedByFilter(filter, da)){ return Collections.emptyList(); @@ -356,17 +500,34 @@ private List getResumedDTTsOfDA(ResumedDataTemplate rootRDT } } + /** + * Checks if given Resumed Data Type Template contains specified DA + * @param filter Resumed Data Type Template to check contain + * @param da SDO to checked + * @return Boolean exclusion result + */ private boolean excludedByFilter(ResumedDataTemplate filter, TDA da) { return filter != null && filter.isDaNameDefined() && !filter.getDaName().getName().equals(da.getName()); } + /** + * Checks if given Resumed Data Type Template contains specified SDO + * @param filter Resumed Data Type Template to check contain + * @param tsdo SDO to checked + * @return Boolean exclusion result + */ private boolean excludedByFilter(ResumedDataTemplate filter, TSDO tsdo) { return filter != null && !filter.getSdoNames().isEmpty() && !filter.getSdoNames().contains(tsdo.getName()); } + /** + * Gets DOType from SDO + * @param name name of SDO linked to DOType + * @return optional of DOTypeAdapter object + */ public Optional getDOTypeAdapterBySdoName(String name) { Optional opSdo = getSDOByName(name); if(!opSdo.isPresent()){ @@ -375,6 +536,11 @@ public Optional getDOTypeAdapterBySdoName(String name) { return parentAdapter.getDOTypeAdapterById(opSdo.get().getType()); } + /** + * Gets DA from current DOType + * @param name name of DA to find + * @return optional of DAAdapter adapter + */ public Optional getDAAdapterByName(String name){ for(TUnNaming tUnNaming : currentElem.getSDOOrDA()){ if(tUnNaming.getClass() == TDA.class && ((TDA)tUnNaming).getName().equals(name)){ @@ -384,6 +550,10 @@ public Optional getDAAdapterByName(String name){ return Optional.empty(); } + /** + * Gets Data Type Template linked to this DOType as parent reference + * @return DataTypeTemplateAdapter object + */ @Override public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return parentAdapter; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapter.java index 160b00ff2..587f1062c 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapter.java @@ -18,19 +18,88 @@ import java.util.stream.Collectors; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TDataTypeTemplates DataTypeTemplates}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DataTypeTemplateAdapter#getLNodeTypeAdapterById Returns the value of the LNodeTypeAdapter reference object By Id}
    • + *
    • {@link DataTypeTemplateAdapter#getLNodeTypeAdapters Returns the value of the LNodeTypeAdapter containment reference list}
    • + *
    • {@link DataTypeTemplateAdapter#getDOTypeAdapterById Returns the value of the DOTypeAdapter reference object By Id}
    • + *
    • {@link DataTypeTemplateAdapter#getDOTypeAdapters Returns the value of the DOTypeAdapters containment reference list}
    • + *
    • {@link DataTypeTemplateAdapter#getDATypeAdapterById Returns the value of the DATypeAdapter reference object By Id}
    • + *
    • {@link DataTypeTemplateAdapter#getDATypeAdapters Returns the value of the DATypeAdapter containment reference list}
    • + *
    • {@link DataTypeTemplateAdapter#getEnumTypeAdapterById Returns the value of the EnumTypeAdapter reference object By Id}
    • + *
    • {@link DataTypeTemplateAdapter#getEnumTypeAdapters Returns the value of the EnumTypeAdapters containment reference list}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DataTypeTemplateAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link DataTypeTemplateAdapter#importDTT Add TDataTypeTemplates + * describing the childrens TLNodeType,TDOType,TDAType,TEnumType that can be created under this object}
    • + *
    • {@link DataTypeTemplateAdapter#importEnumType Add TDataTypeTemplates describing the children TEnumType that can be created under this object}
    • + *
    • {@link DataTypeTemplateAdapter#importLNodeType Add TDataTypeTemplates describing the children TLNodeType that can be created under this object}
    • + *
    • {@link DataTypeTemplateAdapter#importDOType Add TDataTypeTemplates describing the children TDOType that can be created under this object}
    • + *
    • {@link DataTypeTemplateAdapter#importDAType Add TDataTypeTemplates describing the children TDAType that can be created under this object}
    • + * + *
    • {@link DataTypeTemplateAdapter#findLNodeTypesFromDoWithDoTypeId Returns LNodeTypeAdapter containment reference list that match DO object And DOType Id}
    • + *
    • {@link DataTypeTemplateAdapter#findDOTypesFromSDOWithDOTypeId Returns DOTypeAdapter object Of Type SDO By DOType Id}
    • + *
    • {@link DataTypeTemplateAdapter#findDOTypesWhichDAContainsEnumTypeId Returns DOTypeAdapter containment reference list that match DA object Of Type Enum And EnumType Id}
    • + *
    • {@link DataTypeTemplateAdapter#findDATypesWhichBdaContainsEnumTypeId Returns DATypeAdapter containment reference list that match BDA object Of Type Enum And EnumType Id}
    • + *
    • {@link DataTypeTemplateAdapter#findDATypesFromStructBdaWithDATypeId Returns DATypeAdapter object that match BDA object Of Type Struct By DAType Id}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link DataTypeTemplateAdapter#hasSameID Compare Two TIDNaming}
    • + *
    • {@link DataTypeTemplateAdapter#hasSamePrivates Compare Two TDataTypeTemplateAdapter's By these Private}
    • + *
    + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TLNodeType + * @see org.lfenergy.compas.scl2007b4.model.TDOType + * @see org.lfenergy.compas.scl2007b4.model.TDAType + * @see org.lfenergy.compas.scl2007b4.model.TEnumType + * @see org.lfenergy.compas.scl2007b4.model.TDA + * @see org.lfenergy.compas.scl2007b4.model.TBDA + * @see org.lfenergy.compas.scl2007b4.model.TSDO + * @see General rules to define if two DTT are different + */ @Slf4j public class DataTypeTemplateAdapter extends SclElementAdapter { - + /** + * Constructor + * @param parentAdapter Parent container reference + * @param dataTypeTemplate Current reference + */ public DataTypeTemplateAdapter(SclRootAdapter parentAdapter, TDataTypeTemplates dataTypeTemplate) { super(parentAdapter,dataTypeTemplate); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return currentElem == parentAdapter.getCurrentElem().getDataTypeTemplates(); } + @Override + protected String elementXPath() { + return "DataTypeTemplates"; + } + + + /** + * Gets LNodeType from DataTypeTemplate by ID in LNodeTypeAdapter + * @param id LNodeType ID + * @return Optional LNodeTypeAdapter object + */ public Optional getLNodeTypeAdapterById(String id) { for(TLNodeType tlNodeType : currentElem.getLNodeType()){ if(tlNodeType.getId().equals(id)) { @@ -41,6 +110,10 @@ public Optional getLNodeTypeAdapterById(String id) { } + /** + * Gets all LNodeTypes from DataTypeTemplate in list of LNodeTypeAdapter + * @return list LNodeTypeAdapter objects + */ public List getLNodeTypeAdapters(){ return currentElem.getLNodeType() @@ -49,6 +122,11 @@ public List getLNodeTypeAdapters(){ .collect(Collectors.toList()); } + /** + * Gets DOType from DataTypeTemplate by ID in DOTypeAdapter + * @param id DO Type ID + * @return Optional DOTypeAdapter object + */ public Optional getDOTypeAdapterById(String id) { for(TDOType tdoType : currentElem.getDOType()){ if(tdoType.getId().equals(id)) { @@ -58,6 +136,10 @@ public Optional getDOTypeAdapterById(String id) { return Optional.empty(); } + /** + * Gets all DOTypes from DataTypeTemplate in list of DOTypeAdapter + * @return list DOTypeAdapter objects + */ public List getDOTypeAdapters(){ return currentElem.getDOType() .stream() @@ -65,6 +147,11 @@ public List getDOTypeAdapters(){ .collect(Collectors.toList()); } + /** + * Gets DAType from DataTypeTemplate by ID in DATypeAdapter + * @param id DA Type ID + * @return Optional DATypeAdapter object + */ public Optional getDATypeAdapterById(String id) { for(TDAType tdaType : currentElem.getDAType()){ if(tdaType.getId().equals(id)) { @@ -74,6 +161,10 @@ public Optional getDATypeAdapterById(String id) { return Optional.empty(); } + /** + * Gets all DATypes from DataTypeTemplate in list of DATypeAdapter + * @return list DATypeAdapter objects + */ public List getDATypeAdapters(){ return currentElem.getDAType() .stream() @@ -81,6 +172,11 @@ public List getDATypeAdapters(){ .collect(Collectors.toList()); } + /** + * Gets EnumType from DataTypeTemplate by ID in EnumTypeAdapter + * @param id DA Type ID + * @return Optional EnumTypeAdapter object + */ public Optional getEnumTypeAdapterById(String id) { for(TEnumType tEnumType : currentElem.getEnumType()){ if(tEnumType.getId().equals(id)) { @@ -89,6 +185,11 @@ public Optional getEnumTypeAdapterById(String id) { } return Optional.empty(); } + + /** + * Gets all EnumTypes from DataTypeTemplate in list of EnumTypeAdapter + * @return list EnumTypeAdapter objects + */ public List getEnumTypeAdapters(){ return currentElem.getEnumType() .stream() @@ -97,9 +198,9 @@ public List getEnumTypeAdapters(){ } /** - * import enum type from this DTT adapter from provider DTT adapter - * @param prvDttAdapter Adapter of the Data Type template that provides its DTT - * @return map of (old enumId, new enumId) + * Import enum type from this DataTypeTemplate adapter from provider DataTypeTemplate adapter + * @param thisIEDName IED name (in which DO Type is localized) + * @param prvDttAdapter Adapter of the Data Type template that provides its DataTypeTemplate */ public void importEnumType(String thisIEDName, DataTypeTemplateAdapter prvDttAdapter){ @@ -156,6 +257,12 @@ public void importEnumType(String thisIEDName, DataTypeTemplateAdapter prvDttAda }); } + /** + * Import DataTypeTemplate from IEDName and received DataTypeTemplate + * @param thisIEDName IED name (in which DO Type is localized) + * @param rcvDttAdapter Adapter of the Data Type template that receives its DataTypeTemplate + * @return map of (old enumId, new enumId) + */ public Map importDTT(String thisIEDName, DataTypeTemplateAdapter rcvDttAdapter) { this.importEnumType(thisIEDName,rcvDttAdapter); @@ -167,6 +274,12 @@ public Map importDTT(String thisIEDName, DataTypeTemplateAdapter return importLNodeType(thisIEDName,rcvDttAdapter); } + /** + * Import LNodeType from IEDName and received DataTypeTemplate + * @param thisIEDName IED name (in which DO Type is localized) + * @param prvDttAdapter Adapter of the Data Type template that provides its DataTypeTemplate + * @return map of (old enumId, new enumId) + */ protected Map importLNodeType(String thisIEDName, DataTypeTemplateAdapter prvDttAdapter) { Map pairOldAndNewId = new HashMap<>(); List prvLNodeTypeAdapters = prvDttAdapter.getLNodeTypeAdapters(); @@ -190,7 +303,7 @@ protected Map importLNodeType(String thisIEDName, DataTypeTempla } if(isImportable) { - //import this enumType + //import this LNodeType currentElem.getLNodeType().add(prvLNodeType); if(!Objects.equals(oldId,newId)) { pairOldAndNewId.put(oldId,newId); @@ -200,6 +313,11 @@ protected Map importLNodeType(String thisIEDName, DataTypeTempla return pairOldAndNewId; } + /** + * Import DOType from IEDName and received DataTypeTemplate + * @param thisIEDName IED name (in which DO Type is localized) + * @param prvDttAdapter Adapter of the Data Type template that provides its DataTypeTemplate + */ protected void importDOType(String thisIEDName, DataTypeTemplateAdapter prvDttAdapter) { Map pairOldAndNewDOTyYpeId = new HashMap<>(); List prvDOTypeAdapters = prvDttAdapter.getDOTypeAdapters(); @@ -224,7 +342,7 @@ protected void importDOType(String thisIEDName, DataTypeTemplateAdapter prvDttAd } if(isImportable) { - //import this enumType + //import this DOType currentElem.getDOType().add(prvDOType); if(!Objects.equals(oldId,newId)) { pairOldAndNewDOTyYpeId.put(oldId,newId); @@ -251,6 +369,11 @@ protected void importDOType(String thisIEDName, DataTypeTemplateAdapter prvDttAd }); } + /** + * Import DOType from IEDName and received DataTypeTemplate + * @param thisIEDName IED name (in which DO Type is localized) + * @param prvDttAdapter Adapter of the Data Type template that provides its DataTypeTemplate + */ protected void importDAType(String thisIEDName, DataTypeTemplateAdapter prvDttAdapter) { Map pairOldAndNewEnumId = new HashMap<>(); @@ -276,7 +399,7 @@ protected void importDAType(String thisIEDName, DataTypeTemplateAdapter prvDttAd } if(isImportable) { - //import this enumType + //import this DAType currentElem.getDAType().add(prvDAType); if(!Objects.equals(oldId,newId)) { pairOldAndNewEnumId.put(oldId,newId); @@ -307,10 +430,24 @@ protected void importDAType(String thisIEDName, DataTypeTemplateAdapter prvDttAd } + /** + * Checks DO/DA ID equality + * @param rcv input + * @param prd input + * @return Equality ckeck result + * @param Objects' type + */ public static boolean hasSameID(T rcv, T prd){ return rcv.getId().equals(prd.getId()); } + /** + * Checks DO/DA contains same private elements + * @param rcv input + * @param prd input + * @return Comparison result + * @param + */ public static boolean hasSamePrivates(T rcv, T prd){ if(prd.getPrivate().size() != rcv.getPrivate().size()) { return false; @@ -329,6 +466,11 @@ public static boolean hasSamePrivates(T rcv, T prd){ return true ; } + /** + * Finds DA Types for BDA containing specified Enum Type ID + * @param enumTypeId input + * @return list DATypeAdapter object + */ protected List findDATypesWhichBdaContainsEnumTypeId(String enumTypeId){ List result = new ArrayList<>(); @@ -340,6 +482,11 @@ protected List findDATypesWhichBdaContainsEnumTypeId(String enumT return result; } + /** + * Finds DA Types for BDA containing specified Enum Type ID + * @param enumTypeId input + * @return list DOTypeAdapter object + */ protected List findDOTypesWhichDAContainsEnumTypeId(String enumTypeId){ List result = new ArrayList<>(); @@ -351,6 +498,11 @@ protected List findDOTypesWhichDAContainsEnumTypeId(String enumTy return result; } + /** + * Finds DA Types from BDA struct with specified ID + * @param daTypeId input + * @return list DATypeAdapter object + */ protected List findDATypesFromStructBdaWithDATypeId(String daTypeId){ return getDATypeAdapters().stream() .filter(daTypeAdapter -> daTypeAdapter.containsStructBdaWithDATypeId(daTypeId)) @@ -369,6 +521,13 @@ protected List findDOTypesFromSDOWithDOTypeId(String doTypeId){ .collect(Collectors.toList()); } + /** + * Collects DO/SDO/DA/SDA into list cooresponding to specified class type from list + * @param sdoOrDoList input list + * @param clz classe type + * @return list of DO/SDO/DA/SDA object + * @param class type of returned object + */ public static List retrieveSdoOrDA(List sdoOrDoList, Class clz){ return sdoOrDoList.stream() .filter(tUnNaming -> tUnNaming.getClass().isAssignableFrom(clz)) @@ -382,6 +541,12 @@ protected List findLNodeTypesFromDoWithDoTypeId(String doTypeI .collect(Collectors.toList()); } + /** + * Generates formatted DataTypeTemplate ID from + * @param iedName IED name + * @param dttId DataTypeTemplate ID + * @return formatted DataTypeTemplate ID + */ protected String generateDttId(String iedName,String dttId){ final int MAX_LENGTH = 255; StringBuilder stringBuilder = new StringBuilder(); @@ -390,6 +555,13 @@ protected String generateDttId(String iedName,String dttId){ return str.length() <= MAX_LENGTH ? str : str.substring(0,MAX_LENGTH); } + /** + * Gets binding information for ExtRefs + * @param lnType NodeType ID + * @param signalInfo ExtRef signal information + * @return ExtRef binding information in ExtRefBindingInfo object + * @throws ScdException + */ public ExtRefBindingInfo getBinderResumedDTT(String lnType, ExtRefSignalInfo signalInfo) throws ScdException { ExtRefBindingInfo binder = new ExtRefBindingInfo(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapter.java index d7464cdf1..f05d80635 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapter.java @@ -7,21 +7,64 @@ import org.lfenergy.compas.scl2007b4.model.TEnumType; import org.lfenergy.compas.scl2007b4.model.TEnumVal; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.List; import java.util.Objects; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TEnumType EnumType}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link EnumTypeAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link EnumTypeAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link EnumTypeAdapter#hasSameContentAs Compare Two TEnumType}
    • + *
    • {@link EnumTypeAdapter#hasValue Check whether TEnumType contain given value}
    • + *
    + *
+ */ public class EnumTypeAdapter extends AbstractDataTypeAdapter{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public EnumTypeAdapter(DataTypeTemplateAdapter parentAdapter, TEnumType currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getEnumType().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("EnumType[%s]", + Utils.xpathAttributeFilter("id", currentElem.isSetId() ? currentElem.getId() : null)); + } + + /** + * Compares current EnumType and given EnumType + * @param tEnumType EnumType to compare with + * @return Boolean value of comparison result + */ public boolean hasSameContentAs(TEnumType tEnumType) { if(!DataTypeTemplateAdapter.hasSamePrivates(currentElem, tEnumType)) { @@ -44,10 +87,19 @@ public boolean hasSameContentAs(TEnumType tEnumType) { return true; } + /** + * Checks if current EnumType has specified value + * @param val value to check + * @return Boolean value of check result + */ public boolean hasValue(String val) { return currentElem.getEnumVal().stream().anyMatch(tEnumVal -> tEnumVal.getValue().equals(val)); } + /** + * Gets linked DataTypeTemplateAdapter as parent + * @return DataTypeTemplateAdapter object + */ @Override public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return parentAdapter; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDTTComparable.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDTTComparable.java index 80b885dce..d2a1bdcd4 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDTTComparable.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDTTComparable.java @@ -4,6 +4,23 @@ package org.lfenergy.compas.sct.commons.scl.dtt; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.dtt.IDTTComparable DTTComparable}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link IDTTComparable#hasSameContentAs Compare Two SCL element}
  • + *
+ * + */ public interface IDTTComparable { + + /** + * Compares if two elements has the content + * @param sclElement element to compare with + * @return Boolean value of comparison result + */ boolean hasSameContentAs(T sclElement); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDataTemplate.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDataTemplate.java index 87ead630b..8ebc0e6d9 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDataTemplate.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/IDataTemplate.java @@ -4,6 +4,22 @@ package org.lfenergy.compas.sct.commons.scl.dtt; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.dtt.IDataTemplate DataTemplate}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link IDataTemplate#getDataTypeTemplateAdapter get DataTypeTemplateAdapter}
  • + *
+ * + */ public interface IDataTemplate { + + /** + * Gets linked DataTypeTemplateAdapter as parent + * @return DataTypeTemplateAdapter object + */ DataTypeTemplateAdapter getDataTypeTemplateAdapter(); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java index 9a55dcd0e..c10bb1a26 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java @@ -15,26 +15,75 @@ import org.lfenergy.compas.sct.commons.dto.ResumedDataTemplate; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TLNodeType LNodeType}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link LNodeTypeAdapter#getDataTypeTemplateAdapter() Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    • {@link LNodeTypeAdapter#getDOAdapterByName Returns the value of the DOAdapter by DO name }
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link LNodeTypeAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link LNodeTypeAdapter#getDOTypeId Returns the value of the type attribute By DOType Id}
    • + *
    • {@link LNodeTypeAdapter#getId() Returns the value of the id attribute}
    • + *
    • {@link LNodeTypeAdapter#getLNClass Returns the value of the lnClass attribute}
    • + *
    • {@link LNodeTypeAdapter#getResumedDTTs Returns ResumedDataTemplate list}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link LNodeTypeAdapter#hasSameContentAs Compare Two TLNodeType}
    • + *
    • {@link LNodeTypeAdapter#containsDOWithDOTypeId Check whether TLNodeType contain TDO By Id}
    • + *
    + *
+ */ @Slf4j public class LNodeTypeAdapter extends SclElementAdapter implements IDataTemplate,IDTTComparable { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public LNodeTypeAdapter(DataTypeTemplateAdapter parentAdapter, TLNodeType currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getLNodeType().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("LNodeType[%s and %s]", + Utils.xpathAttributeFilter("id", currentElem.isSetId() ? currentElem.getId() : null), + Utils.xpathAttributeFilter("lnClass", currentElem.isSetLnClass() ? currentElem.getLnClass() : null)); + } + + /** + * Compares current LNodeType and given LNodeType + * @param tlNodeType LNodeType to compare with + * @return Boolean value of comparison result + */ @Override public boolean hasSameContentAs(TLNodeType tlNodeType) { @@ -68,17 +117,32 @@ public boolean hasSameContentAs(TLNodeType tlNodeType) { return true; } + /** + * Checks if current LNodeType contains DO with specific DOTYpe ID + * @param doTypeId ID of DOType in DO to check + * @return Boolean value of check result + */ public boolean containsDOWithDOTypeId(String doTypeId) { return currentElem.getDO().stream() .anyMatch(tdo -> tdo.getType().equals(doTypeId)); } + /** + * Gets LnClass value + * @return LnClass Value + */ public String getLNClass() { if(!currentElem.getLnClass().isEmpty()){ return currentElem.getLnClass().get(0); } return null; } + + /** + * Gets DOType ID from current LNodeType + * @param doName name of DO for which ID is search + * @return optional of Boolean value + */ public Optional getDOTypeId(String doName){ return currentElem.getDO() .stream() @@ -87,6 +151,12 @@ public Optional getDOTypeId(String doName){ .findFirst(); } + /** + * return a list of summarized Resumed Data Type Templates beginning from given this LNodeType. + * @apiNote This method doesn't check relationship between DO/SDO and DA. Check should be done by caller + * @param filter filter for LNodeType + * @return list of completed Resumed Data Type Templates beginning from this LNodeType. + */ public List getResumedDTTs(@NonNull ResumedDataTemplate filter) { List resumedDataTemplates = new ArrayList<>(); @@ -122,12 +192,20 @@ public List getResumedDTTs(@NonNull ResumedDataTemplate fil return resumedDataTemplates; } - + /** + * Gets linked DataTypeTemplateAdapter as parent + * @return DataTypeTemplateAdapter object + */ @Override public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return parentAdapter; } + /** + * Gets DO from current LNodeType + * @param name name of DO to find + * @return optional of DOAdapter adapter + */ public Optional getDOAdapterByName(String name) { for(TDO tdo : currentElem.getDO()){ if(tdo.getName().equals(name)){ @@ -137,6 +215,14 @@ public Optional getDOAdapterByName(String name) { return Optional.empty(); } + /** + * Find path from a DO to DA (defined by names) + * @param doName DO from which find a path + * @param daName DA for which find a path to + * @return pair of DO name and DOType. + * @throws ScdException when inconsistency are found in th SCL's + * DataTypeTemplate (unknown reference for example). Which should normally not happens. + */ Pair findPathFromDo2DA(String doName, String daName) throws ScdException { DOAdapter doAdapter = getDOAdapterByName(doName).orElseThrow(); DOTypeAdapter doTypeAdapter = doAdapter.getDoTypeAdapter().orElseThrow(); @@ -147,7 +233,13 @@ Pair findPathFromDo2DA(String doName, String daName) throw } - + /** + * Check if DoTypeName and DaTypeName are correct and coherent with this LNodeTypeAdapter + * @param doTypeName DO/SDO to check + * @param daTypeName DA/BDA to check + * @throws ScdException when inconsistency are found in th SCL's + * DataTypeTemplate (unknown reference for example). Which should normally not happens. + */ public void check(@NonNull DoTypeName doTypeName, @NonNull DaTypeName daTypeName) throws ScdException { if(!doTypeName.isDefined() || !daTypeName.isDefined() ){ throw new ScdException("Invalid Data: data attributes information are missing"); @@ -205,7 +297,11 @@ public void check(@NonNull DoTypeName doTypeName, @NonNull DaTypeName daTypeName } } - + /** + * Gets list of summarized data type template from DaTypeName + * @param daTypeName DaTypeName from which summarized data type templates are created + * @return list of ResumedDataTemplate object + */ public List getResumedDTTByDaName(DaTypeName daTypeName) throws ScdException { Optional opRDtt; List rDtts = new ArrayList<>(); @@ -224,6 +320,11 @@ public List getResumedDTTByDaName(DaTypeName daTypeName) th return rDtts; } + /** + * Gets list of summarized data type template from DoTypeName + * @param doTypeName DoTypeName from which summarized data type templates are created + * @return list of ResumedDataTemplate object + */ public List getResumedDTTByDoName(DoTypeName doTypeName) { @@ -235,6 +336,10 @@ public List getResumedDTTByDoName(DoTypeName doTypeName) { } + /** + * Gets current LNodeType ID + * @return LNodeType ID + */ public String getId() { return currentElem.getId(); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/package-info.java new file mode 100644 index 000000000..81afa71b1 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/package-info.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

scl.dtt is a group of services for operating on + * {@link org.lfenergy.compas.scl2007b4.model.TDataTypeTemplates DataTypeTemplates} object + *

+ * @see org.lfenergy.compas.scl2007b4.model.TLNodeType + * @see org.lfenergy.compas.scl2007b4.model.TDOType + * @see org.lfenergy.compas.scl2007b4.model.TDAType + * @see org.lfenergy.compas.scl2007b4.model.TEnumType + * @see Issue !3 + * @see Issue !5 + */ +package org.lfenergy.compas.sct.commons.scl.dtt; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapter.java index aa5e5ac90..e3d96ef2a 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapter.java @@ -4,19 +4,49 @@ package org.lfenergy.compas.sct.commons.scl.header; - import org.lfenergy.compas.scl2007b4.model.THeader; import org.lfenergy.compas.scl2007b4.model.THitem; +import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.ArrayList; import java.util.Date; import java.util.List; + +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.THeader Header}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link HeaderAdapter#addHistoryItem add History}
  • + *
  • {@link HeaderAdapter#updateVersion update Version}
  • + *
  • {@link HeaderAdapter#updateRevision update Revision}
  • + *
  • {@link HeaderAdapter#addPrivate Add TPrivate under this object}
  • + *
+ * + * @see org.lfenergy.compas.sct.commons.scl.SclRootAdapter + * @see org.lfenergy.compas.scl2007b4.model.THeader + * @see org.lfenergy.compas.scl2007b4.model.THitem + * @see Issue !88 + */ public class HeaderAdapter extends SclElementAdapter { + + /** + * The default value of the {@link THeader#getToolID() ToolID} attribute. + * @see org.lfenergy.compas.scl2007b4.model.THeader#getToolID() + */ public static final String DEFAULT_TOOL_ID = "COMPAS"; + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public HeaderAdapter(SclRootAdapter parentAdapter, THeader currentElem) { super(parentAdapter, currentElem); } @@ -26,6 +56,23 @@ protected boolean amChildElementRef() { return currentElem == parentAdapter.getCurrentElem().getHeader(); } + @Override + protected String elementXPath() { + return String.format("Header[%s and %s and %s]", + Utils.xpathAttributeFilter("id", currentElem.isSetId() ? currentElem.getId() : null), + Utils.xpathAttributeFilter("version", currentElem.isSetVersion() ? currentElem.getVersion() : null), + Utils.xpathAttributeFilter("revision", currentElem.isSetRevision() ? currentElem.getRevision() : null)); + } + + /** + * @param who input + * @param what input + * @param why input + * @return {@link HeaderAdapter} + * + * @see Issue !6 + * @see Issue !71 + */ public HeaderAdapter addHistoryItem(String who, String what, String why){ THitem tHitem = new THitem(); tHitem.setRevision(currentElem.getRevision()); @@ -45,27 +92,52 @@ public HeaderAdapter addHistoryItem(String who, String what, String why){ return this; } + /** + * Returns the value of the id attribute. + * @return the value of the id attribute. + */ public String getHeaderId() { return currentElem.getId(); } + /** + * Returns the value of the Revision attribute. + * @return the value of the Revision attribute. + */ public String getHeaderRevision() { return currentElem.getRevision(); } + /** + * Returns the value of the Version attribute. + * @return the value of the Version attribute. + */ public String getHeaderVersion() { return currentElem.getVersion(); } + /** + * Returns the value of the History containment reference list. + * The list contents are of type {@link org.lfenergy.compas.scl2007b4.model.THitem}. + * @return the value of the History containment reference list. + */ public List getHistoryItems() { if(currentElem.getHistory() == null) return new ArrayList<>(); return currentElem.getHistory().getHitem(); } + /** + * Update the value of the Version attribute + * @param hVersion input + */ public void updateVersion(String hVersion) { currentElem.setVersion(hVersion); } + /** + * Update the value of the Revision attribute + * @param hRevision input + */ public void updateRevision(String hRevision) { currentElem.setRevision(hRevision); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/package-info.java new file mode 100644 index 000000000..55eaf1e46 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/header/package-info.java @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

scl.header is a group of services for operating on + * {@link org.lfenergy.compas.scl2007b4.model.THeader Header} object + *

+ * @see org.lfenergy.compas.scl2007b4.model.THeader + * @see org.lfenergy.compas.scl2007b4.model.THitem + * @see Issue !6 + * @see Issue !71 + */ +package org.lfenergy.compas.sct.commons.scl.header; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractDAIAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractDAIAdapter.java index 3e4c3144e..d612a8cee 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractDAIAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractDAIAdapter.java @@ -5,6 +5,7 @@ package org.lfenergy.compas.sct.commons.scl.ied; import org.lfenergy.compas.scl2007b4.model.TDAI; +import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.scl2007b4.model.TVal; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; @@ -13,24 +14,80 @@ import java.util.Optional; import java.util.stream.Stream; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.AbstractDAIAdapter AbstractDAIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link AbstractDAIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link AbstractDAIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link AbstractDAIAdapter#addDAI(String) Add TDAI under this object}
    • + *
    • {@link AbstractDAIAdapter#addSDOI(String) Add TSDI under this object}
    • + *
    • {@link AbstractDAIAdapter#update(Long, String) Update TDAI (sGroup, value)}
    • + *
    • {@link AbstractDAIAdapter#update(Map) Update Many TDAI (sGroup, value)}
    • + *
    • {@link AbstractDAIAdapter#addPrivate(TPrivate) Add TPrivate under this object}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link AbstractDAIAdapter#isValImport Check value Of valImport attribute}
    • + *
    + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDAI + * @see Issue !70 + */ public abstract class AbstractDAIAdapter

extends SclElementAdapter implements IDataAdapter{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected AbstractDAIAdapter(P parentAdapter, TDAI currentElem) { super(parentAdapter, currentElem); } + /** + * Gets SDI from DAI by name + * @param sName SDI name + * @return IDataAdapter related object + * @param expected class type + * @throws ScdException throws when specified SDI not present in DAI + */ public S getStructuredDataAdapterByName(String sName) throws ScdException { throw new UnsupportedOperationException("DAI doesn't have any SDI"); } + /** + * Gets DataAdapter by DAI + * @param sName DAI name + * @return IDataAdapter related object + * @param expected class type + * @throws ScdException throws when specified DAI unknown + */ public S getDataAdapterByName(String sName) throws ScdException { throw new UnsupportedOperationException("DAI doesn't have any DAI"); } + /** + * Sets ValImport value + * @param b value + */ public void setValImport(boolean b){ currentElem.setValImport(b); } + /** + * Cheks ValImport boolean value + * @return Boolean value of ValImport if define or null + */ public Boolean isValImport(){ return currentElem.isSetValImport() ? currentElem.isValImport() : null; } @@ -46,6 +103,12 @@ public AbstractDAIAdapter update(Map return this; } + /** + * Updates DAI SGroup value + * @param sGroup SGroup to update + * @param val value + * @throws ScdException throws when DAI for which SGroup should be updated is not updatable + */ public void update(Long sGroup, String val) throws ScdException { if(currentElem.isSetValImport() && !currentElem.isValImport()){ String msg = String.format( diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java index db130bb50..1138fa98e 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java @@ -14,17 +14,71 @@ import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter; +import org.lfenergy.compas.sct.commons.scl.dtt.EnumTypeAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.LNodeTypeAdapter; import java.util.*; import java.util.stream.Collectors; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter AbstractLNAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link AbstractLNAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link AbstractLNAdapter#getLNInst Returns the value of the inst attribute}
    • + *
    • {@link AbstractLNAdapter#getLNClass Returns the value of the lnClass attribute}
    • + *
    • {@link AbstractLNAdapter#getLnType Returns the value of the lnType attribute}
    • + *
    • {@link AbstractLNAdapter#getLNodeName Returns the logical node name LNName = prefix + lnClass + lnInst}
    • + * + *
    • {@link AbstractLNAdapter#getExtRefs() Returns the value of the TExtRef containment reference list}
    • + * + *
    • {@link AbstractLNAdapter#getExtRefs(ExtRefSignalInfo) Returns the value of the TExtRef containment reference list By ExtRefSignalInfo }
    • + *
    • {@link AbstractLNAdapter#getExtRefsBySignalInfo(ExtRefSignalInfo) Returns the value of the TExtRef containment reference list By ExtRefSignalInfo }
    • + * + *
    • {@link AbstractLNAdapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
    • + *
    • {@link AbstractLNAdapter#getDAIValues(ResumedDataTemplate) Returns DAI (sGroup, value) containment reference list By ResumedDataTemplate filter}
    • + * + *
    • {@link AbstractLNAdapter#getDataSet(ExtRefInfo) Returns the value of the TDataSet containment reference list By ExtRefInfo }
    • + *
    • {@link AbstractLNAdapter#getDataSetByRef(String) Returns the value of the TDataSet object reference By the value of the name attribute }
    • + * + *
    • {@link AbstractLNAdapter#getControlSetByExtRefInfo(ExtRefInfo) Returns the value of the ControlBlock containment reference list By ExtRefInfo }
    • + *
    • {@link AbstractLNAdapter#getControlBlocks(List, TServiceType) Returns the value of the ControlBlock containment reference list that match datSet value of given TDataSet }
    • + *
    • {@link AbstractLNAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link AbstractLNAdapter#removeAllControlBlocksAndDatasets() Remove all ControlBlock}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link AbstractLNAdapter#matches(ObjectReference) Check whether the section DataName of given ObjectReference match current LNAdapter DataName}
    • + *
    • {@link AbstractLNAdapter#matchesDataAttributes(String) Check whether the section DataName of given ObjectReference match current LNAdapter DataName Excluding DataName from DataTypeTemplat}
    • + *
    + *
+ *
+ *
+ *    ObjectReference: LDName/LNName.DataName[.DataName[…]].DataAttributeName[.DAComponentName[ ….]]
+ *    LDName = "name" attribute of IEDName element + "inst" attribute of LDevice element
+ *    LNName = "prefix" + "lnClass" + "lnInst"
+ * 
+ * @see org.lfenergy.compas.scl2007b4.model.TAnyLN + */ @Getter @Slf4j public abstract class AbstractLNAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected AbstractLNAdapter(LDeviceAdapter parentAdapter, T currentElem) { super(parentAdapter, currentElem); } @@ -37,6 +91,29 @@ public static LNAdapterBuilder builder(){ public abstract String getLNInst(); public abstract String getPrefix(); + /** + * Returns sets of enum value for given ResumedDataTemplate object + * @param enumType enum Type + * @return Enum value list + */ + public Set getEnumValues(String enumType) { + Optional enumTypeAdapter = parentAdapter + .getParentAdapter().getParentAdapter().getDataTypeTemplateAdapter() + .getEnumTypeAdapterById(enumType); + + if(enumTypeAdapter.isEmpty()){ + return Collections.emptySet(); + } + return enumTypeAdapter.get().getCurrentElem().getEnumVal() + .stream().map(TEnumVal::getValue) + .collect(Collectors.toSet()); + } + + /** + * Add given ControlBlock to LNode + * @param controlBlock ControlBlock to add + * @throws ScdException throws when the ControlBlock type is unknown + */ protected void addControlBlock(ControlBlock controlBlock) throws ScdException { switch (controlBlock.getServiceType() ) { @@ -97,10 +174,19 @@ public String getLnType(){ return currentElem.getLnType(); } + /** + * Gets all ExtRefs + * @return list of LNode ExtRefs elements + */ public List getExtRefs() { return getExtRefs(null); } + /** + * Gets all ExtRefs matches specified ExtRef info + * @param filter ExtRef filter value + * @return list of TExtRef + */ public List getExtRefs(ExtRefSignalInfo filter) { if(!hasInputs()){ return new ArrayList<>(); @@ -125,6 +211,11 @@ public boolean hasInputs() { return currentElem.getInputs() != null; } + /** + * Gets all ExtRefs matches specified ExtRef info without PDA attribute + * @param signalInfo ExtRef filter value + * @return list of TExtRef + */ public List getExtRefsBySignalInfo(ExtRefSignalInfo signalInfo) { if (currentElem.getInputs() == null) { return new ArrayList<>(); @@ -146,6 +237,11 @@ public List getExtRefsBySignalInfo(ExtRefSignalInfo signalInfo) { .collect(Collectors.toList()); } + /** + * Update LNode ExtRefs data with ExtRefInfo data + * @param extRefInfo contains new data for LNode ExtREf update + * @throws ScdException throws when mandatory data are missing + */ public void updateExtRefBinders(ExtRefInfo extRefInfo) throws ScdException { if(extRefInfo.getBindingInfo() == null || !extRefInfo.getBindingInfo().isValid()){ @@ -170,6 +266,11 @@ public void updateExtRefBinders(ExtRefInfo extRefInfo) throws ScdException { updateExtRefBindingInfo(extRef, extRefInfo); } + /** + * Updates ExtRef with data from ExtRefInfo + * @param extRef ExtRef to update + * @param extRefInfo contains new data for LNode ExtREf update + */ protected void updateExtRefBindingInfo(TExtRef extRef, ExtRefInfo extRefInfo) { //update binding info ExtRefBindingInfo bindingInfo = extRefInfo.getBindingInfo(); @@ -213,11 +314,22 @@ protected void updateExtRefBindingInfo(TExtRef extRef, ExtRefInfo extRefInfo) { } } + /** + * Gets Control Blocks of LN specified in extRefInfo + * @param extRefInfo ExtRef signal data for which Control Blocks should be found + * @return list of ControlBlock object as ControlBlocks of LNode + */ public List> getControlSetByExtRefInfo(ExtRefInfo extRefInfo) { List tDataSets = this.getDataSet(extRefInfo); return getControlBlocks(tDataSets,extRefInfo.getBindingInfo().getServiceType()); } + /** + * Gets all Control Blocks from LNode for specified Service Type (GOOSE, SMV and REPORT) and Data Sets + * @param tDataSets Data Sets for which Control Blocks are needed + * @param serviceType Service Type of Control Blocks needed + * @return list of ControlBlock objects + */ protected List> getControlBlocks(List tDataSets, TServiceType serviceType) { List> controlBlocks = new ArrayList<>(); List tControls; @@ -268,6 +380,13 @@ protected List> getControlBlocks(List tDataSets, TServ return controlBlocks; } + /** + * Gets Control Blocks for specified Data Set reference + * @param dataSetRef Data Set for which Control Blocks are needed + * @param cls class type of Control Block (GOOSE, SMV and REPORT) + * @return List of Control Blocks corresponding to cls + * @param inference type + */ protected List lookUpControlBlocksByDataSetRef(@NonNull String dataSetRef, Class cls){ List ls = new ArrayList<>(); if (TGSEControl.class.equals(cls) && isLN0()) { @@ -284,6 +403,11 @@ protected List lookUpControlBlocksByDataSetRef(@NonNull .collect(Collectors.toList()); } + /** + * Checks if FCDA is null + * @param tfcda FCDA to check + * @return Boolean value of check result + */ public static boolean isNull(TFCDA tfcda){ return Objects.isNull(tfcda.getLdInst()) && tfcda.getLnClass().isEmpty() && @@ -291,6 +415,11 @@ public static boolean isNull(TFCDA tfcda){ } + /** + * Checks if specified Control Block is present in LNode + * @param controlBlock Control Block to check + * @return Boolean value of check result + */ public boolean hasControlBlock(ControlBlock controlBlock) { switch (controlBlock.getServiceType()){ @@ -322,6 +451,12 @@ public List getDataSet(ExtRefInfo filter){ .collect(Collectors.toList()); } + /** + * Updates ExtRef source binding data's based on given data in extRefInfo + * @param extRefInfo new data for ExtRef source binding data + * @return TExtRef object as update ExtRef with new source binding data + * @throws ScdException throws when mandatory data of ExtRef are missing + */ public TExtRef updateExtRefSource(ExtRefInfo extRefInfo) throws ScdException { ExtRefSignalInfo signalInfo = extRefInfo.getSignalInfo(); ExtRefSourceInfo sourceInfo = extRefInfo.getSourceInfo(); @@ -430,7 +565,7 @@ public TExtRef checkExtRefInfoCoherence(@NonNull ExtRefInfo extRefInfo) throws S /** * Returns a list of resumed DataTypeTemplate for DataAttribute (updatable or not) * @param rDtt reference resumed DataTypeTemplate (used as filter) - * @param updatableOnly true to retrieve only updatable DAI, false to retrieve all DAI + * @param updatableOnly true to retrieve DataTypeTemplate's related to only updatable DAI, false to retrieve all * @return List of resumed DataTypeTemplate for DataAttribute (updatable or not) * @throws ScdException SCD illegal arguments exception */ @@ -459,6 +594,10 @@ public List getDAI(ResumedDataTemplate rDtt, boolean updata } } + /** + * Update given ResumedDataTemplate DAI datas from LNode + * @param rDtt summarized Data Type Template object to update DAI datas + */ protected void overrideAttributesFromDAI(final ResumedDataTemplate rDtt) { findMatch(rDtt.getDoName(), rDtt.getDaName()) .map(iDataAdapter -> (AbstractDAIAdapter) iDataAdapter) @@ -480,16 +619,30 @@ protected void overrideAttributesFromDAI(final ResumedDataTemplate rDtt) { }); } + /** + * Checks if linked IED as parent has Setting Group set + * @return Boolean value of check result + */ private boolean iedHasConfSG() { IEDAdapter iedAdapter = getCurrentIED(); return iedAdapter.isSettingConfig(this.parentAdapter.getInst()); } + /** + * Gets linked IED as parent + * @return IEDAdapter object + */ private IEDAdapter getCurrentIED() { LDeviceAdapter lDeviceAdapter = this.parentAdapter; return lDeviceAdapter.getParentAdapter(); } + + /** + * Checks fro DAI if Setting Group value is set correctly + * @param tdai DAI for which check is done + * @return Boolean value of check result + */ private boolean hasSgGroup(TDAI tdai) { return tdai.getVal().stream().anyMatch(tVal -> tVal.isSetSGroup() && tVal.getSGroup() > 0); } @@ -510,6 +663,14 @@ protected Optional findMatch(DoTypeName doTypeName, DaTypeName daT return Optional.of(daiTracker.getBdaiOrDaiAdapter()); } + /** + * Updates DAI (in LNode section) after checking updatability with summarized Data Type Template given information and LNode + * Summarized Data Type Temple and LNode helps to check updatability. rDtt gives DAI which should be found in LNode, + * that LNode gives LNodeType localized in DataTypeTemplate section and contains DOType in which the DAI is localized. + * @param rDtt summarized Data Type Temple containing new DO and DA data's + * @throws ScdException when inconsistency are found in th SCL's + * DataTypeTemplate. Which should normally not happens. + */ public void updateDAI(@NonNull ResumedDataTemplate rDtt) throws ScdException { if(!rDtt.isDoNameDefined() || !rDtt.isDaNameDefined()){ @@ -577,6 +738,11 @@ public void updateDAI(@NonNull ResumedDataTemplate rDtt) throws ScdException { } } + /** + * Adds DO in LNode + * @param name DOI name + * @return added DOIAdapter object + */ protected DOIAdapter addDOI(String name) { TDOI tdoi = new TDOI(); tdoi.setName(name); @@ -598,6 +764,12 @@ public String getLNodeName() { return stringBuilder.toString(); } + /** + * Checks given reference matches with DataSet or ReportControl or DataTypeTemplate element for calling LNode + * in SCL file + * @param objRef reference to compare with LNode datas + * @return Boolean value of check result + */ public boolean matches(ObjectReference objRef) { String dataAttribute = objRef.getDataAttributes(); SclRootAdapter sclRootAdapter = parentAdapter.getParentAdapter().getParentAdapter(); @@ -618,11 +790,21 @@ public boolean matches(ObjectReference objRef) { rDtts.stream().anyMatch(rDtt -> rDtt.getDataAttributes().startsWith(dataAttribute)); } + /** + * Checks if given attibrute corresponds to DataSet or ReportControl in LNode + * @param dataAttribute attribute to check + * @return Boolean value of check result + */ protected boolean matchesDataAttributes(String dataAttribute){ return currentElem.getDataSet().stream().anyMatch(tDataSet -> tDataSet.getName().equals(dataAttribute)) || currentElem.getReportControl().stream().anyMatch(rptCtl -> rptCtl.getName().equals(dataAttribute)); } + /** + * Gets Data Set in LNode by its name + * @param dataSetRef Data Set name + * @return optional of DataSetInfo + */ public Optional getDataSetByRef(String dataSetRef) { return currentElem.getDataSet() .stream() @@ -631,6 +813,10 @@ public Optional getDataSetByRef(String dataSetRef) { .findFirst(); } + /** + * Adds Data Set to LNode Data Sets + * @param dataSetInfo data's of Data Set to add + */ public void addDataSet(DataSetInfo dataSetInfo) { TDataSet tDataSet = new TDataSet(); tDataSet.setName(dataSetInfo.getName()); @@ -645,6 +831,11 @@ public DataTypeTemplateAdapter getDataTypeTemplateAdapter() { return parentAdapter.getParentAdapter().getParentAdapter().getDataTypeTemplateAdapter(); } + /** + * Gets DAI values for specified DA in summaraized Data Type Template + * @param rDtt summaraized Data Type Template containing DA datas + * @return map of Setting Group and it's VAL + */ public Map getDAIValues(ResumedDataTemplate rDtt) { DAITracker daiTracker = new DAITracker(this,rDtt.getDoName(),rDtt.getDaName()); DAITracker.MatchResult matchResult = daiTracker.search(); @@ -669,16 +860,26 @@ public Map getDAIValues(ResumedDataTemplate rDtt) { return res; } + /** + * Removes all ControlBlocks and DataSets from current LN + */ public void removeAllControlBlocksAndDatasets() { currentElem.unsetReportControl(); currentElem.unsetLogControl(); currentElem.unsetDataSet(); } + /** + * Removes all ExtRefs source binding data's + */ public void removeAllExtRefSourceBindings() { getExtRefs().forEach(this::removeExtRefSourceBinding); } + /** + * Removes specified ExtRef Source binding data's + * @param tExtRef ExtRef Source for which binding data's should be removed + */ private void removeExtRefSourceBinding(final TExtRef tExtRef){ tExtRef.setSrcCBName(null); tExtRef.setSrcLDInst(null); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DAITracker.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DAITracker.java index b96fb384c..87007058b 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DAITracker.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DAITracker.java @@ -18,6 +18,23 @@ import java.util.List; import java.util.Map; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.DAITracker DAITracker}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link DAITracker#search() Compare Two ObjectReference sections }
  • + *
  • {@link DAITracker#validateBoundedDAI() Validate ObjectReference base cdc attribute }
  • + *
  • {@link DAITracker#getDaiNumericValue(DaTypeName, double) Returns value of the bType attribute By DaTypeName }
  • + *
+ *
+ *
+ *     ObjectReference: LDName/LNName.DataName[.DataName[…]].DataAttributeName[.DAComponentName[ ….]]
+ * 
+ * @see Issue !32 + */ @Getter public class DAITracker { private final AbstractLNAdapter lnAdapter; @@ -29,6 +46,12 @@ public class DAITracker { private IDataAdapter bdaiOrDaiAdapter; private int indexDaType = -2; + /** + * Constructor + * @param lnAdapter Parent container reference + * @param doTypeName DOTypeName reference containing DO data leading to DA + * @param daTypeName DATypeName reference containing linked DA to track data + */ public DAITracker(@NonNull AbstractLNAdapter lnAdapter, @NonNull DoTypeName doTypeName, @NonNull DaTypeName daTypeName) { @@ -37,6 +60,16 @@ public DAITracker(@NonNull AbstractLNAdapter lnAdapter, this.daTypeName = daTypeName; } + /** + * Checks DO/DA presence in LNode by browsing through LNode based path given in doTypeName + * and daTypeName attributes + * @return one of MatchResult enum value : + *
    + *
  • FAILED
  • + *
  • PARTIAL_MATCH
  • + *
  • FULL_MATCH
  • + *
+ */ public MatchResult search() { Pair matchResult; @@ -91,6 +124,10 @@ public MatchResult search() { return MatchResult.FULL_MATCH; } + /** + * Validate if DAI Setting Group value is between boundaries (of DA BType) + * @throws ScdException throws when value inconsistancy + */ public void validateBoundedDAI() throws ScdException { if(TPredefinedCDCEnum.ING != doTypeName.getCdc() && TPredefinedCDCEnum.ASG != doTypeName.getCdc() ){ return; @@ -161,6 +198,12 @@ public void validateBoundedDAI() throws ScdException { } } + /** + * Gets DAI value from DaTypeName for specific known types and throws exception if unknown type + * @param daTypeName contains DA information + * @param defaultValue default init value + * @return Double corresponding value for DAI val + */ protected double getDaiNumericValue(DaTypeName daTypeName, double defaultValue) { String value = daTypeName.getDaiValues().values().stream().findFirst().orElse(null); if(value == null){ @@ -187,7 +230,9 @@ protected double getDaiNumericValue(DaTypeName daTypeName, double defaultValue) } } - + /** + * Enumeration of three states for matching check (FAILED, PARTIAL_MATCH, FULL_MATCH) + */ public enum MatchResult { FAILED("FAILED"), PARTIAL_MATCH("PARTIAL_MATCH"), diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java index 5e2fd381d..ecd34e231 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java @@ -10,19 +10,65 @@ import org.lfenergy.compas.scl2007b4.model.TSDI; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.DOIAdapter DOIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DOIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link DOIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DOIAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link DOIAdapter#addSDOI Add TSDI under this object}
    • + *
    • {@link DOIAdapter#addPrivate Add TPrivate under this object}
    • + *
    + * + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDAI + * @see org.lfenergy.compas.scl2007b4.model.TSDI + */ public class DOIAdapter extends SclElementAdapter, TDOI> implements IDataParentAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected DOIAdapter(AbstractLNAdapter parentAdapter, TDOI currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getDOI().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DOI[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + + /** + * Gets SDI by name from current DOI + * @param sName name of SDI to get + * @return RootSDIAdapter object + * @throws ScdException throws when specified name of SDI not present in current DOI + */ @Override public RootSDIAdapter getStructuredDataAdapterByName(String sName) throws ScdException { return currentElem.getSDIOrDAI() @@ -39,6 +85,12 @@ public RootSDIAdapter getStructuredDataAdapterByName(String sName) throws ScdExc ); } + /** + * Gets DAI from current DOI + * @param daName name of DAI to get + * @return DAIAdapter object + * @throws ScdException throws when specified name of DAI not present in current DOI + */ @Override public DAIAdapter getDataAdapterByName(String daName) throws ScdException { return currentElem.getSDIOrDAI() @@ -55,6 +107,12 @@ public DAIAdapter getDataAdapterByName(String daName) throws ScdException { ); } + /** + * Adds DAI to current DOI + * @param name name of DAI to add + * @param isUpdatable updatability state of DAI + * @return DAIAdapter object as added DAI + */ @Override public DAIAdapter addDAI(String name, boolean isUpdatable) { TDAI tdai = new TDAI(); @@ -64,6 +122,11 @@ public DAIAdapter addDAI(String name, boolean isUpdatable) { return new DAIAdapter(this,tdai); } + /** + * Adds SDOI to SDI in current DOI + * @param sdoName name of SDOI to add + * @return RootSDIAdapter object as added SDOI + */ @Override public RootSDIAdapter addSDOI(String sdoName) { TSDI tsdi = new TSDI(); @@ -72,6 +135,28 @@ public RootSDIAdapter addSDOI(String sdoName) { return new RootSDIAdapter(this,tsdi); } + /** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.DOIAdapter.DAIAdapter DOIAdapter.DAIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DAIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link DAIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DAIAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link DAIAdapter#addSDOI Add TSDI under this object}
    • + *
    • {@link DAIAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TSDI + */ public static class DAIAdapter extends AbstractDAIAdapter { protected DAIAdapter(DOIAdapter parentAdapter, TDAI currentElem) { @@ -83,5 +168,11 @@ protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSDIOrDAI().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DAI[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IDataParentAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IDataParentAdapter.java index c2b16fd07..35e5ce0d2 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IDataParentAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IDataParentAdapter.java @@ -9,10 +9,61 @@ import java.util.List; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.IDataParentAdapter IDataParentAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link IDataParentAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link IDataParentAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link IDataParentAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link IDataParentAdapter#addSDOI Add TSDI under this object}
    • + *
    + * + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDAI + * @see org.lfenergy.compas.scl2007b4.model.TSDI + */ public interface IDataParentAdapter extends IDataAdapter { + + /** + * Gets SDI by name from current DOI + * @param sName name of SDI to get + * @return RootSDIAdapter object + * @throws ScdException throws when specified name of SDI not present in current DOI + */ S getStructuredDataAdapterByName(String sName) throws ScdException; + + /** + * Gets DAI from current DOI + * @param sName name of DAI to get + * @return DAIAdapter object + * @throws ScdException throws when specified name of DAI not present in current DOI + */ S getDataAdapterByName(String sName) throws ScdException; + + /** + * Adds DAI to current DOI + * @param name name of DAI to add + * @param isUpdatable updatability state of DAI + * @return DAIAdapter object as added DAI + */ S addDAI(String name,boolean isUpdatable); + + + /** + * Adds SDOI to SDI in current DOI + * @param sdoNme name of SDOI to add + * @return RootSDIAdapter object as added SDOI + */ S addSDOI(String sdoNme) ; /** diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java index ba9651a8d..fd84454af 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java @@ -15,21 +15,72 @@ import org.lfenergy.compas.sct.commons.scl.ObjectReference; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.*; import java.util.stream.Collectors; - +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TIED IED}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link IEDAdapter#getLDeviceAdapters Returns the value of the LDeviceAdapter containment reference list}
    • + *
    • {@link IEDAdapter#getLDeviceAdapterByLdInst Returns the value of the LDeviceAdapter reference object By LDevice Inst}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link IEDAdapter#getName Returns the value of the name attribute}
    • + *
    • {@link IEDAdapter#getServices Returns the value of the Service object}
    • + *
    • {@link IEDAdapter#getPrivateHeader Returns the value of the TPrivate containment reference list}
    • + *
    • {@link IEDAdapter#getExtRefBinders Returns the value of the ExtRefBindingInfo containment reference list By ExtRefSignalInfo}
    • + *
    • {@link IEDAdapter#createDataSet Add DataSetInfo describing the children TDataSet that can be created under TAnyLN}
    • + *
    • {@link IEDAdapter#createControlBlock Add ControlBlock describing the children TControlBlock that can be created under TAnyLN}
    • + *
    • {@link IEDAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link IEDAdapter#updateLDeviceNodesType Update Type describing the value of the children TAnyLN}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link IEDAdapter#isSettingConfig Check whether IIED contain confSG}
    • + *
    • {@link IEDAdapter#hasDataSetCreationCapability Check the ability to support DataSet Creation}
    • + *
    + *
+ * + * @see org.lfenergy.compas.sct.commons.scl.ied.LDeviceAdapter + * @see org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter + * @see org.lfenergy.compas.sct.commons.scl.ied.AbstractDAIAdapter + * @see org.lfenergy.compas.scl2007b4.model.TSettingGroups + * @see Issue !3 (Add new IEDs) + */ @Slf4j public class IEDAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + */ public IEDAdapter(SclRootAdapter parentAdapter) { super(parentAdapter); } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public IEDAdapter(SclRootAdapter parentAdapter, TIED currentElem) { super(parentAdapter, currentElem); } + + /** + * Constructor + * @param parentAdapter Parent container reference + * @param iedName IED name reference + */ public IEDAdapter(SclRootAdapter parentAdapter, String iedName) throws ScdException { super(parentAdapter); TIED ied = parentAdapter.getCurrentElem().getIED() @@ -40,15 +91,32 @@ public IEDAdapter(SclRootAdapter parentAdapter, String iedName) throws ScdExcept setCurrentElem(ied); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getIED().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("IED[%s]", Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + + /** + * Sets IED name in current IED + * @param iedName new name to set + */ public void setIEDName(String iedName) { currentElem.setName(iedName); } + /** + * Gets all LDevices linked to current IED + * @return list of LDeviceAdapter + */ public List getLDeviceAdapters(){ return currentElem.getAccessPoint() .stream() @@ -59,6 +127,11 @@ public List getLDeviceAdapters(){ .collect(Collectors.toList()); } + /** + * Gets LDevice from current IED by ldInst parameter + * @param ldInst ldInst value of LDevice to get + * @return optional of LDeviceAdapter object + */ public Optional getLDeviceAdapterByLdInst(String ldInst){ return currentElem.getAccessPoint() .stream() @@ -70,6 +143,13 @@ public Optional getLDeviceAdapterByLdInst(String ldInst){ .findFirst(); } + /** + * Updates all LNode type value specified in pairOldNewId as key (oldID), by corresponding value (newID) + * in all LDevice of the current IED. + * Update LDevice name by combining IED name and LDevice ldInst value + * @param pairOldNewId map of old ID and new ID. Old ID to find in LNode Type and replace it with New ID + * @throws ScdException throws when renaming LDevice and new name has more than 33 caracteres + */ public void updateLDeviceNodesType(Map pairOldNewId) throws ScdException { // renaming ldName for(LDeviceAdapter lDeviceAdapter : getLDeviceAdapters()) { @@ -90,13 +170,28 @@ public void updateLDeviceNodesType(Map pairOldNewId) throws ScdE } } + /** + * Gets Services of current IED + * @return TServices object + */ public TServices getServices(){ return currentElem.getServices(); } + + /** + * Gets name of current IED + * @return string name + */ public String getName(){ return currentElem.getName(); } + /** + * Checks if given ObjectReference matches with one of current IED LNode + * (ie having common reference with DataSet, ReportControl or DataTypeTemplate) + * @param objRef reference to compare with LNodes data's + * @return Boolean value of check result + */ public boolean matches(ObjectReference objRef){ if(!objRef.getLdName().startsWith(getName())) { return false; @@ -122,12 +217,23 @@ public boolean matches(ObjectReference objRef){ && lnAdapter.matches(objRef)); } + /** + * Checks existence of Access Point in curent IED by name + * @param apName AccessPoint name to check + * @return Boolean value of check result + */ public boolean findAccessPointByName(String apName) { return currentElem.getAccessPoint() .stream() .anyMatch(tAccessPoint -> tAccessPoint.getName().equals(apName)); } + /** + * Checks all possible ExtRef in current IED which could be bound to given ExtRef as parameter + * @param signalInfo ExtRef to bind data + * @return list of ExtRefBindingInfo object (containing binding data for each LDevice in current IED) + * @throws ScdException throws when ExtRef contains inconsistency data + */ public List getExtRefBinders(@NonNull ExtRefSignalInfo signalInfo) throws ScdException { if(!signalInfo.isValid()){ throw new ScdException("Invalid ExtRef signal (pDO,pDA or intAddr))"); @@ -140,6 +246,11 @@ public List getExtRefBinders(@NonNull ExtRefSignalInfo signal return potentialBinders; } + /** + * Checks for a given LDevice in current IED if Setting Group is well setted + * @param ldInst ldInst for LDevice for which Setting Group is checked + * @return Boolean value of check result + */ public boolean isSettingConfig(String ldInst) { TAccessPoint accessPoint = currentElem.getAccessPoint().stream() .filter(tAccessPoint -> @@ -159,6 +270,11 @@ public boolean isSettingConfig(String ldInst) { return srv != null && srv.getSettingGroups() != null && srv.getSettingGroups().getConfSG() != null; } + /** + * Adds Data Set in specified LNode in current IED + * @param dataSetInfo Data Set data to add (and LNode path) + * @throws ScdException throws when IED is not able to add DataSet + */ public void createDataSet(DataSetInfo dataSetInfo) throws ScdException { if(!hasDataSetCreationCapability()){ throw new ScdException("The capability of IED is not allowing DataSet creation"); @@ -180,6 +296,10 @@ public void createDataSet(DataSetInfo dataSetInfo) throws ScdException { lNodeAdapter.addDataSet(dataSetInfo); } + /** + * Checks if IED is able to create new Data Set + * @return Boolean value of check result + */ protected boolean hasDataSetCreationCapability() { if(currentElem.getServices() == null){ return false ; @@ -208,6 +328,12 @@ protected boolean hasDataSetCreationCapability() { return hasCapability ; } + /** + * Creates Control Block in specified LNode in current IED + * @param controlBlock Control Block data to add (and LNode path) + * @return created ControlBlock object + * @throws ScdException throws when inconsistency between given ControlBlock and IED configuration + */ public ControlBlock createControlBlock(ControlBlock controlBlock) throws ScdException { @@ -255,6 +381,11 @@ public ControlBlock createControlBlock(ControlBlockTPrivate object + */ public Optional getPrivateHeader(String privateType){ return currentElem.getPrivate() .stream() diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java index 780e983a0..a037accc3 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java @@ -9,21 +9,63 @@ import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.scl2007b4.model.TLDevice; import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; +import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.sct.commons.dto.*; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.*; import java.util.stream.Collectors; +/** + * A representation of the model object + * {@link org.lfenergy.compas.scl2007b4.model.TLDevice LDevice}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link LDeviceAdapter#getLNAdapters Returns the value of the LNAdapter containment reference list}
    • + *
    • {@link LDeviceAdapter#getLN0Adapter Returns the value of the LN0Adapter containment reference list}
    • + *
    • {@link LDeviceAdapter#getLNAdapter Returns the value of the LNAdapter reference object By LNClass, inst and prefix}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link LDeviceAdapter#getInst Returns the value of the inst attribute}
    • + *
    • {@link LDeviceAdapter#getLdName Returns the value of the ldName attribute}
    • + *
    • {@link LDeviceAdapter#getExtRefInfo em>Returns the value of the ExtRefInfo containment reference}
    • + *
    • {@link LDeviceAdapter#getExtRefBinders Returns the value of the ExtRefBindingInfo containment reference list By ExtRefSignalInfo}
    • + *
    • {@link LDeviceAdapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
    • + *
    • {@link LDeviceAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
+ * + * @see org.lfenergy.compas.sct.commons.scl.ied.LNAdapter + * @see org.lfenergy.compas.sct.commons.scl.ied.LN0Adapter + * @see org.lfenergy.compas.scl2007b4.model.TLDevice + * @see org.lfenergy.compas.scl2007b4.model.TDOI + * @see org.lfenergy.compas.scl2007b4.model.TDAI + * @see Issue !32 + */ @Slf4j public class LDeviceAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public LDeviceAdapter(IEDAdapter parentAdapter, TLDevice currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getAccessPoint() @@ -34,6 +76,15 @@ protected boolean amChildElementRef() { .anyMatch(tlDevice -> currentElem.getInst().equals(tlDevice.getInst())); } + @Override + protected String elementXPath() { + return String.format("LDevice[%s]", Utils.xpathAttributeFilter("inst", currentElem.isSetInst() ? currentElem.getInst() : null)); + } + + /** + * Updates LDevice name by combining IED name and LDevice ldInst value + * @throws ScdException throws when renaming LDevice and new name has more than 33 caracteres + */ public void updateLDName() throws ScdException { String newLdName = parentAdapter.getCurrentElem().getName() + currentElem.getInst(); if(newLdName.length() > 33){ @@ -43,18 +94,34 @@ public void updateLDName() throws ScdException { currentElem.setLdName(newLdName); } + /** + * Gets current LDevice Inst parameter value + * @return Inst parameter value + */ public String getInst(){ return currentElem.getInst(); } + /** + * Gets current LDevice name + * @return LDevice name + */ public String getLdName() { return currentElem.getLdName(); } + /** + * Gets current LDevice LNode LN0 + * @return LN0Adapter + */ public LN0Adapter getLN0Adapter(){ return new LN0Adapter(this, currentElem.getLN0()); } + /** + * Gets current LDevice LNodes (except LN0) + * @return list of LNAdapter object + */ public List getLNAdapters(){ return currentElem.getLN() .stream() @@ -62,6 +129,14 @@ public List getLNAdapters(){ .collect(Collectors.toList()); } + /** + * Gets specific LNode from current LDevice + * @param lnClass LNode lnClass value + * @param lnInst LNode lnInst value + * @param prefix LNode prefix value + * @return LNAdapter object + * @throws ScdException thros when specified LNode not found in current IED + */ public LNAdapter getLNAdapter(String lnClass, String lnInst, String prefix) throws ScdException { return currentElem.getLN() .stream() @@ -81,6 +156,12 @@ public LNAdapter getLNAdapter(String lnClass, String lnInst, String prefix) thro } + /** + * Checks all possible ExtRef in current LDevice which could be bound to given ExtRef as parameter + * @param signalInfo ExtRef to bind data + * @return list of ExtRefBindingInfo object (containing binding data for each LDNode in current LDevice + * related to given ExtRef) + */ public List getExtRefBinders(ExtRefSignalInfo signalInfo) { DataTypeTemplateAdapter dttAdapter = parentAdapter.getParentAdapter().getDataTypeTemplateAdapter(); List potentialBinders = new ArrayList<>(); @@ -106,6 +187,10 @@ public List getExtRefBinders(ExtRefSignalInfo signalInfo) { return potentialBinders; } + /** + * Gets all ExtRef of all LNodes of current LDevice + * @return list of ExtRefInfo object (containing binding data for each LDNode in current LDevice) + */ public List getExtRefInfo() { List extRefInfos = new ArrayList<>(); List> lnAdapters = new ArrayList<>(); @@ -120,6 +205,13 @@ public List getExtRefInfo() { return extRefInfos; } + /** + * Gets a list of summarized DataTypeTemplate for DataAttribute DAIs (updatable or not) + * @param rDtt reference resumed DataTypeTemplate (used as filter) + * @param updatable true to retrieve only updatable DAIs, false to retrieve all DAIs + * @return List of ResumedDataTemplate (updatable or not) + * @throws ScdException SCD illegal arguments exception + */ public Set getDAI(ResumedDataTemplate rDtt, boolean updatable) throws ScdException { List> lnAdapters = new ArrayList<>(); if(StringUtils.isBlank(rDtt.getLnClass())){ diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java index de6bebe65..92deae202 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java @@ -5,40 +5,134 @@ package org.lfenergy.compas.sct.commons.scl.ied; -import org.lfenergy.compas.scl2007b4.model.LN0; -import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; +import org.apache.commons.lang3.tuple.Pair; +import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.dto.*; +import org.lfenergy.compas.sct.commons.scl.LDeviceActivation; +import org.lfenergy.compas.sct.commons.scl.ObjectReference; +import org.lfenergy.compas.sct.commons.scl.PrivateService; +import org.lfenergy.compas.sct.commons.util.Utils; -public class LN0Adapter extends AbstractLNAdapter { +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.LN0Adapter LN0Adapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link LN0Adapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link LN0Adapter#getLNInst Returns the value of the inst attribute}
    • + *
    • {@link LN0Adapter#getLNClass Returns the value of the lnClass attribute}
    • + *
    • {@link LN0Adapter#getLnType Returns the value of the lnTYpe attribute}
    • + *
    • {@link LN0Adapter#getLNodeName Returns the logical node name LNName = prefix + lnClass + lnInst}
    • + * + *
    • {@link LN0Adapter#getExtRefs() Returns the value of the TExtRef containment reference list}
    • + *
    • {@link LN0Adapter#getExtRefs(ExtRefSignalInfo) Returns the value of the TExtRef containment reference list By ExtRefSignalInfo }
    • + *
    • {@link LN0Adapter#getExtRefsBySignalInfo(ExtRefSignalInfo) Returns the value of the TExtRef containment reference list By ExtRefSignalInfo }
    • + * + *
    • {@link LN0Adapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
    • + *
    • {@link LN0Adapter#getDAIValues(ResumedDataTemplate) Returns DAI (sGroup, value) containment reference list By ResumedDataTemplate filter}
    • + * + *
    • {@link LN0Adapter#getDataSet(ExtRefInfo) Returns the value of the TDataSet containment reference list By ExtRefInfo }
    • + *
    • {@link LN0Adapter#getDataSetByRef(String) Returns the value of the TDataSet object reference By the value of the name attribute }
    • + * + *
    • {@link LN0Adapter#getControlSetByExtRefInfo(ExtRefInfo) Returns the value of the ControlBlock containment reference list By ExtRefInfo }
    • + *
    • {@link LN0Adapter#getControlBlocks(List, TServiceType) Returns the value of the ControlBlock containment reference list that match datSet value of given TDataSet }
    • + *
    • {@link LN0Adapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link LN0Adapter#removeAllControlBlocksAndDatasets() Remove all ControlBlock}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link LN0Adapter#matches(ObjectReference) Check whether the section DataName of given ObjectReference match current LN0Adapter DataName}
    • + *
    • {@link LN0Adapter#matchesDataAttributes(String) Check whether the section DataName of given ObjectReference match current LNAdapter DataName Excluding DataName from DataTypeTemplat}
    • + *
    + *
+ *
+ *
+ *      ObjectReference: LDName/LNName.DataName[.DataName[…]].DataAttributeName[.DAComponentName[ ….]]
+ *      LDName = "name" attribute of IEDName element + "inst" attribute of LDevice element
+ *      LNName = "prefix" + "lnClass" + "lnInst"
+ *  
+ * @see org.lfenergy.compas.scl2007b4.model.TLN0 + * @see org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter + */ +public class LN0Adapter extends AbstractLNAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param ln0 Current reference + */ public LN0Adapter(LDeviceAdapter parentAdapter, LN0 ln0) { super(parentAdapter,ln0); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return currentElem == parentAdapter.getCurrentElem().getLN0(); } + @Override + protected String elementXPath() { + return String.format("LN[lnClass=\"LLN0\" and %s and %s]", + Utils.xpathAttributeFilter("inst", currentElem.isSetInst() ? currentElem.getInst() : null), + Utils.xpathAttributeFilter("lnType", currentElem.isSetLnType() ? currentElem.getLnType() : null)); + } + + /** + * Gets current LN0 class type + * @return LN0.class + */ @Override protected Class getElementClassType() { return LN0.class; } + /** + * Gets LNClass enum value of current LNO + * @return LNClass value + */ public String getLNClass() { return TLLN0Enum.LLN_0.value(); } + /** + * Gets LNInst value of current LN0 + * @return "" + */ @Override public String getLNInst() { return ""; } + /** + * Gets Prefix value of current LN0 + * @return "" + */ @Override public String getPrefix() { return ""; } + /** Checks if given attibrute corresponds to DataSet or ReportControl or SMVControl or GSEControl in current LN0 + * @param dataAttribute attribute to check + * @return Boolean value of check result + */ @Override protected boolean matchesDataAttributes(String dataAttribute){ return super.matchesDataAttributes(dataAttribute) || @@ -46,10 +140,94 @@ protected boolean matchesDataAttributes(String dataAttribute){ currentElem.getGSEControl().stream().anyMatch(gse -> gse.getName().equals(dataAttribute)); } + /** + * Remove all SMVControl and GSEControl of current LN0 + */ @Override public void removeAllControlBlocksAndDatasets() { super.removeAllControlBlocksAndDatasets(); currentElem.unsetGSEControl(); currentElem.unsetSampledValueControl(); } + + /** + * Construct ResumedDataTemplate object with DO and DA attributes + * @param doName The value of the name attribute of DO object + * @param daTypeName DaTypeName object + * @return ResumedDataTemplate + */ + private ResumedDataTemplate getResumedDataTemplate(String doName, DaTypeName daTypeName) { + ResumedDataTemplate filter = new ResumedDataTemplate(); + filter.setLnClass(getLNClass()); + filter.setLnInst(getLNInst()); + filter.setPrefix(getPrefix()); + filter.setLnType(getLnType()); + filter.setDoName(new DoTypeName(doName)); + filter.setDaName(daTypeName); + return filter; + } + + /** + * Verify and update LDevice status in parent Node + * @param iedNameLDeviceInstList pair of Ied name and LDevice inst attributes + * @return Set of Errors + */ + public List checkAndUpdateLDeviceStatus(List> iedNameLDeviceInstList) { + List errors = new ArrayList<>(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLDeviceInstList); + final String iedName = getParentAdapter().getParentAdapter().getName(); + final String ldInst = getParentAdapter().getInst(); + DaTypeName daTypeNameBeh = new DaTypeName(); + daTypeNameBeh.setName(STVAL); + daTypeNameBeh.setBType(TPredefinedBasicTypeEnum.ENUM); + daTypeNameBeh.setFc(TFCEnum.ST); + ResumedDataTemplate daiBehFilter = getResumedDataTemplate(BEHAVIOUR_DO_NAME, daTypeNameBeh); + List daiBehList = getDAI(daiBehFilter, false); + if (daiBehList.isEmpty()) { + errors.add(buildErrorDescriptionMessage("The LDevice doesn't have a DO @name='Beh' OR its associated DA@fc='ST' AND DA@name='stVal'")); + return errors; + } + Set enumValues = getEnumValues(daiBehList.get(0).getDaName().getType()); + List compasLDevicePrivateList = PrivateService.getCompasPrivates(getParentAdapter().getCurrentElem(), TCompasLDevice.class); + if (compasLDevicePrivateList.isEmpty()) { + errors.add(buildErrorDescriptionMessage("The LDevice doesn't have a Private compas:LDevice.")); + return errors; + } + if (!compasLDevicePrivateList.get(0).isSetLDeviceStatus()) { + errors.add(buildErrorDescriptionMessage("The Private compas:LDevice doesn't have the attribute 'LDeviceStatus'")); + return errors; + } + TCompasLDeviceStatus compasLDeviceStatus = compasLDevicePrivateList.get(0).getLDeviceStatus(); + DaTypeName daTypeNameMod = new DaTypeName(); + daTypeNameMod.setName(STVAL); + ResumedDataTemplate daiModFilter = getResumedDataTemplate(MOD_DO_NAME, daTypeNameMod); + List daiModList = getDAI(daiModFilter, false); + if (daiModList.isEmpty()) { + errors.add(buildErrorDescriptionMessage("The LDevice doesn't have a DO @name='Mod'")); + return errors; + } + ResumedDataTemplate newDaModToSetInLN0 = daiModList.get(0); + String initialValue = newDaModToSetInLN0.getDaName().getDaiValues().isEmpty() ? "" : newDaModToSetInLN0.getDaName().getDaiValues().values().toArray()[0].toString(); + lDeviceActivation.checkLDeviceActivationStatus(iedName, ldInst, compasLDeviceStatus, enumValues); + if(lDeviceActivation.isUpdatable()){ + if(!initialValue.equals(lDeviceActivation.getNewVal())) { + newDaModToSetInLN0.setVal(lDeviceActivation.getNewVal()); + updateDAI(newDaModToSetInLN0); + } + }else { + if(lDeviceActivation.getErrorMessage() != null) { + errors.add(buildErrorDescriptionMessage(lDeviceActivation.getErrorMessage()));} + } + return errors; + } + + /** + * builds message with message content and xpath + * @param message message to return + * @return error description with message and xpath as SclReport.ErrorDescription object + */ + private SclReport.ErrorDescription buildErrorDescriptionMessage(String message){ + return SclReport.ErrorDescription.builder().message(message).xpath(getXPath()).build(); + } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java index f281540a5..b79ebeb24 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java @@ -5,19 +5,88 @@ package org.lfenergy.compas.sct.commons.scl.ied; import org.lfenergy.compas.scl2007b4.model.TLN; +import org.lfenergy.compas.scl2007b4.model.TServiceType; +import org.lfenergy.compas.sct.commons.dto.ExtRefInfo; +import org.lfenergy.compas.sct.commons.dto.ExtRefSignalInfo; +import org.lfenergy.compas.sct.commons.dto.ResumedDataTemplate; +import org.lfenergy.compas.sct.commons.scl.ObjectReference; +import org.lfenergy.compas.sct.commons.util.Utils; +import java.util.List; + +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.LNAdapter LNAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link LNAdapter#getDataTypeTemplateAdapter Returns the value of the DataTypeTemplateAdapter reference object}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link LNAdapter#getLNInst Returns the value of the inst attribute}
    • + *
    • {@link LNAdapter#getLNClass Returns the value of the lnClass attribute}
    • + *
    • {@link LNAdapter#getLnType Returns the value of the lnTYpe attribute}
    • + *
    • {@link LNAdapter#getLNodeName Returns the logical node name LNName = prefix + lnClass + lnInst}
    • + * + *
    • {@link LNAdapter#getExtRefs() Returns the value of the TExtRef containment reference list}
    • + *
    • {@link LNAdapter#getExtRefs(ExtRefSignalInfo) Returns the value of the TExtRef containment reference list By ExtRefSignalInfo }
    • + *
    • {@link LNAdapter#getExtRefsBySignalInfo(ExtRefSignalInfo) Returns the value of the TExtRef containment reference list By ExtRefSignalInfo }
    • + * + *
    • {@link LNAdapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
    • + *
    • {@link LNAdapter#getDAIValues(ResumedDataTemplate) Returns DAI (sGroup, value) containment reference list By ResumedDataTemplate filter}
    • + + *
    • {@link LNAdapter#getDataSet(ExtRefInfo) Returns the value of the TDataSet containment reference list By ExtRefInfo }
    • + *
    • {@link LNAdapter#getDataSetByRef(String) Returns the value of the TDataSet object reference By the value of the name attribute }
    • + * + *
    • {@link LNAdapter#getControlSetByExtRefInfo(ExtRefInfo) Returns the value of the ControlBlock containment reference list By ExtRefInfo }
    • + *
    • {@link LNAdapter#getControlBlocks(List, TServiceType) Returns the value of the ControlBlock containment reference list that match datSet value of given TDataSet }
    • + *
    • {@link LNAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link LNAdapter#removeAllControlBlocksAndDatasets() Remove all ControlBlock}
    • + *
    + *
  5. Checklist functions
  6. + *
      + *
    • {@link LNAdapter#matches(ObjectReference) Check whether the section DataName of given ObjectReference match current LNAdapter DataName}
    • + *
    • {@link LNAdapter#matchesDataAttributes(String) Check whether the section DataName of given ObjectReference match current LNAdapter DataName Excluding DataName from DataTypeTemplat}
    • + *
    + *
+ *
+ *
+ *      ObjectReference: LDName/LNName.DataName[.DataName[…]].DataAttributeName[.DAComponentName[ ….]]
+ *      LDName = "name" attribute of IEDName element + "inst" attribute of LDevice element
+ *      LNName = "prefix" + "lnClass" + "lnInst"
+ *  
+ * @see org.lfenergy.compas.scl2007b4.model.TAnyLN + * @see org.lfenergy.compas.sct.commons.scl.ied.AbstractLNAdapter + */ public class LNAdapter extends AbstractLNAdapter{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public LNAdapter(LDeviceAdapter parentAdapter, TLN currentElem) { super(parentAdapter, currentElem); } + /** + * Gets LNode class type + * @return TLN.class + */ @Override protected Class getElementClassType() { return TLN.class; } + /** + * Gets LNClass enum value of current LNode + * @return LNClass value + */ @Override public String getLNClass() { @@ -27,20 +96,41 @@ public String getLNClass() { return null; } + /** + * Gets LNInst value of current LNode + * @return LNInst value + */ @Override public String getLNInst() { return currentElem.getInst(); } + /** + * Gets Prefix value of current LNode + * @return Prefix value + */ @Override public String getPrefix() { return currentElem.getPrefix(); } + + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { // the contains method compares object ref by default // as there's no equals method in TLN return parentAdapter.getCurrentElem().getLN().contains(currentElem); } + + @Override + protected String elementXPath() { + return String.format("LN[%s and %s and %s]", + Utils.xpathAttributeFilter("lnClass", currentElem.isSetLnClass() ? currentElem.getLnClass() : null), + Utils.xpathAttributeFilter("inst", currentElem.isSetInst() ? currentElem.getInst() : null), + Utils.xpathAttributeFilter("lnType", currentElem.isSetLnType() ? currentElem.getLnType() : null)); + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterBuilder.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterBuilder.java index 6926b15e7..326acdf40 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterBuilder.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterBuilder.java @@ -7,6 +7,17 @@ import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; import org.lfenergy.compas.sct.commons.exception.ScdException; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.LNAdapterBuilder LNAdapterBuilder}. + *

+ * The following features are supported: + *

+ *
    + *
  • {@link LNAdapterBuilder#build() Returns Adapter of Given TAnyLN attributes }
  • + *
+ * @see org.lfenergy.compas.scl2007b4.model.TAnyLN + */ public class LNAdapterBuilder { private String lnInst = ""; private String prefix = ""; diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapter.java index 39f04e794..b6fbe737d 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapter.java @@ -8,19 +8,64 @@ import org.lfenergy.compas.scl2007b4.model.TSDI; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.RootSDIAdapter DOIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link RootSDIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link RootSDIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link RootSDIAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link RootSDIAdapter#addSDOI Add TSDI under this object}
    • + *
    • {@link RootSDIAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TSDI + */ public class RootSDIAdapter extends SclElementAdapter implements IDataParentAdapter{ + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected RootSDIAdapter(DOIAdapter parentAdapter, TSDI currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSDIOrDAI().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("SDI[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + + + /** + * Gets in current root SDI specific SDI by its name + * @param name name of SDI to get + * @return SDIAdapter object + * @throws ScdException throws when DAI unknown in current root SDI + */ public SDIAdapter getStructuredDataAdapterByName(String name) throws ScdException { return currentElem.getSDIOrDAI() .stream() @@ -34,6 +79,12 @@ public SDIAdapter getStructuredDataAdapterByName(String name) throws ScdExceptio )); } + /** + * Gets in current root SDI specific DAI by its name + * @param sName name of DAI to get + * @return DAIAdapter object + * @throws ScdException throws when DAI unknown in current root SDI + */ @Override public DAIAdapter getDataAdapterByName(String sName) throws ScdException { return currentElem.getSDIOrDAI() @@ -48,6 +99,12 @@ public DAIAdapter getDataAdapterByName(String sName) throws ScdException { )); } + /** + * Adds in current root SDI a specific DAI + * @param name name of DAI to add + * @param isUpdatable updatability state of DAI + * @return DAIAdapter object + */ @Override public DAIAdapter addDAI(String name, boolean isUpdatable) { TDAI tdai = new TDAI(); @@ -57,6 +114,11 @@ public DAIAdapter addDAI(String name, boolean isUpdatable) { return new DAIAdapter(this,tdai); } + /** + * Adds in current root SDI a specific SDOI + * @param sdoName name of SDOI to add + * @return IDataParentAdapter object as added SDOI + */ @Override public IDataParentAdapter addSDOI(String sdoName) { TSDI tsdi = new TSDI(); @@ -65,6 +127,28 @@ public IDataParentAdapter addSDOI(String sdoName) { return new SDIAdapter(this,tsdi); } + /** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.RootSDIAdapter.DAIAdapter RootSDIAdapter.DAIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DAIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link DAIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DAIAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link DAIAdapter#addSDOI Add TSDI under this object}
    • + *
    + * + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDAI + */ public static class DAIAdapter extends AbstractDAIAdapter { public DAIAdapter(RootSDIAdapter rootSDIAdapter, TDAI tdai) { @@ -76,5 +160,11 @@ protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSDIOrDAI().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DAI[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapter.java index 245cb1dba..5c861dd12 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapter.java @@ -8,15 +8,48 @@ import org.lfenergy.compas.scl2007b4.model.TSDI; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.Objects; +/** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.SDIAdapter DOIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link SDIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link SDIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link SDIAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link SDIAdapter#addSDOI Add TSDI under this object}
    • + *
    • {@link SDIAdapter#addPrivate Add TPrivate under this object}
    • + *
    + * + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TSDI + */ public class SDIAdapter extends SclElementAdapter implements IDataParentAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ protected SDIAdapter(SclElementAdapter parentAdapter, TSDI currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { if(parentAdapter.getClass().equals(RootSDIAdapter.class)){ @@ -25,6 +58,18 @@ protected boolean amChildElementRef() { return SDIAdapter.class.cast(parentAdapter).getCurrentElem().getSDIOrDAI().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("SDI[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } + + /** + * Gets in current SDI specific SDI by its name + * @param sName name of SDI to get + * @return SDIAdapter object + * @throws ScdException throws when DAI unknown in current SDI + */ @Override public SDIAdapter getStructuredDataAdapterByName(String sName) throws ScdException { return currentElem.getSDIOrDAI() @@ -41,7 +86,12 @@ public SDIAdapter getStructuredDataAdapterByName(String sName) throws ScdExcepti ); } - + /** + * Gets in current SDI specific DAI by its name + * @param sName name of DAI to get + * @return DAIAdapter object + * @throws ScdException throws when DAI unknown in current SDI + */ @Override public DAIAdapter getDataAdapterByName(String sName) throws ScdException { return currentElem.getSDIOrDAI() @@ -58,6 +108,12 @@ public DAIAdapter getDataAdapterByName(String sName) throws ScdException { ); } + /** + * Adds in current SDI a specific DAI + * @param name name of DAI to add + * @param isUpdatable updatability state of DAI + * @return DAIAdapter object + */ @Override public DAIAdapter addDAI(String name, boolean isUpdatable) { TDAI tdai = new TDAI(); @@ -67,6 +123,11 @@ public DAIAdapter addDAI(String name, boolean isUpdatable) { return new DAIAdapter(this,tdai); } + /** + * Adds in current SDI a specific SDOI + * @param sdoName name of SDOI to add + * @return IDataParentAdapter object as added SDOI + */ @Override public SDIAdapter addSDOI(String sdoName) { TSDI tsdi = new TSDI(); @@ -75,6 +136,28 @@ public SDIAdapter addSDOI(String sdoName) { return new SDIAdapter(this,tsdi); } + /** + * A representation of the model object + * {@link org.lfenergy.compas.sct.commons.scl.ied.SDIAdapter.DAIAdapter SDIAdapter.DAIAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link DAIAdapter#getStructuredDataAdapterByName(String) Returns the value of the Child Adapter object reference}
    • + *
    • {@link DAIAdapter#getDataAdapterByName(String) Returns the value of the Child Adapter object reference By Name}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link DAIAdapter#addDAI Add TDAI under this object}
    • + *
    • {@link DAIAdapter#addSDOI Add TSDI under this object}
    • + *
    • {@link DAIAdapter#addPrivate Add TPrivate under this object}
    • + *
    + *
+ * + * @see org.lfenergy.compas.scl2007b4.model.TDAI + */ public static class DAIAdapter extends AbstractDAIAdapter { protected DAIAdapter(SDIAdapter parentAdapter, TDAI currentElem) { @@ -86,5 +169,10 @@ protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSDIOrDAI().contains(currentElem); } + @Override + protected String elementXPath() { + return String.format("DAI[%s]", + Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); + } } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/package-info.java new file mode 100644 index 000000000..5d8086435 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/package-info.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

scl.ied is a group of services for operating on + * {@link org.lfenergy.compas.scl2007b4.model.TIED IED} object + *

+ * @see org.lfenergy.compas.scl2007b4.model.TAccessPoint + * @see org.lfenergy.compas.scl2007b4.model.TServices + * @see org.lfenergy.compas.scl2007b4.model.TLDevice + * @see org.lfenergy.compas.scl2007b4.model.TLNode + * @see Issue !3 + * @see Issue !5 + */ +package org.lfenergy.compas.sct.commons.scl.ied; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/package-info.java new file mode 100644 index 000000000..9385c8a5a --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/package-info.java @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +/** + *

commons.scl is a group of services for operating on + * {@link org.lfenergy.compas.scl2007b4.model.THeader Header} , + * {@link org.lfenergy.compas.scl2007b4.model.TSubstation Substation} , + * {@link org.lfenergy.compas.scl2007b4.model.TCommunication Communication} , + * {@link org.lfenergy.compas.scl2007b4.model.TIED IED} , + * {@link org.lfenergy.compas.scl2007b4.model.TDataTypeTemplates DataTypeTemplates} objects + *

+ * @see org.lfenergy.compas.scl2007b4.model.SCL + */ +package org.lfenergy.compas.sct.commons.scl; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/BayAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/BayAdapter.java index 7c178bc49..cb805f7f5 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/BayAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/BayAdapter.java @@ -10,14 +10,55 @@ import java.util.stream.Stream; +/** + * A representation of the model object + * {@link BayAdapter BayAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link BayAdapter#streamFunctionAdapters Returns the value of the FunctionAdapter reference object as stream}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link BayAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link BayAdapter#elementXPath Returns the XPATH for this object}
    • + *
    + *
+ *

+ * XPATH Example : + *

+ *          Bay[@name="BayName"]
+ *      
+ *

+ * @see org.lfenergy.compas.scl2007b4.model.TFunction + * @see org.lfenergy.compas.scl2007b4.model.TCompasICDHeader + * @see Issue !124 (update LNode iedName) + */ public class BayAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + */ public BayAdapter(VoltageLevelAdapter parentAdapter){super(parentAdapter);} + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public BayAdapter(VoltageLevelAdapter parentAdapter, TBay currentElem){ super (parentAdapter, currentElem); } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param bayName BAY name reference + */ public BayAdapter(VoltageLevelAdapter parentAdapter, String bayName) throws ScdException { super(parentAdapter); TBay tBay = parentAdapter.getCurrentElem().getBay() @@ -28,20 +69,33 @@ public BayAdapter(VoltageLevelAdapter parentAdapter, String bayName) throws ScdE setCurrentElem(tBay); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getBay().contains(currentElem); } + /** + * Returns XPath path to current Bay + * @return path to current Bay + */ @Override protected String elementXPath() { return String.format("Bay[%s]", Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); } + /** + * Gets Functions in Stream from current Bay + * @return Stream of FunctionAdapter object from Bay + */ public Stream streamFunctionAdapters(){ if (!currentElem.isSetFunction()){ return Stream.empty(); } return currentElem.getFunction().stream().map(tFunction -> new FunctionAdapter(this, tFunction)); } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapter.java index c2062ea26..4dea90f95 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapter.java @@ -3,77 +3,60 @@ // SPDX-License-Identifier: Apache-2.0 package org.lfenergy.compas.sct.commons.scl.sstation; -import org.apache.commons.lang3.StringUtils; -import org.lfenergy.compas.scl2007b4.model.TCompasICDHeader; import org.lfenergy.compas.scl2007b4.model.TFunction; -import org.lfenergy.compas.scl2007b4.model.TLNode; -import org.lfenergy.compas.scl2007b4.model.TPrivate; -import org.lfenergy.compas.sct.commons.exception.ScdException; -import org.lfenergy.compas.sct.commons.scl.PrivateService; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.util.Utils; -import java.util.List; -import java.util.ListIterator; -import java.util.stream.Collectors; - -import static org.lfenergy.compas.sct.commons.util.PrivateEnum.COMPAS_ICDHEADER; - +/** + * A representation of the model object + * {@link FunctionAdapter FunctionAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Principal functions
  2. + *
      + *
    • {@link FunctionAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link FunctionAdapter#elementXPath Returns the XPATH for this object}
    • + *
    + *
+ *

+ * XPATH Example : + *

+ *           Function[@name="functionName"]
+ *       
+ *

+ * @see org.lfenergy.compas.scl2007b4.model.TLNode + * @see org.lfenergy.compas.scl2007b4.model.TCompasICDHeader + * @see Issue !124 (update LNode iedName) + */ public class FunctionAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public FunctionAdapter(BayAdapter parentAdapter, TFunction currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getFunction().contains(currentElem); } + /** + * Returns XPath path to current Function + * @return path to current Function + */ @Override protected String elementXPath() { return String.format("Function[%s]", Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); } - public void updateLNodeIedNames() throws ScdException { - if (!currentElem.isSetLNode()) { - return; - } - // use ListIterator to add & remove items in place - ListIterator lNodeIterator = currentElem.getLNode().listIterator(); - while (lNodeIterator.hasNext()) { - TLNode lNode = lNodeIterator.next(); - List icdHeaders = PrivateService.getCompasPrivates(lNode, TCompasICDHeader.class); - if (icdHeaders.isEmpty()){ - throw new ScdException(getXPath() + " doesn't contain any Private/compas:ICDHeader"); - } - if (icdHeaders.size() == 1) { - setLNodeIedName(lNode, icdHeaders.get(0).getIEDName()); - } else { - lNodeIterator.remove(); - PrivateService.removePrivates(lNode, COMPAS_ICDHEADER); - List newLNodes = distributeIcdHeaders(lNode, icdHeaders); - newLNodes.forEach(lNodeIterator::add); - } - } - } - - private List distributeIcdHeaders(TLNode lNode, List compasICDHeaders) { - LNodeAdapter lNodeAdapter = new LNodeAdapter(null, lNode); - return compasICDHeaders.stream().map(compasICDHeader -> { - TPrivate icdHeaderPrivate = PrivateService.createPrivate(compasICDHeader); - TLNode newLNode = lNodeAdapter.deepCopy(); - newLNode.getPrivate().add(icdHeaderPrivate); - setLNodeIedName(newLNode, compasICDHeader.getIEDName()); - return newLNode; - }).collect(Collectors.toList()); - } - - private void setLNodeIedName(TLNode lNode, String privateIedName) { - if (StringUtils.isBlank(privateIedName)){ - LNodeAdapter lNodeAdapter = new LNodeAdapter(null, lNode); - throw new ScdException(getXPath() + lNodeAdapter.getXPath() + "/Private/compas:ICDHeader/@IEDName is missing or is blank"); - } - lNode.setIedName(privateIedName); - } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/LNodeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/LNodeAdapter.java index 51fec68b9..0b4be05c8 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/LNodeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/LNodeAdapter.java @@ -10,18 +10,54 @@ import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.util.Utils; +/** + * A representation of the model object + * {@link LNodeAdapter LNodeAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Principal functions
  2. + *
      + *
    • {@link LNodeAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link LNodeAdapter#elementXPath Returns the XPATH for this object}
    • + *
    • {@link LNodeAdapter#deepCopy Copy the LNode object}
    • + *
    + *
+ *

+ * XPATH Example : + *

+ *      LNode[@iedName="iedName1" and @ldInst="ldInst1" and @Prefix="prefix1" and @lnClass="lnClass1" and @lnInst="lnInst1"]
+ *    
+ *

+ * @see org.lfenergy.compas.scl2007b4.model.TCompasICDHeader + * @see Issue !124 (update LNode iedName) + */ public class LNodeAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public LNodeAdapter(FunctionAdapter parentAdapter, TLNode currentElem) { super(parentAdapter, currentElem); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { TFunction parentElem = parentAdapter.getCurrentElem(); return parentElem.isSetLNode() && parentElem.getLNode().contains(this.currentElem); } + /** + * Copies current Substation LNode to new one + * @return copie of current Substation LNode + */ public TLNode deepCopy() { TLNode newLNode = new TLNode(); newLNode.setDesc(currentElem.getDesc()); @@ -50,6 +86,10 @@ public TLNode deepCopy() { return newLNode; } + /** + * Returns XPath path to current Substation LNode + * @return path to current Substation LNode + */ @Override protected String elementXPath() { return String.format("LNode[%s and %s and %s and %s and %s]", diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapter.java index 6b1e34200..02d9ce17c 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapter.java @@ -4,25 +4,69 @@ package org.lfenergy.compas.sct.commons.scl.sstation; +import org.apache.commons.lang3.tuple.Pair; +import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; import org.lfenergy.compas.scl2007b4.model.TSubstation; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.util.Utils; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * A representation of the model object + * {@link SubstationAdapter SubstationAdapter}. + *

+ * The following features are supported: + *

+ *
    + *
  1. Adapter
  2. + *
      + *
    • {@link SubstationAdapter#getVoltageLevelAdapter Returns the value of the VoltageLevelAdapter reference object By Name}
    • + *
    • {@link SubstationAdapter#streamVoltageLevelAdapters Returns the value of the VoltageLevelAdapter reference object as stream}
    • + *
    + *
  3. Principal functions
  4. + *
      + *
    • {@link SubstationAdapter#addPrivate Add TPrivate under this object}
    • + *
    • {@link SubstationAdapter#elementXPath Returns the XPATH for this object}
    • + *
    + *

    + * XPATH Example : + *

    + *           Substation[@name="SUBSTATION"]
    + *       
    + *

    + * @see org.lfenergy.compas.scl2007b4.model.TVoltageLevel + * @see org.lfenergy.compas.scl2007b4.model.TCompasICDHeader + */ public class SubstationAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + */ public SubstationAdapter(SclRootAdapter parentAdapter) { super(parentAdapter); } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param currentElem Current reference + */ public SubstationAdapter(SclRootAdapter parentAdapter, TSubstation currentElem) { super(parentAdapter, currentElem); } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param ssName Substation name reference + */ public SubstationAdapter(SclRootAdapter parentAdapter, String ssName) throws ScdException { super(parentAdapter); TSubstation tSubstation = parentAdapter.getCurrentElem().getSubstation() @@ -33,16 +77,29 @@ public SubstationAdapter(SclRootAdapter parentAdapter, String ssName) throws Scd setCurrentElem(tSubstation); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getSubstation().contains(currentElem); } + /** + * Returns XPath path to current Substation + * @return path to current Substation + */ @Override protected String elementXPath() { return String.format("Substation[%s]", Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); } + /** + * Gets Voltage Level from current Substation + * @param vLevelName name of Voltage Level to get + * @return optional of VoltageLevelAdapter object + */ public Optional getVoltageLevelAdapter(String vLevelName) { return currentElem.getVoltageLevel() .stream() @@ -51,6 +108,10 @@ public Optional getVoltageLevelAdapter(String vLevelName) { .findFirst(); } + /** + * Gets Voltage Level in Stream from current Substation + * @return Stream of VoltageLevelAdapter object from Substation + */ public Stream streamVoltageLevelAdapters() { if (!currentElem.isSetVoltageLevel()){ return Stream.empty(); @@ -58,4 +119,18 @@ public Stream streamVoltageLevelAdapters() { return currentElem.getVoltageLevel().stream().map(tVoltageLevel -> new VoltageLevelAdapter(this, tVoltageLevel)); } + /** + * Gets a pair of IedName and LDevice inst from Substation LNodes for LN0 type object + * @return a pair of Ied name and LDevice inst attributes + */ + public List> getIedAndLDeviceNamesForLN0FromLNode() { + return streamVoltageLevelAdapters() + .flatMap(VoltageLevelAdapter::streamBayAdapters) + .flatMap(BayAdapter::streamFunctionAdapters) + .flatMap(functionAdapter -> functionAdapter.getCurrentElem().getLNode().stream()) + .filter(tlNode -> tlNode.getLnClass().contains(TLLN0Enum.LLN_0.value())) + .map(tlNode -> Pair.of(tlNode.getIedName(), tlNode.getLdInst())) + .collect(Collectors.toList()); + } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/VoltageLevelAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/VoltageLevelAdapter.java index de7408704..e3aa7a883 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/VoltageLevelAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/VoltageLevelAdapter.java @@ -11,14 +11,55 @@ import java.util.Optional; import java.util.stream.Stream; +/** + * A representation of the model object + * {@link VoltageLevelAdapter VoltageLevelAdapter}. + *

    + * The following features are supported: + *

    + *
      + *
    1. Adapter
    2. + *
        + *
      • {@link VoltageLevelAdapter#getBayAdapter(String) Returns the value of the BayAdapter reference object By Name}
      • + *
      • {@link VoltageLevelAdapter#streamBayAdapters Returns the value of the BayAdapter reference object as stream}
      • + *
      + *
    3. Principal functions
    4. + *
        + *
      • {@link VoltageLevelAdapter#addPrivate Add TPrivate under this object}
      • + *
      • {@link VoltageLevelAdapter#elementXPath Returns the XPATH for this object}
      • + *
      + *

      + * XPATH Example : + *

      + *          VoltageLevel[@name="VOLTAGE_LEVEL"]
      + *      
      + *

      + * @see org.lfenergy.compas.scl2007b4.model.TBay + * @see org.lfenergy.compas.scl2007b4.model.TCompasICDHeader + * @see Issue !124 (update LNode iedName) + */ public class VoltageLevelAdapter extends SclElementAdapter { + /** + * Constructor + * @param parentAdapter Parent container reference + */ public VoltageLevelAdapter(SubstationAdapter parentAdapter) {super(parentAdapter);} + /** + * Constructor + * @param substationAdapter Parent container reference + * @param currentElem Current reference + */ public VoltageLevelAdapter(SubstationAdapter substationAdapter, TVoltageLevel currentElem){ super(substationAdapter, currentElem); } + /** + * Constructor + * @param parentAdapter Parent container reference + * @param vLevelName Voltage Level name reference + */ public VoltageLevelAdapter(SubstationAdapter parentAdapter, String vLevelName) throws ScdException { super(parentAdapter); TVoltageLevel tVoltageLevel = parentAdapter.getCurrentElem().getVoltageLevel() @@ -29,16 +70,29 @@ public VoltageLevelAdapter(SubstationAdapter parentAdapter, String vLevelName) t setCurrentElem(tVoltageLevel); } + /** + * Check if node is child of the reference node + * @return link parent child existence + */ @Override protected boolean amChildElementRef() { return parentAdapter.getCurrentElem().getVoltageLevel().contains(currentElem); } + /** + * Returns XPath path to current Substation + * @return path to current Substation + */ @Override protected String elementXPath() { return String.format("VoltageLevel[%s]", Utils.xpathAttributeFilter("name", currentElem.isSetName() ? currentElem.getName() : null)); } + /** + * Gets Bay from current VoltageLevel + * @param bayName name of Bay to get + * @return optional of BayAdapter object + */ public Optional getBayAdapter(String bayName) { return currentElem.getBay() .stream() @@ -47,6 +101,10 @@ public Optional getBayAdapter(String bayName) { .findFirst(); } + /** + * Gets Ba in Stream from current VoltageLevel + * @return Stream of BayAdapter object from VoltageLevel + */ public Stream streamBayAdapters() { if (!currentElem.isSetBay()){ return Stream.empty(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/package-info.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/package-info.java new file mode 100644 index 000000000..2f3987594 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/sstation/package-info.java @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 +/** + *

      scl.sstation is a group of services for operating on + * {@link org.lfenergy.compas.scl2007b4.model.TSubstation Substation} object + *

      + * @see org.lfenergy.compas.scl2007b4.model.TVoltageLevel + * @see org.lfenergy.compas.scl2007b4.model.TFunction + * @see org.lfenergy.compas.scl2007b4.model.TBay + * @see org.lfenergy.compas.scl2007b4.model.TLNode + * @see org.lfenergy.compas.scl2007b4.model.TCompasICDHeader + */ +package org.lfenergy.compas.sct.commons.scl.sstation; \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CommonConstants.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CommonConstants.java index 752d71b0f..f6dd373ad 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CommonConstants.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CommonConstants.java @@ -4,6 +4,10 @@ package org.lfenergy.compas.sct.commons.util; + +/** + * A representation of constants used in application + */ public final class CommonConstants { public static final String ICD_SYSTEM_VERSION_UUID = "ICDSystemVersionUUID"; @@ -11,7 +15,13 @@ public final class CommonConstants { public static final String HEADER_ID = "headerId"; public static final String HEADER_VERSION = "headerVersion"; public static final String HEADER_REVISION = "headerRevision"; + public static final String BEHAVIOUR_DO_NAME = "Beh"; + public static final String MOD_DO_NAME = "Mod"; + public static final String STVAL = "stVal"; + /** + * Private Controlller, should not be instanced + */ private CommonConstants() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateEnum.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateEnum.java index b9a1f8fa5..9abf4a580 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateEnum.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateEnum.java @@ -14,6 +14,10 @@ import java.util.function.Function; import java.util.stream.Collectors; +/** + * A representation of the literals of the enumeration 'Private Enum', + * and utility methods for working with them. + */ public enum PrivateEnum { COMPAS_BAY("COMPAS-Bay", TCompasBay.class), diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/STValEnum.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/STValEnum.java new file mode 100644 index 000000000..3b1559172 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/STValEnum.java @@ -0,0 +1,21 @@ +/* + * // SPDX-FileCopyrightText: 2022 RTE FRANCE + * // + * // SPDX-License-Identifier: Apache-2.0 + */ + +package org.lfenergy.compas.sct.commons.util; + +/** + * A representation of a specific object TDAI name that have attribute name STVal. + * + * @see org.lfenergy.compas.scl2007b4.model.TPredefinedBasicTypeEnum + */ +public enum STValEnum { + ON("on"), + OFF("off"); + public final String value; + STValEnum(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java index cb84ae828..f9346c2e1 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java @@ -18,15 +18,27 @@ public final class Utils { public static final String LEAVING_PREFIX = "<<< Leaving: ::"; public static final String ENTERING_PREFIX = ">>> Entering: ::"; + /** + * Private Controlller, should not be instanced + */ private Utils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } + /** + * Generic message + * @return >>> Entering: :: + methode name + */ public static String entering() { return ENTERING_PREFIX + getMethodName(); } + /** + * Generic message for leaving a methode call, its calculates CPU time taken by methode execution + * @param startTime methode call start time + * @return message with methode name and duration + */ public static String leaving(Long startTime) { if (startTime == null || startTime <= 0) { return LEAVING_PREFIX + @@ -39,6 +51,10 @@ public static String leaving(Long startTime) { " sec."; } + /** + * Gets methode name + * @return methode name + */ private static String getMethodName() { try { return (new Throwable()).getStackTrace()[2].getMethodName(); @@ -47,6 +63,10 @@ private static String getMethodName() { } } + /** + * Generic message for leaving a methode call + * @return >>> Entering: :: + methode name + */ public static String leaving() { return LEAVING_PREFIX + getMethodName(); @@ -72,6 +92,12 @@ public static boolean equalsOrNotSet(T o1, T o2, Predicate isSet, Func return Objects.equals(getValue.apply(o1), getValue.apply(o2)); } + /** + * Builds string + * @param name name to display + * @param value value to display + * @return not (name) or name=value + */ public static String xpathAttributeFilter(String name, String value) { if (value == null){ return String.format("not(@%s)", name); @@ -80,6 +106,12 @@ public static String xpathAttributeFilter(String name, String value) { } } + /** + * Builds string + * @param name name to display + * @param value values to display + * @return not (name) or name=values + */ public static String xpathAttributeFilter(String name, Collection value) { if (value == null || value.isEmpty() || value.stream().allMatch(Objects::isNull)){ return String.format("not(@%s)", name); diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivationTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivationTest.java new file mode 100644 index 000000000..c553282be --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/LDeviceActivationTest.java @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.scl; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.lfenergy.compas.scl2007b4.model.TCompasLDeviceStatus; + +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class LDeviceActivationTest { + + @ParameterizedTest + @ValueSource(strings = {"ACTIVE", "UNTESTED"}) + void checkLDeviceActivationStatus_shouldReturnNoError_when_LDeviceStatusACTIVE_Or_UNTESTED_And_Contains_ON_And_LDeviceReferencedINLNode(String lDeviceStatus) { + // Given + List> iedNameLdInstList = List.of(Pair.of("iedName1", "ldInst1")); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.fromValue(lDeviceStatus), Set.of("on")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isTrue(); + assertThat(lDeviceActivation.getErrorMessage()).isNull(); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("on"); + } + + @ParameterizedTest + @ValueSource(strings = {"ACTIVE", "UNTESTED"}) + void checkLDeviceActivationStatus_shouldReturnError_when_LDeviceStatusACTIVE_Or_UNTESTED_And_Contains_ON_And_NotLDeviceReferencedINLNode(String lDeviceStatus) { + // Given + List> iedNameLdInstList = List.of(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst2", TCompasLDeviceStatus.valueOf(lDeviceStatus), Set.of("on")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isFalse(); + assertThat(lDeviceActivation.getErrorMessage()).isEqualTo("The LDevice cannot be set to 'off' but has not been selected into SSD."); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("on"); + } + + @ParameterizedTest + @ValueSource(strings = {"ACTIVE", "UNTESTED"}) + void checkLDeviceActivationStatus_shouldReturnError_when_LDeviceStatusACTIVE_Or_UNTESTED_And_Contains_OFF_And_LDeviceReferencedINLNode(String lDeviceStatus) { + // Given + List> iedNameLdInstList = List.of(Pair.of("iedName1", "ldInst1")); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("off"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.valueOf(lDeviceStatus), Set.of("off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isFalse(); + assertThat(lDeviceActivation.getErrorMessage()).isEqualTo( + "The LDevice cannot be set to 'on' but has been selected into SSD."); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + + @ParameterizedTest + @ValueSource(strings = {"ACTIVE", "UNTESTED"}) + void checkLDeviceActivationStatus_shouldReturnNoError_when_LDeviceStatusACTIVE_Or_UNTESTED_And_Contains_OFF_And_NotLDeviceReferencedINLNode(String lDeviceStatus) { + // Given + List> iedNameLdInstList = List.of(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.valueOf(lDeviceStatus), Set.of("off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isTrue(); + assertThat(lDeviceActivation.getErrorMessage()).isNull(); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + + @ParameterizedTest + @ValueSource(strings = {"ACTIVE", "UNTESTED"}) + void checkLDeviceActivationStatus_shouldReturnNoError_when_LDeviceStatusACTIVE_Or_UNTESTED_And_Contains_ON_And_OFF_And_LDeviceReferencedINLNode(String lDeviceStatus) { + // Given + List> iedNameLdInstList = List.of(Pair.of("iedName1", "ldInst1")); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("off"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.valueOf(lDeviceStatus), Set.of("on", "off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isTrue(); + assertThat(lDeviceActivation.getErrorMessage()).isNull(); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("on"); + } + + @ParameterizedTest + @ValueSource(strings = {"ACTIVE", "UNTESTED"}) + void checkLDeviceActivationStatus_shouldReturnNoError_when_LDeviceStatusACTIVE_Or_UNTESTED_And_Contains_ON_And_OFF_And_NotLDeviceReferencedINLNode(String lDeviceStatus) { + // Given + List> iedNameLdInstList = List.of(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("off"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst2", TCompasLDeviceStatus.valueOf(lDeviceStatus), Set.of("on", "off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isTrue(); + assertThat(lDeviceActivation.getErrorMessage()).isNull(); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + + @ParameterizedTest + @CsvSource(value = {"ACTIVE; iedName1; ldInst1","INACTIVE; iedName1; ldInst1", "UNTESTED; iedName1; ldInst1", "ACTIVE;;","INACTIVE;;", "UNTESTED;;"}, delimiter = ';') + void checkLDeviceActivationStatus_shouldReturnError_when_Contains_Not_ON_Nor_OFF(String lDeviceStatus, String iedName, String ldInst) { + // Given + List> iedNameLdInstList = List.of(Pair.of(iedName, ldInst)); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("off"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.valueOf(lDeviceStatus), Set.of()); + // Then + assertThat(lDeviceActivation.isUpdatable()).isFalse(); + assertThat(lDeviceActivation.getErrorMessage()).isEqualTo( + "The LDevice cannot be activated or desactivated because its BehaviourKind Enum contains NOT 'on' AND NOT 'off'."); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + + @Test + void checkLDeviceActivationStatus_shouldReturnError_when_LDeviceStatusINACTIVE_And_Contains_ON_And_LDeviceReferencedINLNode() { + // Given + List> iedNameLdInstList = List.of(Pair.of("iedName1", "ldInst1")); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.INACTIVE, Set.of("on")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isFalse(); + assertThat(lDeviceActivation.getErrorMessage()).isEqualTo("The LDevice is not qualified into STD but has been selected into SSD."); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("on"); + } + + @Test + void checkLDeviceActivationStatus_shouldReturnNoError_when_LDeviceStatusINACTIVE_And_Contains_ON_And_OFF_And_NotLDeviceReferencedINLNode() { + // Given + List> iedNameLdInstList = List.of(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.INACTIVE, Set.of("on", "off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isTrue(); + assertThat(lDeviceActivation.getErrorMessage()).isNull(); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + + @Test + void checkLDeviceActivationStatus_shouldReturnError_when_LDeviceStatusINACTIVE_And_Contains_ON_And_NotLDeviceReferencedINLNode() { + // Given + List> iedNameLdInstList = List.of(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.INACTIVE, Set.of("on")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isFalse(); + assertThat(lDeviceActivation.getErrorMessage()).isEqualTo("The LDevice cannot be set to 'off' but has not been selected into SSD."); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("on"); + } + + @Test + void checkLDeviceActivationStatus_shouldReturnNoError_when_LDeviceStatusINACTIVE_And_Contains_OFF_And_NotLDeviceReferencedINLNode() { + // Given + List> iedNameLdInstList = List.of(); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("on"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.INACTIVE, Set.of("off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isTrue(); + assertThat(lDeviceActivation.getErrorMessage()).isNull(); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + + @Test + void checkLDeviceActivationStatus_shouldReturnError_when_LDeviceStatusINACTIVE_And_Contains_ON_And_OFF_And_LDeviceReferencedINLNode() { + // Given + List> iedNameLdInstList = List.of(Pair.of("iedName1", "ldInst1")); + LDeviceActivation lDeviceActivation = new LDeviceActivation(iedNameLdInstList); + lDeviceActivation.setNewVal("off"); + // When + lDeviceActivation.checkLDeviceActivationStatus("iedName1", "ldInst1", TCompasLDeviceStatus.INACTIVE, Set.of("on", "off")); + // Then + assertThat(lDeviceActivation.isUpdatable()).isFalse(); + assertThat(lDeviceActivation.getErrorMessage()).isEqualTo( + "The LDevice is not qualified into STD but has been selected into SSD."); + assertThat(lDeviceActivation.getNewVal()).isEqualTo("off"); + } + +} \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java index 571891ae9..6133cd298 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java @@ -5,7 +5,11 @@ package org.lfenergy.compas.sct.commons.scl; import org.apache.commons.lang3.StringUtils; +import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.*; import org.lfenergy.compas.sct.commons.exception.ScdException; @@ -14,12 +18,15 @@ import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.*; import static org.lfenergy.compas.sct.commons.testhelpers.DataTypeUtils.createDa; import static org.lfenergy.compas.sct.commons.testhelpers.DataTypeUtils.createDo; import static org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller.assertIsMarshallable; +import static org.lfenergy.compas.sct.commons.testhelpers.marshaller.SclTestMarshaller.createWrapper; import static org.lfenergy.compas.sct.commons.util.PrivateEnum.COMPAS_SCL_FILE_TYPE; class SclServiceTest { @@ -701,4 +708,186 @@ void removeControlBlocksAndDatasetAndExtRefSrc_should_remove_srcXXX_attributes_o assertIsMarshallable(scl); } + private static Stream sclProviderMissingRequiredObjects() throws Exception { + SCL scl1 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd"); + SCL scl2 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd"); + SCL scl3 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd"); + SCL scl4 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd"); + Tuple[] scl1Errors = new Tuple[]{Tuple.tuple("The LDevice doesn't have a DO @name='Beh' OR its associated DA@fc='ST' AND DA@name='stVal'", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]")}; + Tuple[] scl2Errors = new Tuple[]{Tuple.tuple("The LDevice doesn't have a Private compas:LDevice.", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]")}; + Tuple[] scl3Errors = new Tuple[]{Tuple.tuple("The Private compas:LDevice doesn't have the attribute 'LDeviceStatus'", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]")}; + Tuple[] scl4Errors = new Tuple[]{Tuple.tuple("The LDevice doesn't have a DO @name='Mod'", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]")}; + return Stream.of( + Arguments.of("MissingDOBeh",scl1, scl1Errors), + Arguments.of("MissingLDevicePrivate",scl2, scl2Errors), + Arguments.of("MissingLDevicePrivateAttribute",scl3, scl3Errors), + Arguments.of("MissingDOMod",scl4, scl4Errors) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("sclProviderMissingRequiredObjects") + void updateLDeviceStatus_shouldReturnReportWithError_MissingRequiredObject(String testCase, SCL scl, Tuple... errors) { + // Given + assertTrue(getLDeviceStatusValue(scl, "IedName1", "LDSUIED").isPresent()); + assertEquals("off", getLDeviceStatusValue(scl, "IedName1", "LDSUIED").get().getValue()); + String before = createWrapper().marshall(scl); + // When + SclReport sclReport = SclService.updateLDeviceStatus(scl); + // Then + String after = createWrapper().marshall(sclReport.getSclRootAdapter().getCurrentElem()); + assertFalse(sclReport.isSuccess()); + assertThat(sclReport.getErrorDescriptionList()) + .hasSize(1) + .extracting(SclReport.ErrorDescription::getMessage, SclReport.ErrorDescription::getXpath) + .containsExactly(errors); + assertEquals("off", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName1", "LDSUIED").get().getValue()); + assertEquals(before, after); + } + + private static Stream sclProviderBasedLDeviceStatus() throws Exception { + SCL scl1 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd"); + SCL scl2 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd"); + SCL scl3 = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd"); + Tuple[] scl1Errors = new Tuple[]{Tuple.tuple("The LDevice cannot be set to 'off' but has not been selected into SSD.", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]"), + Tuple.tuple("The LDevice cannot be set to 'on' but has been selected into SSD.", + "/SCL/IED[@name=\"IedName2\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType2\"]"), + Tuple.tuple("The LDevice cannot be activated or desactivated because its BehaviourKind Enum contains NOT 'on' AND NOT 'off'.", + "/SCL/IED[@name=\"IedName3\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType3\"]" + )}; + Tuple[] scl2Errors = new Tuple[]{Tuple.tuple("The LDevice cannot be set to 'off' but has not been selected into SSD.", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]"), + Tuple.tuple("The LDevice cannot be set to 'on' but has been selected into SSD.", + "/SCL/IED[@name=\"IedName2\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType2\"]"), + Tuple.tuple("The LDevice cannot be activated or desactivated because its BehaviourKind Enum contains NOT 'on' AND NOT 'off'.", + "/SCL/IED[@name=\"IedName3\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType3\"]" + )}; + Tuple[] scl3Errors = new Tuple[]{Tuple.tuple("The LDevice is not qualified into STD but has been selected into SSD.", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]"), + Tuple.tuple("The LDevice cannot be set to 'on' but has been selected into SSD.", + "/SCL/IED[@name=\"IedName2\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType2\"]"), + Tuple.tuple("The LDevice cannot be activated or desactivated because its BehaviourKind Enum contains NOT 'on' AND NOT 'off'.", + "/SCL/IED[@name=\"IedName3\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType3\"]" + )}; + return Stream.of( + Arguments.of("ACTIVE", scl1, scl1Errors), + Arguments.of("UNTESTED", scl2, scl2Errors), + Arguments.of("INACTIVE", scl3, scl3Errors) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("sclProviderBasedLDeviceStatus") + void updateLDeviceStatus_shouldReturnReportWithError_WhenLDeviceStatusActiveOrUntestedOrInactive(String testCase, SCL scl, Tuple... errors) { + // Given + assertEquals("off", getLDeviceStatusValue(scl, "IedName1", "LDSUIED").get().getValue()); + assertEquals("on", getLDeviceStatusValue(scl, "IedName2", "LDSUIED").get().getValue()); + assertFalse(getLDeviceStatusValue(scl, "IedName3", "LDSUIED").isPresent()); + String before = createWrapper().marshall(scl); + // When + SclReport sclReport = SclService.updateLDeviceStatus(scl); + // Then + String after = createWrapper().marshall(sclReport.getSclRootAdapter().getCurrentElem()); + assertFalse(sclReport.isSuccess()); + assertThat(sclReport.getErrorDescriptionList()) + .hasSize(3) + .extracting(SclReport.ErrorDescription::getMessage, SclReport.ErrorDescription::getXpath) + .containsExactly(errors); + assertEquals("off", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName1", "LDSUIED").get().getValue()); + assertEquals("on", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName2", "LDSUIED").get().getValue()); + assertFalse(getLDeviceStatusValue(scl, "IedName3", "LDSUIED").isPresent()); + assertEquals(before, after); + } + + @Test + void updateLDeviceStatus_shouldReturnReportWithError_WhenAllLDeviceInactive_Test2() throws Exception { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd"); + assertEquals("off", getLDeviceStatusValue(scl, "IedName1", "LDSUIED").get().getValue()); + assertEquals("on", getLDeviceStatusValue(scl, "IedName2", "LDSUIED").get().getValue()); + assertFalse(getLDeviceStatusValue(scl, "IedName3", "LDSUIED").isPresent()); + // When + SclReport sclReport = SclService.updateLDeviceStatus(scl); + // Then + assertFalse(sclReport.isSuccess()); + assertThat(sclReport.getErrorDescriptionList()) + .hasSize(2) + .extracting(SclReport.ErrorDescription::getMessage, SclReport.ErrorDescription::getXpath) + .containsExactly(Tuple.tuple("The LDevice cannot be set to 'off' but has not been selected into SSD.", + "/SCL/IED[@name=\"IedName1\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType1\"]"), + Tuple.tuple("The LDevice is not qualified into STD but has been selected into SSD.", + "/SCL/IED[@name=\"IedName2\"]/LDevice[@inst=\"LDSUIED\"]/LN[lnClass=\"LLN0\" and @inst=\"\" and @lnType=\"LNType2\"]")); + assertEquals("off", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName1", "LDSUIED").get().getValue()); + assertEquals("on", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName2", "LDSUIED").get().getValue()); + assertTrue(getLDeviceStatusValue(scl, "IedName3", "LDSUIED").isPresent()); + assertEquals("off", getLDeviceStatusValue(scl, "IedName3", "LDSUIED").get().getValue()); + } + + + @Test + void updateLDeviceStatus_shouldReturnUpdatedFile() throws Exception { + // Given + SCL givenScl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_Template.scd"); + assertTrue(getLDeviceStatusValue(givenScl, "IedName1", "LDSUIED").isPresent()); + assertEquals("off", getLDeviceStatusValue(givenScl, "IedName1", "LDSUIED").get().getValue()); + + assertTrue(getLDeviceStatusValue(givenScl, "IedName2", "LDSUIED").isPresent()); + assertEquals("on", getLDeviceStatusValue(givenScl, "IedName2", "LDSUIED").get().getValue()); + + assertFalse(getLDeviceStatusValue(givenScl, "IedName3", "LDSUIED").isPresent()); + + // When + SclReport sclReport = SclService.updateLDeviceStatus(givenScl); + // Then + assertTrue(sclReport.isSuccess()); + assertTrue(getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName1", "LDSUIED").isPresent()); + assertEquals("on", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName1", "LDSUIED").get().getValue()); + + assertTrue(getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName2", "LDSUIED").isPresent()); + assertEquals("off", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName2", "LDSUIED").get().getValue()); + + assertTrue(getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName3", "LDSUIED").isPresent()); + assertEquals("off", getLDeviceStatusValue(sclReport.getSclRootAdapter().getCurrentElem(), "IedName3", "LDSUIED").get().getValue()); + } + + @Test + void updateLDeviceStatus_shouldReturnError_when_DaiNotUpdatable() throws Exception { + // Given + SCL givenScl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd"); + assertTrue(getLDeviceStatusValue(givenScl, "IedName1", "LDSUIED").isPresent()); + assertEquals("off", getLDeviceStatusValue(givenScl, "IedName1", "LDSUIED").get().getValue()); + assertTrue(getLDeviceStatusValue(givenScl, "IedName2", "LDSUIED").isPresent()); + assertEquals("on", getLDeviceStatusValue(givenScl, "IedName2", "LDSUIED").get().getValue()); + assertFalse(getLDeviceStatusValue(givenScl, "IedName3", "LDSUIED").isPresent()); + + // When + // Then + assertThatCode(() -> SclService.updateLDeviceStatus(givenScl)) + .isInstanceOf(ScdException.class) + .hasMessage("DAI (Mod -stVal) cannot be updated"); + } + + + private Optional getLDeviceStatusValue(SCL scl, String iedName, String ldInst){ + SclRootAdapter sclRootAdapter = new SclRootAdapter(scl); + IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(iedName); + Optional lDeviceAdapter = iedAdapter.getLDeviceAdapterByLdInst(ldInst); + LN0Adapter ln0Adapter = lDeviceAdapter.get().getLN0Adapter(); + Optional doiAdapter = ln0Adapter.getDOIAdapters().stream() + .filter(doiAdapter1 -> doiAdapter1.getCurrentElem().getName().equals("Mod")) + .findFirst(); + if(doiAdapter.isEmpty()) return Optional.empty(); + return doiAdapter.get().getCurrentElem().getSDIOrDAI().stream() + .filter(tUnNaming -> tUnNaming.getClass().equals(TDAI.class)) + .map(TDAI.class::cast) + .filter(tdai -> tdai.getName().equals("stVal")) + .map(tdai -> tdai.getVal().get(0)) + .findFirst(); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SubstationServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SubstationServiceTest.java index 4f2a23a9e..16d46ef35 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SubstationServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SubstationServiceTest.java @@ -6,25 +6,15 @@ import org.junit.jupiter.api.Test; import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TCompasICDHeader; -import org.lfenergy.compas.scl2007b4.model.TLNode; import org.lfenergy.compas.scl2007b4.model.TSubstation; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; import static org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller.assertIsMarshallable; class SubstationServiceTest { - public static final String XPATH_FUNCTION_BAY_1_TDCL = "/SCL/Substation[@name=\"SITE\"]/VoltageLevel[@name=\"voltageLevelName\"]/Bay[@name=\"bay1\"]/Function[@name=\"bay1TDCL\"]"; - - public static final String XPATH_LNODE_RADR = XPATH_FUNCTION_BAY_1_TDCL + "/LNode[@iedName=\"None\" and not(@ldInst) and not(@Prefix) and @lnClass=\"RADR\" and not(@lnInst)]"; - @Test void addSubstation_when_SCD_has_no_substation_should_succeed() throws Exception { // Given @@ -87,109 +77,4 @@ void addSubstation_when_substations_names_differ_should_throw_exception() throws assertThrows(ScdException.class, () -> SubstationService.addSubstation(scd, ssd)); } - @Test - void updateLNodeIEDNames_when_LNode_has_single_private_should_set_IedName() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - // When - SubstationService.updateLNodeIEDNames(scl); - // Then - assertThat(scl.getSubstation()).hasSize(1); - assertThat(scl.getSubstation().get(0).getVoltageLevel()).hasSize(1); - assertThat(scl.getSubstation().get(0).getVoltageLevel().get(0).getBay()).hasSize(1); - assertThat(scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction()).hasSize(1); - List lNodes = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode(); - assertThat(lNodes).extracting(TLNode::getIedName).containsOnlyOnce("iedName1"); - assertIsMarshallable(scl); - } - - @Test - void updateLNodeIEDNames_when_LNode_has_multiples_private_should_set_IedName() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - // When - SubstationService.updateLNodeIEDNames(scl); - // Then - assertThat(scl.getSubstation()).hasSize(1); - assertThat(scl.getSubstation().get(0).getVoltageLevel()).hasSize(1); - assertThat(scl.getSubstation().get(0).getVoltageLevel().get(0).getBay()).hasSize(1); - assertThat(scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction()).hasSize(1); - List lNodes = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode(); - assertThat(lNodes) - .hasSize(4) - .extracting(TLNode::getIedName) - .containsExactly("iedName1", "iedName2", "iedName3", "iedName4"); - assertThat(lNodes.subList(1, 4)).allSatisfy( - tlNode -> assertThat(tlNode) - .hasFieldOrPropertyWithValue("lnClass", List.of("LPHD")) - .hasFieldOrPropertyWithValue("ldInst", "ldInst1") - .hasFieldOrPropertyWithValue("lnInst", "1") - .hasFieldOrPropertyWithValue("lnType", "lnType1")); - assertThat(lNodes.subList(1, 4)).allSatisfy(tlNode -> - assertThat(tlNode.getPrivate()) - .isNotEmpty() - .allSatisfy(tPrivate -> assertThat(PrivateService.getCompasICDHeader(tPrivate)).isPresent())); - assertIsMarshallable(scl); - } - - @Test - void updateLNodeIEDNames_when_private_is_missing_should_throw_exception() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - List lNodes = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode(); - lNodes.get(0).unsetPrivate(); - // When & Then - assertThatThrownBy(() -> SubstationService.updateLNodeIEDNames(scl)).isInstanceOf(ScdException.class) - .hasMessage(XPATH_FUNCTION_BAY_1_TDCL + " doesn't contain any Private/compas:ICDHeader"); - } - - @Test - void updateLNodeIEDNames_when_private_is_empty_should_throw_exception() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - List lNodes = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode(); - lNodes.get(0).getPrivate().clear(); - // When & Then - assertThatThrownBy(() -> SubstationService.updateLNodeIEDNames(scl)).isInstanceOf(ScdException.class) - .hasMessage(XPATH_FUNCTION_BAY_1_TDCL + " doesn't contain any" + - " Private/compas:ICDHeader");; - } - - @Test - void updateLNodeIEDNames_when_private_iedName_is_empty_should_throw_exception() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - TLNode lNode = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode().get(0); - PrivateService.getCompasPrivates(lNode, TCompasICDHeader.class).get(0).setIEDName(""); - // When & Then - assertThatThrownBy(() -> SubstationService.updateLNodeIEDNames(scl)).isInstanceOf(ScdException.class) - .hasMessage(XPATH_LNODE_RADR + "/Private/compas:ICDHeader/@IEDName is missing or is blank");; - } - - @Test - void updateLNodeIEDNames_when_private_iedName_is_missing_should_throw_exception() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - TLNode lNode = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode().get(0); - List compasPrivates = PrivateService.getCompasPrivates(lNode, TCompasICDHeader.class); - assertThat(compasPrivates).hasSize(1); - compasPrivates.get(0).setIEDName(null); - // When & Then - assertThatThrownBy(() -> SubstationService.updateLNodeIEDNames(scl)).isInstanceOf(ScdException.class) - .hasMessage(XPATH_LNODE_RADR + "/Private/compas:ICDHeader/@IEDName is missing or is blank");; - } - - @Test - void updateLNodeIEDNames_when_private_iedName_is_missing_and_has_multiple_privates_should_throw_exception() throws Exception { - // Given - SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/scd-with-substation-lnode.xml"); - TLNode lNode = scl.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0).getFunction().get(0).getLNode().get(1); - List compasPrivates = PrivateService.getCompasPrivates(lNode, TCompasICDHeader.class); - assertThat(compasPrivates).hasSize(3); - compasPrivates.get(0).setIEDName(null); - // When & Then - assertThatThrownBy(() -> SubstationService.updateLNodeIEDNames(scl)).isInstanceOf(ScdException.class) - .hasMessage(XPATH_FUNCTION_BAY_1_TDCL + "/LNode[@iedName=\"None\" and @ldInst=\"ldInst1\" and @Prefix=\"prefix1\" and @lnClass=\"LPHD\" and @lnInst=\"1\"]/Private/compas:ICDHeader/@IEDName is missing or is blank");; - } - } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapterTest.java index d5d70b0a1..3115a3f19 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/CommunicationAdapterTest.java @@ -9,6 +9,7 @@ import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class CommunicationAdapterTest { @@ -92,4 +93,15 @@ void addPrivate() { communicationAdapter.addPrivate(tPrivate); assertEquals(1, communicationAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath() { + // Given + CommunicationAdapter communicationAdapter = new CommunicationAdapter(null,new TCommunication()); + // When + String result = communicationAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("Communication"); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java index b71d8790d..c868c8a82 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java @@ -4,9 +4,10 @@ package org.lfenergy.compas.sct.commons.scl.com; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.lfenergy.compas.scl2007b4.model.SCL; import org.lfenergy.compas.scl2007b4.model.TConnectedAP; import org.lfenergy.compas.scl2007b4.model.TPrivate; @@ -17,8 +18,8 @@ import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.*; class ConnectedAPAdapterTest { @@ -90,4 +91,20 @@ void testCopyAddressAndPhysConnFromIcd_withEmptyIcd() { assertThat(connectedAPAdapter.getCurrentElem().getPhysConn()).isEmpty(); assertThat(connectedAPAdapter.getCurrentElem().getGSE()).isEmpty(); } + + @ParameterizedTest + @CsvSource(value = {"IED_NAME;AP_NAME;ConnectedAP[@apName=\"IED_NAME\" and @iedName=\"IED_NAME\"]", ";;ConnectedAP[not(@apName) and not(@iedName)]"} + , delimiter = ';') + void elementXPath(String iedName, String apName, String message) { + // Given + TConnectedAP tConnectedAP = new TConnectedAP(); + tConnectedAP.setApName(iedName); + tConnectedAP.setIedName(apName); + ConnectedAPAdapter connectedAPAdapter = new ConnectedAPAdapter(null, tConnectedAP); + // When + String elementXPath = connectedAPAdapter.elementXPath(); + // Then + assertThat(elementXPath).isEqualTo(message); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java index ffd2fe4ad..494968162 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java @@ -5,6 +5,8 @@ package org.lfenergy.compas.sct.commons.scl.com; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.lfenergy.compas.scl2007b4.model.TCommunication; import org.lfenergy.compas.scl2007b4.model.TConnectedAP; import org.lfenergy.compas.scl2007b4.model.TPrivate; @@ -12,6 +14,7 @@ import org.lfenergy.compas.sct.commons.dto.DTO; import org.lfenergy.compas.sct.commons.exception.ScdException; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class SubNetworkAdapterTest { @@ -74,4 +77,21 @@ void addPrivate() { subNetworkAdapter.addPrivate(tPrivate); assertEquals(1, subNetworkAdapter.getCurrentElem().getPrivate().size()); } + + @ParameterizedTest + @CsvSource(value = {"sName;sType;SubNetwork[@name=\"sName\"]", ";;SubNetwork[not(@name)]"} + , delimiter = ';') + void elementXPath(String sName, String sType, String message) { + // Given + TSubNetwork tSubNetwork = new TSubNetwork(); + tSubNetwork.setName(sName); + tSubNetwork.setType(sType); + SubNetworkAdapter subNetworkAdapter = new SubNetworkAdapter(null, tSubNetwork); + // When + String elementXPath = subNetworkAdapter.elementXPath(); + // Then + assertThat(elementXPath).isEqualTo(message); + + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/BDAAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/BDAAdapterTest.java index 3ea61b167..8533444d2 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/BDAAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/BDAAdapterTest.java @@ -11,6 +11,7 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class BDAAdapterTest extends AbstractDTTLevel { @@ -102,4 +103,16 @@ void addPrivate() { bdaAdapter.addPrivate(tPrivate); assertEquals(1, bdaAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath() { + // Given + init(); + DATypeAdapter.BDAAdapter bdaAdapter = new DATypeAdapter.BDAAdapter(sclElementAdapter,sclElement); + // When + String result = bdaAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("BDA[not(@name)]"); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapterTest.java index aebfad53c..8a87a8f0f 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DAAdapterTest.java @@ -12,6 +12,7 @@ import org.lfenergy.compas.sct.commons.dto.DaTypeName; import org.lfenergy.compas.sct.commons.exception.ScdException; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DAAdapterTest { @@ -77,4 +78,16 @@ void addPrivate() throws Exception { daAdapter.addPrivate(tPrivate); assertEquals(1, daAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath() throws Exception { + // Given + DataTypeTemplateAdapter dttAdapter = AbstractDTTLevel.initDttAdapterFromFile(AbstractDTTLevel.SCD_DTT); + DOTypeAdapter doTypeAdapter = dttAdapter.getDOTypeAdapters().get(0); + DAAdapter daAdapter = assertDoesNotThrow(() -> doTypeAdapter.getDAAdapterByName("dataNs").get()); + // When + String result = daAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DA[name=@name=\"dataNs\" and type=not(@type)]"); + } } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapterTest.java index 5f40825a7..dc2483b95 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DATypeAdapterTest.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DATypeAdapterTest extends AbstractDTTLevel { @@ -153,4 +154,15 @@ void addPrivate() throws Exception { daTypeAdapter.addPrivate(tPrivate); assertEquals(2, daTypeAdapter.getCurrentElem().getPrivate().size()); } + @Test + void elementXPath() throws Exception { + // Given + DataTypeTemplateAdapter dttAdapter = AbstractDTTLevel.initDttAdapterFromFile(AbstractDTTLevel.SCD_DTT); + DATypeAdapter daTypeAdapter = assertDoesNotThrow(() ->dttAdapter.getDATypeAdapterById("DA1").get()); + // When + String result = daTypeAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DAType[@id=\"DA1\"]"); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapterTest.java new file mode 100644 index 000000000..95fa8014a --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOAdapterTest.java @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.scl.dtt; + +import org.junit.jupiter.api.Test; +import org.lfenergy.compas.scl2007b4.model.TDO; +import org.lfenergy.compas.scl2007b4.model.TLNodeType; +import org.mockito.Mockito; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class DOAdapterTest { + + @Test + void elementXPath() { + // Given + LNodeTypeAdapter lNodeAdapter = Mockito.mock(LNodeTypeAdapter.class); + TLNodeType tlNodeType = Mockito.mock(TLNodeType.class); + Mockito.when(lNodeAdapter.getParentAdapter()).thenReturn(null); + Mockito.when(lNodeAdapter.getCurrentElem()).thenReturn(tlNodeType); + + TDO tdo = new TDO(); + tdo.setName("doName"); + Mockito.when(tlNodeType.getDO()).thenReturn(List.of(tdo)); + DOAdapter doAdapter = new DOAdapter(lNodeAdapter, tdo); + // When + String result = doAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DO[@name=\"doName\" and not(@type)]"); + } +} \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapterTest.java index 843bd9684..589c5ef4f 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DOTypeAdapterTest.java @@ -14,6 +14,7 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DOTypeAdapterTest extends AbstractDTTLevel { @@ -171,4 +172,17 @@ void addPrivate() throws Exception { doTypeAdapter.addPrivate(tPrivate); assertEquals(1, doTypeAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath() throws Exception { + // Given + DataTypeTemplateAdapter dttAdapter = AbstractDTTLevel.initDttAdapterFromFile( + AbstractDTTLevel.SCD_DTT_DIFF_CONTENT_SAME_ID + ); + DOTypeAdapter doTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getDOTypeAdapterById("DO1").get()); + // When + String result = doTypeAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DOType[@id=\"DO1\"]"); + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapterTest.java index 5fca85bf4..cc3aed22b 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateAdapterTest.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DataTypeTemplateAdapterTest { @@ -369,4 +370,15 @@ void addPrivate() throws Exception { tPrivate.setSource("Private Source"); assertThrows(UnsupportedOperationException.class, () -> dttAdapter.addPrivate(tPrivate)); } + + @Test + void elementXPath() throws Exception { + // Given + DataTypeTemplateAdapter dttAdapter = AbstractDTTLevel.initDttAdapterFromFile(AbstractDTTLevel.SCD_DTT_DIFF_CONTENT_SAME_ID); + // When + String result = dttAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DataTypeTemplates"); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapterTest.java index 1df3fa817..1f0efcb27 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/EnumTypeAdapterTest.java @@ -12,6 +12,7 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class EnumTypeAdapterTest extends AbstractDTTLevel { @@ -73,4 +74,18 @@ void addPrivate() { enumTypeAdapter.addPrivate(tPrivate); assertEquals(1, enumTypeAdapter.getCurrentElem().getPrivate().size()); } + + + @Test + void elementXPath() { + // Given + init(); + EnumTypeAdapter enumTypeAdapter = assertDoesNotThrow( + () -> new EnumTypeAdapter(sclElementAdapter,sclElement) + ); + // When + String result = enumTypeAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("EnumType[@id=\"ID\"]"); + } } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java index 3e61b66db..09a305bed 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class LNodeTypeAdapterTest extends AbstractDTTLevel { @@ -196,4 +197,16 @@ void addPrivate() throws Exception { lNodeTypeAdapter.addPrivate(tPrivate); assertEquals(2, lNodeTypeAdapter.getCurrentElem().getPrivate().size()); } + + + @Test + void elementXPath() throws Exception { + // Given + DataTypeTemplateAdapter dttAdapter = AbstractDTTLevel.initDttAdapterFromFile(AbstractDTTLevel.SCD_DTT); + LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() ->dttAdapter.getLNodeTypeAdapterById("LN1").get()); + // When + String result = lNodeTypeAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("LNodeType[@id=\"LN1\" and @lnClass=\"PIOC\"]"); + } } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapterTest.java index 7c7597891..8079a0f28 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/header/HeaderAdapterTest.java @@ -5,12 +5,15 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.lfenergy.compas.scl2007b4.model.THeader; import org.lfenergy.compas.scl2007b4.model.THitem; import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class HeaderAdapterTest { @@ -58,7 +61,7 @@ void testAddHistoryItem() throws ScdException { } @Test - void addPrivate() throws Exception { + void addPrivate() { SclRootAdapter sclRootAdapter = new SclRootAdapter( "hID","hVersion","hRevision" ); @@ -67,6 +70,22 @@ void addPrivate() throws Exception { tPrivate.setType("Private Type"); tPrivate.setSource("Private Source"); assertThrows(UnsupportedOperationException.class, () -> hAdapter.addPrivate(tPrivate)); + } + @ParameterizedTest + @CsvSource(value = {"hID;hVersion;hRevision;Header[@id=\"hID\" and @version=\"hVersion\" and @revision=\"hRevision\"]", ";;;Header[not(@id) and not(@version) and not(@revision)]"} + , delimiter = ';') + void elementXPath(String hID, String hVersion, String hRevision,String message) { + // Given + THeader tHeader = new THeader(); + tHeader.setId(hID); + tHeader.setVersion(hVersion); + tHeader.setRevision(hRevision); + HeaderAdapter headerAdapter = new HeaderAdapter(null, tHeader); + // When + String elementXPathResult = headerAdapter.elementXPath(); + // Then + assertThat(elementXPathResult).isEqualTo(message); } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DAITrackerTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DAITrackerTest.java index c3de7b4a6..3f6fa1ad2 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DAITrackerTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DAITrackerTest.java @@ -16,6 +16,7 @@ import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DAITrackerTest { @@ -176,4 +177,5 @@ void testGetDaiNumericValue() { daTypeName.setBType(TPredefinedBasicTypeEnum.FLOAT_32); assertDoesNotThrow(()->daiTracker.getDaiNumericValue(daTypeName,13.0)); } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapterTest.java index d848c7199..4c6d9a30f 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapterTest.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class DOIAdapterTest { @@ -156,4 +157,30 @@ void addPrivate() { daiAdapter.addPrivate(tPrivate); assertEquals(1, daiAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath_doi() { + // Given + TDOI tdoi = new TDOI(); + tdoi.setName("doName"); + DOIAdapter doiAdapter = new DOIAdapter(null,new TDOI()); + DOIAdapter namedDoiAdapter = new DOIAdapter(null,tdoi); + // When + String elementXPathResult = doiAdapter.elementXPath(); + String namedElementXPathResult = namedDoiAdapter.elementXPath(); + // Then + assertThat(elementXPathResult).isEqualTo("DOI[not(@name)]"); + assertThat(namedElementXPathResult).isEqualTo("DOI[@name=\"doName\"]"); + } + + @Test + void elementXPath_dai() { + // Given + DOIAdapter.DAIAdapter daiAdapter = initInnerDAIAdapter("Do","da"); + // When + String result = daiAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DAI[@name=\"da\"]"); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java index b75e19ff6..cd9277ef4 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java @@ -13,6 +13,7 @@ import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.ObjectReference; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.scl.com.SubNetworkAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import org.mockito.Mockito; @@ -20,6 +21,7 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller.assertIsMarshallable; @@ -244,4 +246,21 @@ void addPrivate() { iAdapter.addPrivate(tPrivate); assertEquals(1, iAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath() { + // Given + SclRootAdapter sclRootAdapter = Mockito.mock(SclRootAdapter.class); + SCL scl = Mockito.mock(SCL.class); + Mockito.when(sclRootAdapter.getCurrentElem()).thenReturn(scl); + TIED tied = new TIED(); + tied.setName("iedName"); + Mockito.when(scl.getIED()).thenReturn(List.of(tied)); + IEDAdapter iAdapter = new IEDAdapter(sclRootAdapter, tied); + // When + String result = iAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("IED[@name=\"iedName\"]"); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java index b311d60f0..4c5f893cb 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java @@ -6,7 +6,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.lfenergy.compas.scl2007b4.model.SCL; +import org.lfenergy.compas.scl2007b4.model.TLDevice; import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.sct.commons.dto.DTO; @@ -19,6 +22,7 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class LDeviceAdapterTest { @@ -100,4 +104,19 @@ void addPrivate() { lDeviceAdapter.addPrivate(tPrivate); assertEquals(1, lDeviceAdapter.getCurrentElem().getPrivate().size()); } + + @ParameterizedTest + @CsvSource(value = {"ldInst;LDevice[@inst=\"ldInst\"]", ";LDevice[not(@inst)]"} + , delimiter = ';') + void elementXPath(String ldInst, String message) { + // Given + TLDevice tlDevice = new TLDevice(); + tlDevice.setInst(ldInst); + LDeviceAdapter lDeviceAdapter = new LDeviceAdapter(null, tlDevice); + // When + String elementXPathResult = lDeviceAdapter.elementXPath(); + // Then + assertThat(elementXPathResult).isEqualTo(message); + + } } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java index 5b05181e3..b0387c69c 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java @@ -14,7 +14,9 @@ import org.mockito.Mockito; import java.util.List; +import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class LN0AdapterTest { @@ -325,4 +327,72 @@ void addPrivate() { lnAdapter.addPrivate(tPrivate); assertEquals(1, lnAdapter.getCurrentElem().getPrivate().size()); } + + + @Test + void testGetDAI() throws Exception { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ied-dtt-com-import-stds/std.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + IEDAdapter iAdapter = assertDoesNotThrow(() -> sclRootAdapter.getIEDAdapterByName("IED4d4fe1a8cda64cf88a5ee4176a1a0eef")); + LDeviceAdapter lDeviceAdapter = assertDoesNotThrow(()-> iAdapter.getLDeviceAdapterByLdInst("LDSUIED").get()); + LN0Adapter ln0Adapter = lDeviceAdapter.getLN0Adapter(); + ResumedDataTemplate filter = new ResumedDataTemplate(); + filter.setLnClass(ln0Adapter.getLNClass()); + filter.setLnInst(ln0Adapter.getLNInst()); + filter.setPrefix(ln0Adapter.getPrefix()); + filter.setLnType(ln0Adapter.getLnType()); + filter.setDoName(new DoTypeName("Beh")); + DaTypeName daTypeName = new DaTypeName(); + daTypeName.setName("stVal"); + daTypeName.setBType(TPredefinedBasicTypeEnum.ENUM); + daTypeName.setFc(TFCEnum.ST); + filter.setDaName(daTypeName); + //When + var rDtts = ln0Adapter.getDAI(filter,false); + //Then + assertFalse(rDtts.isEmpty()); + assertEquals(1,rDtts.size()); + assertNotNull(rDtts.get(0).getDaName().getType()); + assertEquals("BehaviourModeKind", rDtts.get(0).getDaName().getType()); + } + + @Test + void getEnumValue() throws Exception { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ied-dtt-com-import-stds/std.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + IEDAdapter iAdapter = assertDoesNotThrow(() -> sclRootAdapter.getIEDAdapterByName("IED4d4fe1a8cda64cf88a5ee4176a1a0eef")); + LDeviceAdapter lDeviceAdapter = assertDoesNotThrow(()-> iAdapter.getLDeviceAdapterByLdInst("LDSUIED").get()); + LN0Adapter ln0Adapter = lDeviceAdapter.getLN0Adapter(); + ResumedDataTemplate filter = new ResumedDataTemplate(); + filter.setLnClass(ln0Adapter.getLNClass()); + filter.setLnInst(ln0Adapter.getLNInst()); + filter.setPrefix(ln0Adapter.getPrefix()); + filter.setLnType(ln0Adapter.getLnType()); + filter.setDoName(new DoTypeName("Beh")); + DaTypeName daTypeName = new DaTypeName(); + daTypeName.setName("stVal"); + daTypeName.setBType(TPredefinedBasicTypeEnum.ENUM); + daTypeName.setFc(TFCEnum.ST); + daTypeName.setType("BehaviourModeKind"); + filter.setDaName(daTypeName); + //When + Set enumValues = ln0Adapter.getEnumValues(filter.getType()); + //Then + assertEquals(5, enumValues.size()); + assertThat(enumValues).containsExactlyInAnyOrder("blocked", "test", "test/blocked", "off", "on"); + } + + @Test + void elementXPath() { + // Given + LN0 tln = new LN0(); + tln.getLnClass().add(TLLN0Enum.LLN_0.value()); + LN0Adapter lnAdapter = new LN0Adapter(null,tln); + // When + String result = lnAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("LN[lnClass=\"LLN0\" and not(@inst) and not(@lnType)]"); + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java index 544550871..e5f38aaee 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java @@ -16,6 +16,7 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class LNAdapterTest { @@ -318,4 +319,15 @@ void addPrivate() throws Exception { assertEquals(1, lnAdapter.getCurrentElem().getPrivate().size()); } + @Test + void elementXPath() { + // Given + TLN tln = new TLN(); + LNAdapter lnAdapter = initLNAdapter(tln); + // When + String result = lnAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("LN[@lnClass=\"LN_CLASS_H\" and @inst=\"1\" and @lnType=\"LN_TYPE\"]"); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapterTest.java index f68ea2a32..dd436deaa 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/RootSDIAdapterTest.java @@ -11,6 +11,7 @@ import org.lfenergy.compas.scl2007b4.model.TSDI; import org.lfenergy.compas.sct.commons.exception.ScdException; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class RootSDIAdapterTest { @@ -69,4 +70,34 @@ void addPrivate() { rootSDIAdapter.addPrivate(tPrivate); assertEquals(1, rootSDIAdapter.getCurrentElem().getPrivate().size()); } + + @Test + void elementXPath_sdi() { + // Given + TSDI tsdi = new TSDI(); + tsdi.setName("sdo1"); + RootSDIAdapter rootSDIAdapter = new RootSDIAdapter(null,tsdi); + // When + String result = rootSDIAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("SDI[@name=\"sdo1\"]"); + } + + @Test + void elementXPath_dai() { + // Given + TSDI tsdi = new TSDI(); + tsdi.setName("sdo1"); + RootSDIAdapter rootSDIAdapter = new RootSDIAdapter(null,tsdi); + + TDAI tdai = new TDAI(); + tdai.setName("angRef"); + tsdi.getSDIOrDAI().add(tdai); + RootSDIAdapter.DAIAdapter daiAdapter = assertDoesNotThrow(() -> new RootSDIAdapter.DAIAdapter(rootSDIAdapter,tdai)); + // When + String result = daiAdapter.elementXPath(); + // Then + assertThat(result).isEqualTo("DAI[@name=\"angRef\"]"); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapterTest.java index 809c36336..adc454ae2 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/SDIAdapterTest.java @@ -5,11 +5,14 @@ package org.lfenergy.compas.sct.commons.scl.ied; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.lfenergy.compas.scl2007b4.model.TDAI; import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.scl2007b4.model.TSDI; import org.lfenergy.compas.sct.commons.exception.ScdException; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class SDIAdapterTest { @@ -72,4 +75,18 @@ void addPrivate() { assertEquals(1, sdiAdapter.getCurrentElem().getPrivate().size()); } + @ParameterizedTest + @CsvSource(value = {"sdo1;SDI[@name=\"sdo1\"]", ";SDI[not(@name)]"} + , delimiter = ';') + void elementXPath(String sdo, String message) { + // Given + TSDI tsdi = new TSDI(); + tsdi.setName(sdo); + SDIAdapter sdiAdapter = new SDIAdapter(null,tsdi); + // When + String sdiAdapterResult = sdiAdapter.elementXPath(); + // Then + assertThat(sdiAdapterResult).isEqualTo(message); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapterTest.java index c213fffa3..95fb84948 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/FunctionAdapterTest.java @@ -7,14 +7,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.lfenergy.compas.scl2007b4.model.TBay; -import org.lfenergy.compas.scl2007b4.model.TCompasICDHeader; import org.lfenergy.compas.scl2007b4.model.TFunction; -import org.lfenergy.compas.scl2007b4.model.TLNode; -import org.lfenergy.compas.sct.commons.exception.ScdException; -import org.lfenergy.compas.sct.commons.scl.PrivateService; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; class FunctionAdapterTest { @@ -58,52 +53,4 @@ void elementXPath() { assertThat(result).isEqualTo("Function[@name=\"functionName\"]"); } - @Test - void updateLNodeIedNames_when_one_private_should_succeed() { - // Given - TLNode tlNode = new TLNode(); - TCompasICDHeader tCompasICDHeader = new TCompasICDHeader(); - tCompasICDHeader.setIEDName("iedName1"); - tlNode.getPrivate().add(PrivateService.createPrivate(tCompasICDHeader)); - functionAdapter.getCurrentElem().getLNode().add(tlNode); - // When - functionAdapter.updateLNodeIedNames(); - // Then - assertThat(functionAdapter.getCurrentElem().getLNode()) - .hasSize(1) - .map(TLNode::getIedName).containsExactly("iedName1"); - } - - @Test - void updateLNodeIedNames_when_multiples_privates_should_succeed() { - // Given - TLNode tlNode = new TLNode(); - TCompasICDHeader tCompasICDHeader1 = new TCompasICDHeader(); - tCompasICDHeader1.setIEDName("iedName1"); - TCompasICDHeader tCompasICDHeader2 = new TCompasICDHeader(); - tCompasICDHeader2.setIEDName("iedName2"); - TCompasICDHeader tCompasICDHeader3 = new TCompasICDHeader(); - tCompasICDHeader3.setIEDName("iedName3"); - tlNode.getPrivate().add(PrivateService.createPrivate(tCompasICDHeader1)); - tlNode.getPrivate().add(PrivateService.createPrivate(tCompasICDHeader2)); - tlNode.getPrivate().add(PrivateService.createPrivate(tCompasICDHeader3)); - functionAdapter.getCurrentElem().getLNode().add(tlNode); - // When - functionAdapter.updateLNodeIedNames(); - // Then - assertThat(functionAdapter.getCurrentElem().getLNode()) - .hasSize(3) - .map(TLNode::getIedName) - .containsExactly("iedName1", "iedName2", "iedName3"); - } - - @Test - void updateLNodeIedNames_when_no_private_should_throw_exception() { - // Given - TLNode tlNode = new TLNode(); - functionAdapter.getCurrentElem().getLNode().add(tlNode); - // When & Then - assertThatThrownBy(() -> functionAdapter.updateLNodeIedNames()).isInstanceOf(ScdException.class); - } - } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapterTest.java index a827f4dfc..0ea8e5143 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/sstation/SubstationAdapterTest.java @@ -4,14 +4,20 @@ package org.lfenergy.compas.sct.commons.scl.sstation; +import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.lfenergy.compas.scl2007b4.model.SCL; import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.scl2007b4.model.TSubstation; import org.lfenergy.compas.scl2007b4.model.TVoltageLevel; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class SubstationAdapterTest { @@ -72,4 +78,19 @@ void addPrivate() { ssAdapter.addPrivate(tPrivate); assertEquals(1, ssAdapter.getCurrentElem().getPrivate().size()); } + + + @Test + void getIedAndLDeviceNamesForLN0FromLNode_shouldReturnListOf1Pair_WhenLNodeContainsLN0() throws Exception { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scd-refresh-lnode/issue68_Test_Template.scd"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scl); + SubstationAdapter substationAdapter = sclRootAdapter.getSubstationAdapter(); + // When + List> iedNameLdInstList = substationAdapter.getIedAndLDeviceNamesForLN0FromLNode(); + // Then + assertEquals(1, iedNameLdInstList.size()); + assertThat(iedNameLdInstList).containsExactly(Pair.of("IedName1", "LDSUIED")); + } + } \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd new file mode 100644 index 000000000..b6f2124f7 --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd @@ -0,0 +1,224 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + on + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + blocked + test + test/blocked + + + off + blocked + test + test/blocked + + + blocked + test + test/blocked + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd new file mode 100644 index 000000000..f94cd1956 --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd @@ -0,0 +1,222 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + on + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + blocked + test + test/blocked + + + on + off + blocked + test + test/blocked + + + on + off + blocked + test + test/blocked + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd new file mode 100644 index 000000000..1de3c1de7 --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd @@ -0,0 +1,223 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + on + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + blocked + test + test/blocked + + + on + off + blocked + test + test/blocked + + + on + off + blocked + test + test/blocked + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd new file mode 100644 index 000000000..068f2cf7f --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd @@ -0,0 +1,114 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + + + + + + + + + + + + + blocked + test + test/blocked + off + on + + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd new file mode 100644 index 000000000..48877c0b5 --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd @@ -0,0 +1,112 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + + + + + + + + + + + + + + blocked + test + test/blocked + off + on + + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd new file mode 100644 index 000000000..8ab10e71c --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd @@ -0,0 +1,115 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + + + + + + + + + + + + + + blocked + test + test/blocked + off + on + + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd new file mode 100644 index 000000000..f6d2f2979 --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd @@ -0,0 +1,114 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + + + + + + + + + + + + + blocked + test + test/blocked + off + on + + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd new file mode 100644 index 000000000..0823597c2 --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd @@ -0,0 +1,219 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + on + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + blocked + test + test/blocked + + + off + blocked + test + test/blocked + + + blocked + test + test/blocked + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd new file mode 100644 index 000000000..6f2cb7e9d --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd @@ -0,0 +1,219 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + on + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + blocked + test + test/blocked + + + off + blocked + test + test/blocked + + + blocked + test + test/blocked + + +
      \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd new file mode 100644 index 000000000..140e2390d --- /dev/null +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd @@ -0,0 +1,223 @@ + + + + + + + SCD + +
      + + + +
      + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + +
      +

      00000001

      +
      +
      + +
      +

      00000001

      +
      +
      +
      + + +
      +

      Adresse IP du serveur Syslog

      +
      +
      + +
      +

      Adresse IP du serveur Syslog

      +
      +
      +
      +
      + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + off + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + on + + + + + + + + + + + SAMU + SAMU + + + + + + + + + + + + + + + + + 01.00.000 + + + 01.00.000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + blocked + test + test/blocked + + + on + off + blocked + test + test/blocked + + + on + off + blocked + test + test/blocked + + +
      \ No newline at end of file diff --git a/sct-coverage/pom.xml b/sct-coverage/pom.xml index 852a7029d..66ac9ef72 100644 --- a/sct-coverage/pom.xml +++ b/sct-coverage/pom.xml @@ -20,6 +20,7 @@ true + true **/scl2007b4/**/* diff --git a/sct-data/pom.xml b/sct-data/pom.xml index c585ba219..248813512 100644 --- a/sct-data/pom.xml +++ b/sct-data/pom.xml @@ -20,6 +20,7 @@ ${basedir}/${aggregate.report.dir} + true

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 + # COMPAS SCT (Substation Configuration Tool) diff --git a/doc/drawio/SCD Process ver06.drawio b/docs/drawio/SCD Process ver06.drawio similarity index 100% rename from doc/drawio/SCD Process ver06.drawio rename to docs/drawio/SCD Process ver06.drawio diff --git a/docs/drawio/SCD_file_generation.drawio b/docs/drawio/SCD_file_generation.drawio new file mode 100644 index 000000000..1f75c0e23 --- /dev/null +++ b/docs/drawio/SCD_file_generation.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/doc/drawio/compas-sct.drawio b/docs/drawio/compas-sct.drawio similarity index 100% rename from doc/drawio/compas-sct.drawio rename to docs/drawio/compas-sct.drawio diff --git a/docs/examples/example-app/pom.xml b/docs/examples/example-app/pom.xml new file mode 100644 index 000000000..52bf33115 --- /dev/null +++ b/docs/examples/example-app/pom.xml @@ -0,0 +1,108 @@ + + + + + + 4.0.0 + + org.lfenergy.compas + example-app + 1.0-SNAPSHOT + + example-app + + + UTF-8 + 11 + 11 + 11 + + + + + github-packages-compas + GitHub Packages + https://maven.pkg.github.com/com-pas/compas-sct + + + + + + github-packages-compas + Github Packages CoMPAS + https://maven.pkg.github.com/com-pas/* + + + + + + + org.lfenergy.compas + sct-commons + 0.1.0 + + + + org.lfenergy.compas + sct-app + 0.1.0 + + + org.junit.jupiter + junit-jupiter-api + 5.8.1 + test + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/docs/examples/example-app/src/main/java/org/lfenergy/compas/sct/examples/SctAppExample.java b/docs/examples/example-app/src/main/java/org/lfenergy/compas/sct/examples/SctAppExample.java new file mode 100644 index 000000000..b0c2bbfcf --- /dev/null +++ b/docs/examples/example-app/src/main/java/org/lfenergy/compas/sct/examples/SctAppExample.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.examples; + +import org.lfenergy.compas.scl2007b4.model.SCL; +import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.scl.SclService; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import java.util.Optional; +import java.util.UUID; + +public class SctAppExample { + + public static void main( String[] args ) throws JAXBException { + initSclWithSclService(Optional.empty(), "1.0", "1.0"); + } + + public static SclRootAdapter initSclWithSclService(Optional hId, String hVersion, String hRevision) throws JAXBException { + SclRootAdapter scl = SclService.initScl(hId, hVersion, hRevision); + marshaller.marshal(scl.getCurrentElem(), System.out); + return scl; + } + + private static Marshaller marshaller; + static{ + try { + JAXBContext context = JAXBContext.newInstance(SCL.class); + marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + } catch (JAXBException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/docs/examples/example-app/src/test/java/org/lfenergy/compas/sct/examples/SctAppExampleTest.java b/docs/examples/example-app/src/test/java/org/lfenergy/compas/sct/examples/SctAppExampleTest.java new file mode 100644 index 000000000..d2fbca3f1 --- /dev/null +++ b/docs/examples/example-app/src/test/java/org/lfenergy/compas/sct/examples/SctAppExampleTest.java @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.examples; + +import org.junit.jupiter.api.Test; +import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; + +import javax.xml.bind.JAXBException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + + +public class SctAppExampleTest { + + @Test + public void initSclWithSclServiceTest() throws JAXBException { + // Given : Header attributes + Optional headerId = Optional.of(UUID.randomUUID()); + String headerVersion = SclRootAdapter.VERSION; + String headerRevision = SclRootAdapter.REVISION; + // When: Sct Service + SclRootAdapter scl = SctAppExample + .initSclWithSclService(headerId, headerVersion, headerRevision); + // Then + assertNotNull(scl.getCurrentElem().getHeader()); + assertEquals(headerId.get().toString(), scl.getCurrentElem().getHeader().getId()); + assertEquals(headerVersion, scl.getCurrentElem().getHeader().getVersion()); + assertEquals(headerRevision, scl.getCurrentElem().getHeader().getRevision()); + THeader.History history = scl.getCurrentElem().getHeader().getHistory(); + List substations = scl.getCurrentElem().getSubstation(); + TCommunication communication = scl.getCurrentElem().getCommunication(); + List iedList = scl.getCurrentElem().getIED(); + TDataTypeTemplates dataTypeTemplates = scl.getCurrentElem().getDataTypeTemplates(); + assertNull(history); + assertEquals(0, substations.size()); + assertNull(communication); + assertEquals(0, iedList.size()); + assertNull(dataTypeTemplates); + } + +} diff --git a/docs/github_pages/_config.yml b/docs/github_pages/_config.yml new file mode 100644 index 000000000..67534a0d0 --- /dev/null +++ b/docs/github_pages/_config.yml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2021 Alliander N.V. +# +# SPDX-License-Identifier: CC-BY-4.0 + +# Setup +title: CoMPAS Contributing +tagline: 'Contributing to the CoMPAS project' + +paginate: 5 + +# Custom vars +version: 0.0.1 + +github: + repo: https://github.com/com-pas/contributing + +github_username: com-pas +git_repo: contributing +git_branch: master + +baseurl: /contributing + +defaults: + - + scope: + path: "HOME.md" + values: + permalink: "/" \ No newline at end of file diff --git a/doc/images/PackageDiagram-CompasSCT.png b/docs/images/PackageDiagram-CompasSCT.png similarity index 100% rename from doc/images/PackageDiagram-CompasSCT.png rename to docs/images/PackageDiagram-CompasSCT.png diff --git a/docs/images/SequenceDiagram-CompasSCT.png b/docs/images/SequenceDiagram-CompasSCT.png new file mode 100644 index 0000000000000000000000000000000000000000..afb7262ba7143378494e4419657441417a9a5f53 GIT binary patch literal 91278 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 literal 0 HcmV?d00001 diff --git a/docs/public/LFEnergy-slack.svg b/docs/public/LFEnergy-slack.svg new file mode 100644 index 000000000..9de473bb0 --- /dev/null +++ b/docs/public/LFEnergy-slack.svg @@ -0,0 +1 @@ +LF Energy: slackLF Energyslack \ No newline at end of file diff --git a/docs/public/LFEnergy-slack.svg.license b/docs/public/LFEnergy-slack.svg.license new file mode 100644 index 000000000..433ef15a6 --- /dev/null +++ b/docs/public/LFEnergy-slack.svg.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2021 Alliander N.V. + +SPDX-License-Identifier: CC-BY-4.0 \ No newline at end of file diff --git a/docs/public/apple-touch-icon-144-precomposed.png b/docs/public/apple-touch-icon-144-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..345010c3afe47bfa228477f47970650152d87140 GIT binary patch literal 588 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q2}owBl)eX2k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7~griIEGZ*dUMAyk1>#;;bB%xLtc8a!0cTNo<}*Q9!bS& zbQ*iS@NBu^apN`X4U@Nmx8^OpHf4)pzR=t6`o}9OjqlGEm!90o-KrhycRi2MfKk{W zfk`dl0E^Fo2F@7`KrSP*S^^WB&jA*m84a8g5CzK;Qki>iXKrnKTsSK{sONFAeCVmA zM~kLDOJUo4FU9P0i{=gCZ8!7ER_DA=SR1!Lf4gwx{{2?}|33cwK|4^ywq*Bn|1`U~ zcI&UVPnUmjEbY=C_OzhiPde|uf3CXKzwP$loX$E`E}XVwx<@Uce&ZWezh9XqR`nBN z^w@Ql3RQ|W>ITl+pnP`uWtVM7cBctPNZEe-{hU8YbGn}3nQ0uT9>VQoxGSupR&3`8 zIkogvZYju9{wW{)R+m;37|oWo{;xJa-%P5SeX)jDiqZYJD`~qcD{Hw6lZ^`(3MAdR zYb)D#JX3FPlFa-F+wP}YXHW7}?G@%eG3`e`*VmSl{e0L$7{ftam!>l?Gc-STHCP|D SF%y_P7(8A5T-G@yGywpdT->Vw literal 0 HcmV?d00001 diff --git a/docs/public/apple-touch-icon-144-precomposed.png.license b/docs/public/apple-touch-icon-144-precomposed.png.license new file mode 100644 index 000000000..433ef15a6 --- /dev/null +++ b/docs/public/apple-touch-icon-144-precomposed.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2021 Alliander N.V. + +SPDX-License-Identifier: CC-BY-4.0 \ No newline at end of file diff --git a/docs/public/compas-horizontal-color.svg b/docs/public/compas-horizontal-color.svg new file mode 100644 index 000000000..1ab8d0b27 --- /dev/null +++ b/docs/public/compas-horizontal-color.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/compas-horizontal-color.svg.license b/docs/public/compas-horizontal-color.svg.license new file mode 100644 index 000000000..433ef15a6 --- /dev/null +++ b/docs/public/compas-horizontal-color.svg.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2021 Alliander N.V. + +SPDX-License-Identifier: CC-BY-4.0 \ No newline at end of file diff --git a/docs/public/css/gitbutton.css b/docs/public/css/gitbutton.css new file mode 100644 index 000000000..5fb11fd8b --- /dev/null +++ b/docs/public/css/gitbutton.css @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2021 Alliander N.V. + * + * SPDX-License-Identifier: CC-BY-4.0 + */ + +.icon.medium > svg { + display: inline-block; + width: 24px; + height: 24px; + vertical-align: middle; + } + + .icon.medium > svg path { + fill: #828282 + } + + .gitbutton { + position: relative; + overflow: visible; + display: inline-block; + padding: 0.5em 1em; + border: 1px solid #d4d4d4; + margin: 0; + text-decoration: none; + text-align: center; + text-shadow: 1px 1px 0 #fff; + /* + * I removed the original CSS defining the font type, as it did not play nicely with + * the CSS defining my link size. + */ + /*font:11px/normal sans-serif; */ + color: #333; + white-space: nowrap; + cursor: pointer; + outline: none; + background-color: #ececec; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f4f4f4), to(#ececec)); + background-image: -moz-linear-gradient(#f4f4f4, #ececec); + background-image: -ms-linear-gradient(#f4f4f4, #ececec); + background-image: -o-linear-gradient(#f4f4f4, #ececec); + background-image: linear-gradient(#f4f4f4, #ececec); + -moz-background-clip: padding; /* for Firefox 3.6 */ + background-clip: padding-box; + border-radius: 0.2em; + /* IE hacks */ + zoom: 1; + *display: inline; + } + + .gitbutton:hover, + .gitbutton:focus, + .gitbutton:active, + .gitbutton.active { + border-color: #3072b3; + border-bottom-color: #2a65a0; + text-decoration: none; + text-shadow: -1px -1px 0 rgba(0,0,0,0.3); + color: #fff; + background-color: #3c8dde; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#599bdc), to(#3072b3)); + background-image: -moz-linear-gradient(#599bdc, #3072b3); + background-image: -o-linear-gradient(#599bdc, #3072b3); + background-image: linear-gradient(#599bdc, #3072b3); + } + .gitbutton:active, + .gitbutton.active { + border-color: #2a65a0; + border-bottom-color: #3884cd; + background-color: #3072b3; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#3072b3), to(#599bdc)); + background-image: -moz-linear-gradient(#3072b3, #599bdc); + background-image: -ms-linear-gradient(#3072b3, #599bdc); + background-image: -o-linear-gradient(#3072b3, #599bdc); + background-image: linear-gradient(#3072b3, #599bdc); + } + /* overrides extra padding on gitbutton elements in Firefox */ + .gitbutton::-moz-focus-inner { + padding: 0; + border: 0; + } + + .gitbutton.pill { + border-radius: 50em; + } \ No newline at end of file diff --git a/docs/public/css/hyde.css b/docs/public/css/hyde.css new file mode 100644 index 000000000..e1f18272e --- /dev/null +++ b/docs/public/css/hyde.css @@ -0,0 +1,255 @@ +/* + * SPDX-FileCopyrightText: 2021 Alliander N.V. + * + * SPDX-License-Identifier: CC-BY-4.0 + */ + +/* + * __ __ + * /\ \ /\ \ + * \ \ \___ __ __ \_\ \ __ + * \ \ _ `\/\ \/\ \ /'_` \ /'__`\ + * \ \ \ \ \ \ \_\ \/\ \_\ \/\ __/ + * \ \_\ \_\/`____ \ \___,_\ \____\ + * \/_/\/_/`/___/> \/__,_ /\/____/ + * /\___/ + * \/__/ + * + * Designed, built, and released under MIT license by @mdo. Learn more at + * https://github.com/poole/hyde. + */ + + +/* + * Contents + * + * Global resets + * Sidebar + * Container + * Reverse layout + * Themes + */ + + +/* + * Global resets + * + * Update the foundational and global aspects of the page. + */ + +html { + font-family: "PT Sans", Helvetica, Arial, sans-serif; +} +@media (min-width: 48em) { + html { + font-size: 16px; + } +} +@media (min-width: 58em) { + html { + font-size: 20px; + } +} + +/* + * Sidebar + * + * Flexible banner for housing site name, intro, and "footer" content. Starts + * out above content in mobile and later moves to the side with wider viewports. + */ + +.sidebar { + text-align: center; + padding: 2rem 1rem; + color: rgba(255,255,255,.5); + background-color: #202020; +} +@media (min-width: 48em) { + .sidebar { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 21rem; + text-align: left; + } +} + +/* Sidebar links */ +.sidebar a { + color: #fff; +} + +/* About section */ +.sidebar-about h1 { + color: #fff; + margin-top: 0; + font-family: "Abril Fatface", serif; + font-size: 3.25rem; +} + +/* Sidebar nav */ +.sidebar-nav { + margin-bottom: 1rem; +} +.sidebar-nav-item { + display: block; + line-height: 1.75; +} +a.sidebar-nav-item:hover, +a.sidebar-nav-item:focus { + text-decoration: underline; +} +.sidebar-nav-item.active { + font-weight: bold; +} + +/* Sticky sidebar + * + * Add the `sidebar-sticky` class to the sidebar's container to affix it the + * contents to the bottom of the sidebar in tablets and up. + */ + +@media (min-width: 48em) { + .sidebar-sticky { + position: absolute; + right: 1rem; + bottom: 1rem; + left: 1rem; + } +} + + +/* Container + * + * Align the contents of the site above the proper threshold with some margin-fu + * with a 25%-wide `.sidebar`. + */ + +.content { + padding-top: 4rem; + padding-bottom: 4rem; +} + +@media (min-width: 48em) { + .content { + max-width: 38rem; + margin-left: 20rem; + margin-right: 2rem; + } +} + +@media (min-width: 64em) { + .content { + margin-left: 22rem; + margin-right: 4rem; + } +} + + +/* + * Reverse layout + * + * Flip the orientation of the page by placing the `.sidebar` on the right. + */ + +@media (min-width: 48em) { + .layout-reverse .sidebar { + left: auto; + right: 0; + } + .layout-reverse .content { + margin-left: 2rem; + margin-right: 20rem; + } +} + +@media (min-width: 64em) { + .layout-reverse .content { + margin-left: 4rem; + margin-right: 22rem; + } +} + + + +/* + * Themes + * + * As of v1.1, Hyde includes optional themes to color the sidebar and links + * within blog posts. To use, add the class of your choosing to the `body`. + */ + +/* Base16 (http://chriskempson.github.io/base16/#default) */ + +/* Red */ +.theme-base-08 .sidebar { + background-color: #ac4142; +} +.theme-base-08 .content a, +.theme-base-08 .related-posts li a:hover { + color: #ac4142; +} + +/* Orange */ +.theme-base-09 .sidebar { + background-color: #d28445; +} +.theme-base-09 .content a, +.theme-base-09 .related-posts li a:hover { + color: #d28445; +} + +/* Yellow */ +.theme-base-0a .sidebar { + background-color: #f4bf75; +} +.theme-base-0a .content a, +.theme-base-0a .related-posts li a:hover { + color: #f4bf75; +} + +/* Green */ +.theme-base-0b .sidebar { + background-color: #90a959; +} +.theme-base-0b .content a, +.theme-base-0b .related-posts li a:hover { + color: #90a959; +} + +/* Cyan */ +.theme-base-0c .sidebar { + background-color: #75b5aa; +} +.theme-base-0c .content a, +.theme-base-0c .related-posts li a:hover { + color: #75b5aa; +} + +/* Blue */ +.theme-base-0d .sidebar { + background-color: #6a9fb5; +} +.theme-base-0d .content a, +.theme-base-0d .related-posts li a:hover { + color: #6a9fb5; +} + +/* Magenta */ +.theme-base-0e .sidebar { + background-color: #aa759f; +} +.theme-base-0e .content a, +.theme-base-0e .related-posts li a:hover { + color: #aa759f; +} + +/* Brown */ +.theme-base-0f .sidebar { + background-color: #8f5536; +} +.theme-base-0f .content a, +.theme-base-0f .related-posts li a:hover { + color: #8f5536; +} diff --git a/docs/public/css/poole.css b/docs/public/css/poole.css new file mode 100644 index 000000000..e6eb714e6 --- /dev/null +++ b/docs/public/css/poole.css @@ -0,0 +1,436 @@ +/* + * SPDX-FileCopyrightText: 2021 Alliander N.V. + * + * SPDX-License-Identifier: CC-BY-4.0 + */ + +/* + * ___ + * /\_ \ + * _____ ___ ___\//\ \ __ + * /\ '__`\ / __`\ / __`\\ \ \ /'__`\ + * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/ + * \ \ ,__/\ \____/\ \____//\____\ \____\ + * \ \ \/ \/___/ \/___/ \/____/\/____/ + * \ \_\ + * \/_/ + * + * Designed, built, and released under MIT license by @mdo. Learn more at + * https://github.com/poole/poole. + */ + + +/* + * Contents + * + * Body resets + * Custom type + * Messages + * Container + * Masthead + * Posts and pages + * Pagination + * Reverse layout + * Themes + */ + + +/* + * Body resets + * + * Update the foundational and global aspects of the page. + */ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +html { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; +} +@media (min-width: 38em) { + html { + font-size: 20px; + } +} + +body { + color: #515151; + background-color: #fff; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +/* No `:visited` state is required by default (browsers will use `a`) */ +a { + color: #268bd2; + text-decoration: none; +} +a strong { + color: inherit; +} +/* `:focus` is linked to `:hover` for basic accessibility */ +a:hover, +a:focus { + text-decoration: underline; +} + +/* Headings */ +h1, h2, h3, h4, h5, h6 { + margin-bottom: .5rem; + font-weight: bold; + line-height: 1.25; + color: #313131; + text-rendering: optimizeLegibility; +} +h1 { + font-size: 2rem; +} +h2 { + margin-top: 1rem; + font-size: 1.5rem; +} +h3 { + margin-top: 1.5rem; + font-size: 1.25rem; +} +h4, h5, h6 { + margin-top: 1rem; + font-size: 1rem; +} + +/* Body text */ +p { + margin-top: 0; + margin-bottom: 1rem; +} + +strong { + color: #303030; +} + + +/* Lists */ +ul, ol, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +dt { + font-weight: bold; +} +dd { + margin-bottom: .5rem; +} + +/* Misc */ +hr { + position: relative; + margin: 1.5rem 0; + border: 0; + border-top: 1px solid #eee; + border-bottom: 1px solid #fff; +} + +abbr { + font-size: 85%; + font-weight: bold; + color: #555; + text-transform: uppercase; +} +abbr[title] { + cursor: help; + border-bottom: 1px dotted #e5e5e5; +} + +/* Code */ +code, +pre { + font-family: Menlo, Monaco, "Courier New", monospace; +} +code { + padding: .25em .5em; + font-size: 85%; + color: #bf616a; + background-color: #f9f9f9; + border-radius: 3px; +} +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + padding: 1rem; + font-size: .8rem; + line-height: 1.4; + white-space: pre; + white-space: pre-wrap; + word-break: break-all; + word-wrap: break-word; + background-color: #f9f9f9; +} +pre code { + padding: 0; + font-size: 100%; + color: inherit; + background-color: transparent; +} + +/* Pygments via Jekyll */ +.highlight { + margin-bottom: 1rem; + border-radius: 4px; +} +.highlight pre { + margin-bottom: 0; +} + +/* Gist via GitHub Pages */ +.gist .gist-file { + font-family: Menlo, Monaco, "Courier New", monospace !important; +} +.gist .markdown-body { + padding: 15px; +} +.gist pre { + padding: 0; + background-color: transparent; +} +.gist .gist-file .gist-data { + font-size: .8rem !important; + line-height: 1.4; +} +.gist code { + padding: 0; + color: inherit; + background-color: transparent; + border-radius: 0; +} + +/* Quotes */ +blockquote { + padding: .5rem 1rem; + margin: .8rem 0; + color: #7a7a7a; + border-left: .25rem solid #e5e5e5; +} +blockquote p:last-child { + margin-bottom: 0; +} +@media (min-width: 30em) { + blockquote { + padding-right: 5rem; + padding-left: 1.25rem; + } +} + +img { + display: block; + max-width: 100%; + margin: 0 0 1rem; + border-radius: 5px; +} + +/* Tables */ +table { + margin-bottom: 1rem; + width: 100%; + border: 1px solid #e5e5e5; + border-collapse: collapse; +} +td, +th { + padding: .25rem .5rem; + border: 1px solid #e5e5e5; +} +tbody tr:nth-child(odd) td, +tbody tr:nth-child(odd) th { + background-color: #f9f9f9; +} + + +/* + * Custom type + * + * Extend paragraphs with `.lead` for larger introductory text. + */ + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + + +/* + * Messages + * + * Show alert messages to users. You may add it to single elements like a `