From 1f09d05e6f29acc7c0293a3909ec6f1b511b987b Mon Sep 17 00:00:00 2001 From: klinachev Date: Sun, 17 Mar 2024 23:48:54 +0300 Subject: [PATCH 01/14] Init Adapter extensions commit --- .../org.fbme.ide.iec61499.lang.behavior.mps | 506 ++ ...org.fbme.ide.iec61499.lang.constraints.mps | 47 + .../org.fbme.ide.iec61499.lang.editor.mps | 117 + .../org.fbme.ide.iec61499.lang.structure.mps | 115 +- ...rg.fbme.ide.iec61499.adapter.fbnetwork.mps | 660 ++- ...bme.ide.iec61499.adapter.interfacepart.mps | 1347 +++++- .../org.fbme.ide.iec61499.repository.mps | 181 + .../org/fbme/lib/iec61499/IEC61499Factory.kt | 1 + .../declarations/AdapterTypeDeclaration.kt | 82 +- .../DeclarationWithInterfaceSection.kt | 3 + .../iec61499/descriptors/FBTypeDescriptor.kt | 16 + .../fbme/lib/iec61499/fbnetwork/FBNetwork.kt | 17 +- .../lib/iec61499/instances/NetworkInstance.kt | 7 + .../iec61499/parser/AdapterTypeConverter.kt | 31 + .../iec61499/stringify/AdapterTypePrinter.kt | 35 + .../org.fbme.ide.richediting.lang.editor.mps | 327 +- .../org.fbme.ide.richediting.plugin.mps | 2 +- .../actions/GenerateAdapterRouterAction.kt | 119 + .../richediting/actions/SwitchGenerator.kt | 297 ++ .../richediting/adapters/ecc/ECCEditors.kt | 6 +- .../fbnetwork/FBConnectionController.kt | 16 +- .../adapters/fbnetwork/FBNetworkEditors.kt | 6 +- .../viewmodel/FunctionBlockView.kt | 7 +- .../ide/richediting/viewmodel/NetworkView.kt | 24 +- .../src/main/resources/META-INF/plugin.xml | 3 + .../diagram/ConnectionControllerFactory.kt | 2 +- .../controllers/diagram/ConnectionEntry.kt | 9 +- .../diagram/ConnectionsFacility.kt | 13 +- .../controllers/diagram/DiagramFacility.kt | 2 +- .../org.fbme.ide.eccSandbox/OneStateTest.fbt | 1 + ....fbme.ide.iec61499.lang.sandbox.blinky.mps | 4270 ++++++++++++++++- .../ConfigurationManager.fbt | 22 +- .../ConsoleControl.fbt | 20 +- ...g.fbme.ide.iec61499.lang.sandbox.festo.mps | 44 +- ...e.iec61499.lang.sandbox.mpsPersistence.mps | 73 +- 35 files changed, 8153 insertions(+), 275 deletions(-) create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DeclarationWithInterfaceSection.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps index e5cb9e719..782dcbb2a 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps @@ -236,6 +236,7 @@ + @@ -4599,5 +4600,510 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps index e9b69780b..95cd88cec 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps @@ -212,6 +212,7 @@ + @@ -578,6 +579,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps index e652db39a..a98a7384f 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps @@ -1441,6 +1441,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4381,5 +4460,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps index 118e9d192..7b1050aef 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps @@ -539,26 +539,39 @@ - - + + + + + - - + + - - + + - - + - - + + + + + - - - - + + + + + + + + + + + + @@ -1575,5 +1588,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps index c407d7d91..695f80b9a 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps @@ -58,6 +58,7 @@ + @@ -215,6 +216,7 @@ + @@ -4423,7 +4425,15 @@ - + + + + + + + + + @@ -4449,9 +4459,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -4471,7 +4538,7 @@ - + @@ -4514,7 +4581,7 @@ - + @@ -4557,7 +4624,7 @@ - + @@ -4600,7 +4667,7 @@ - + @@ -4623,6 +4690,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4648,7 +4740,7 @@ - + @@ -4701,7 +4793,7 @@ - + @@ -4764,7 +4856,7 @@ - + @@ -4820,10 +4912,11 @@ + - + @@ -4856,7 +4949,6 @@ - @@ -4882,7 +4974,7 @@ - + @@ -4938,10 +5030,11 @@ + - + @@ -4974,7 +5067,6 @@ - @@ -4990,7 +5082,7 @@ - + @@ -5041,7 +5133,7 @@ - + @@ -5064,6 +5156,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5902,5 +6342,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps index 5a040de71..bc53b2102 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps @@ -28,6 +28,7 @@ + @@ -62,6 +63,7 @@ + @@ -129,6 +131,9 @@ + + + @@ -156,6 +161,7 @@ + @@ -254,6 +260,7 @@ + @@ -959,51 +966,977 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + @@ -7660,5 +8593,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps index e64069bfc..021c6d218 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps @@ -93,6 +93,9 @@ + + + @@ -445,6 +448,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4300,6 +4349,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt index 5cf2d46dc..8a20badbd 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt @@ -18,6 +18,7 @@ interface IEC61499Factory { fun createApplicationDeclaration(identifier: Identifier?): ApplicationDeclaration fun createBasicFBTypeDeclaration(identifier: Identifier?): BasicFBTypeDeclaration fun createCompositeFBTypeDeclaration(identifier: Identifier?): CompositeFBTypeDeclaration + fun createDeclarationWithInterfaceSection(identifier: Identifier?): DeclarationWithInterfaceSection fun createDeviceDeclaration(identifier: Identifier?): DeviceDeclaration fun createDeviceTypeDeclaration(identifier: Identifier?): DeviceTypeDeclaration fun createParameterAssignment(): ParameterAssignment diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt index 13e7cc6ee..2b5642172 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt @@ -1,14 +1,86 @@ package org.fbme.lib.iec61499.declarations -import org.fbme.lib.common.Declaration import org.fbme.lib.common.RootElement -import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor -import org.fbme.lib.iec61499.descriptors.PlugType -import org.fbme.lib.iec61499.descriptors.SocketType +import org.fbme.lib.iec61499.descriptors.* +import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider +import org.fbme.lib.iec61499.fbnetwork.EntryKind -interface AdapterTypeDeclaration : FBInterfaceDeclaration, Declaration, RootElement { +interface AdapterTypeDeclaration : CustomNetworkComponentProvider, FBInterfaceDeclaration, DeclarationWithNetwork, + RootElement { val plugTypeDescriptor: FBTypeDescriptor get() = PlugType(this) val socketTypeDescriptor: FBTypeDescriptor get() = SocketType(this) + + val internalFbPlugDescriptor: FBTypeDescriptor + get() = FBTypeDescriptorImpl( + typeName = this.name, + declaration = this, + eventInputPorts = outputEvents.asSequence() + .plus(fbToPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = true) + .toList(), + eventOutputPorts = inputEvents.asSequence() + .plus(fbToPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = false) + .toList(), + dataInputPorts = outputParameters.asSequence() + .plus(fbToPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = true) + .toList(), + dataOutputPorts = inputParameters.asSequence() + .plus(fbToPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = false) + .toList(), + ) + + val internalFbSocketDescriptor: FBTypeDescriptor + get() = FBTypeDescriptorImpl( + typeName = this.name, + declaration = this, + eventInputPorts = inputEvents.asSequence() + .plus(socketToFbInterface?.outputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = true) + .toList(), + eventOutputPorts = outputEvents.asSequence() + .plus(socketToFbInterface?.inputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = false) + .toList(), + dataInputPorts = inputParameters.asSequence() + .plus(socketToFbInterface?.outputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = true) + .toList(), + dataOutputPorts = outputParameters.asSequence() + .plus(socketToFbInterface?.inputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = false) + .toList(), + ) + + var inputRouter: ParameterDeclaration? + var outputRouter: ParameterDeclaration? + var socketToFbInterface: DeclarationWithInterfaceSection? + var fbToPlugInterface: DeclarationWithInterfaceSection? } + +private fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> + FBPortDescriptor( + name = event.name, + connectionKind = EntryKind.EVENT, + position = index, + isInput = isInput, + isValid = true, + declaration = event, + ) +} + +private fun Sequence.toParametersPortDescriptors(isInput: Boolean) = + mapIndexed { index, event -> + FBPortDescriptor( + name = event.name, + connectionKind = EntryKind.DATA, + position = index, + isInput = isInput, + isValid = true, + declaration = event, + ) + } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DeclarationWithInterfaceSection.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DeclarationWithInterfaceSection.kt new file mode 100644 index 000000000..94db76dda --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DeclarationWithInterfaceSection.kt @@ -0,0 +1,3 @@ +package org.fbme.lib.iec61499.declarations + +interface DeclarationWithInterfaceSection : FBInterfaceDeclaration \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt index 9f5185a74..1cf5cee74 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt @@ -14,3 +14,19 @@ interface FBTypeDescriptor { fun getAssociatedVariablesForInputEvent(eventNumber: Int): List fun getAssociatedVariablesForOutputEvent(eventNumber: Int): List } + +data class FBTypeDescriptorImpl( + override val typeName: String, + override val declaration: Declaration?, + override val eventInputPorts: List = listOf(), + override val eventOutputPorts: List = listOf(), + override val dataInputPorts: List = listOf(), + override val dataOutputPorts: List = listOf(), + override val socketPorts: List = listOf(), + override val plugPorts: List = listOf(), +) : FBTypeDescriptor { + + override fun getAssociatedVariablesForInputEvent(eventNumber: Int): List = listOf() + + override fun getAssociatedVariablesForOutputEvent(eventNumber: Int): List = listOf() +} \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt index 77e29eadd..7c7beb049 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt @@ -2,10 +2,15 @@ package org.fbme.lib.iec61499.fbnetwork import org.fbme.lib.common.Declaration import org.fbme.lib.common.Element -import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.DeclarationWithNetwork +import org.fbme.lib.iec61499.declarations.EventDeclaration +import org.fbme.lib.iec61499.declarations.ParameterDeclaration import java.util.* interface FBNetwork : Element { + /*readonly*/ + val customNetworkComponents: List + /*readonly*/ val contextComponents: List @@ -54,3 +59,13 @@ interface FBNetwork : Element { } } } + +interface CustomNetworkComponentProvider { + fun getCustomNetworkComponents(): List +} + +data class CustomNetworkComponent( + val block: FunctionBlockDeclarationBase, + val associatedElement: Element, // adapter node + val editable: Boolean = false, +) \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt index c780d4904..5c6136cf8 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt @@ -61,6 +61,12 @@ interface NetworkInstance : Instance { return RegularNetworkInstance(parent, networkDeclaration, device) } + @JvmStatic + fun createForAdapter(adapter: AdapterTypeDeclaration, parent: Instance?): NetworkInstance { + val networkDeclaration = adapter.network + return RegularNetworkInstance(parent, networkDeclaration, adapter) + } + @JvmStatic @JvmOverloads fun createForDeclaration(declaration: Declaration, parent: Instance? = null): NetworkInstance { @@ -76,6 +82,7 @@ interface NetworkInstance : Instance { is ApplicationDeclaration -> createForApplication(decl, parent) is ResourceDeclaration -> createForResource(decl, parent) is DeviceDeclaration -> createForImplicitResourceOfDevice(decl, parent) + is AdapterTypeDeclaration -> createForAdapter(decl, parent) else -> throw IllegalArgumentException("Unknown kind of declaration: " + decl!!.javaClass) } } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt index 5de0d80a4..4554e183c 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt @@ -2,12 +2,43 @@ package org.fbme.lib.iec61499.parser import org.fbme.lib.common.Identifier import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection +import org.jdom.Element class AdapterTypeConverter(arguments: ConverterArguments) : DeclarationConverterBase(arguments) { override fun extractDeclarationBody(identifier: Identifier?): AdapterTypeDeclaration { val declaration = factory.createAdapterTypeDeclaration(identifier) FBInterfaceConverter(this, declaration).extractInterface() + val element = checkNotNull(element) + FBNetworkConverter(with(element.getChild("FBNetwork")), declaration.network).extractNetwork() + extractRouters(element, declaration) + extractInternalInterfaces(element, declaration) return declaration } + + private fun extractInternalInterfaces(element: Element, declaration: AdapterTypeDeclaration) { + declaration.socketToFbInterface = + extractDeclarationWithInterfaceSection(element.getChild("socketToFbInterface")) + declaration.fbToPlugInterface = + extractDeclarationWithInterfaceSection(element.getChild("fbToPlugInterface")) + } + + private fun extractRouters(element: Element, declaration: AdapterTypeDeclaration) { + declaration.inputRouter = extractParameter(element, "inputRouter") + declaration.outputRouter = extractParameter(element, "outputRouter") + } + + private fun extractParameter(element: Element, fieldName: String) = element.getChild(fieldName)?.let { + ParameterDeclarationConverter(with(it)).extract() + } + + private fun extractDeclarationWithInterfaceSection( + element: Element?, + ): DeclarationWithInterfaceSection? = element?.let { + val identifier = identifierLocus.onDeclarationEntered(it) + val interfaceSection = factory.createDeclarationWithInterfaceSection(identifier) + FBInterfaceConverter(this.with(it), interfaceSection).extractInterface() + interfaceSection + } } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt index 5d95649a2..2d66f7fc5 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt @@ -1,11 +1,46 @@ package org.fbme.lib.iec61499.stringify import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection +import org.fbme.lib.iec61499.declarations.ParameterDeclaration import org.jdom.Element class AdapterTypePrinter(declaration: AdapterTypeDeclaration) : DeclarationPrinterBase(declaration, "AdapterType") { override fun printDeclarationBody(element: Element) { element.addContent(FBInterfacePrinter(this.element, false).print()) + element.addContent(FBNetworkPrinter(this.element.network).print()) + printRouters(element) + printInternalInterfaces(element, this.element) } + + private fun printRouters(element: Element) { + printParameter(root = element, parameterElementName = "inputRouter", declaration = this.element.inputRouter) + printParameter(root = element, parameterElementName = "outputRouter", declaration = this.element.outputRouter) + } + + private fun printInternalInterfaces(element: Element, declaration: AdapterTypeDeclaration) { + addNullableContent( + element, + printDeclarationWithInterface(declaration.socketToFbInterface, "socketToFbInterface"), + ) + addNullableContent( + element, + printDeclarationWithInterface(declaration.fbToPlugInterface, "fbToPlugInterface"), + ) + } + + private fun printDeclarationWithInterface( + fbInterface: DeclarationWithInterfaceSection?, + name: String, + ) = fbInterface?.let { + val child = Element(name) + child.addContent(FBInterfacePrinter(fbInterface, false).print()) + } + + private fun printParameter(root: Element, parameterElementName: String, declaration: ParameterDeclaration?) { + val createdElement = ParameterDeclarationPrinter.printAll(parameterElementName, listOfNotNull(declaration)) + addNullableContent(root, createdElement) + } + } diff --git a/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps b/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps index a9894c0b9..3e1170110 100644 --- a/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps +++ b/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps @@ -2070,7 +2070,6 @@ - @@ -2078,97 +2077,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + @@ -3173,5 +3086,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/solutions/org.fbme.ide.richediting/models/org.fbme.ide.richediting.plugin.mps b/code/richediting/solutions/org.fbme.ide.richediting/models/org.fbme.ide.richediting.plugin.mps index f9cc459f5..759653d8e 100644 --- a/code/richediting/solutions/org.fbme.ide.richediting/models/org.fbme.ide.richediting.plugin.mps +++ b/code/richediting/solutions/org.fbme.ide.richediting/models/org.fbme.ide.richediting.plugin.mps @@ -48,8 +48,8 @@ + - diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt new file mode 100644 index 000000000..cd2ba088b --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt @@ -0,0 +1,119 @@ +package org.fbme.ide.richediting.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.components.JBTextField +import com.intellij.ui.dsl.builder.* +import jetbrains.mps.ide.actions.MPSCommonDataKeys +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.lib.common.StringIdentifier +import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.fbnetwork.* +import org.fbme.lib.st.expressions.* +import org.fbme.lib.st.types.ElementaryType +import javax.swing.JComponent + + +class GenerateAdapterRouterAction : AnAction(), DumbAware { + + override fun update(event: AnActionEvent) = event.executeReadAction { + val repository = event.repository + val node = event.getData(MPSCommonDataKeys.NODE) + val adapterTypeDeclaration = node?.let { repository.adapterOrNull(node) } + event.presentation.isEnabledAndVisible = adapterTypeDeclaration != null + } + + override fun actionPerformed(event: AnActionEvent) { + val (outputsCount, routerName) = event.executeReadAction?> { + val repository = event.repository + + val node = event.getRequiredData(MPSCommonDataKeys.NODE) + val adapterTypeDeclaration = repository.adapter(node) + + val parameterNames = adapterTypeDeclaration.outputParameters + .mapNotNull { parameter -> parameter.name.takeIf { parameter.type == ElementaryType.INT } } + .toTypedArray() + val dialog = GenerateAdapterRouterDialog(checkNotNull(event.project), parameterNames) + if (!dialog.showAndGet()) { + return@executeReadAction null + } + val outputsCount = dialog.outputsCount + val routerName = dialog.routeParameterName + outputsCount to routerName + } ?: return + event.executeWriteActionInEditor { + val repository = event.repository + val factory = repository.iec61499Factory + val node = event.getRequiredData(MPSCommonDataKeys.NODE) + val adapterTypeDeclaration = repository.adapter(node) + val adapterName = adapterTypeDeclaration.name + val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) + + val routerDeclaration = factory.createCompositeFBTypeDeclaration( + StringIdentifier("${adapterName}_router") + ) + model.addRootNode((routerDeclaration as PlatformElement).node) + + val socket = factory.createSocketDeclaration(StringIdentifier("${adapterName}_socket")) + socket.typeReference.setTarget(adapterTypeDeclaration) + routerDeclaration.sockets.add(socket) + + for (i in 0 until outputsCount) { + val plug = factory.createPlugDeclaration(StringIdentifier("${adapterName}_plug$i")) + plug.typeReference.setTarget(adapterTypeDeclaration) + routerDeclaration.plugs.add(plug) + } + val switchGenerator = SwitchGenerator(factory, repository.stFactory) + switchGenerator.addSocketPlugsSwitch( + adapterName = "${adapterName}_internalLeftSwitch", + model = model, + root = routerDeclaration, + socket = socket, + plugs = routerDeclaration.plugs, + socketToPlug = true, + routerName = routerName, + ) + switchGenerator.addSocketPlugsSwitch( + adapterName = "${adapterName}_internalRightSwitch", + model = model, + root = routerDeclaration, + socket = socket, + plugs = routerDeclaration.plugs, + socketToPlug = false, + routerName = routerName, + ) + } + } + + @Suppress("UnstableApiUsage") + private class GenerateAdapterRouterDialog( + project: Project, + private val parametersNames: Array, + ) : DialogWrapper(project) { + private lateinit var outputsCountField: Cell + private lateinit var _routeParameterName: Cell> + + val outputsCount: Int get() = outputsCountField.component.text.toInt() + val routeParameterName: String get() = _routeParameterName.component.item + + init { + init() + title = "Generate Adapter Router" + } + + override fun createCenterPanel(): JComponent = panel { + row("Outputs count:") { + outputsCountField = intTextField(1..20) + } + row("Output selector field:") { + _routeParameterName = comboBox(parametersNames) + } + } + + } + +} diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt new file mode 100644 index 000000000..805ee9f45 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt @@ -0,0 +1,297 @@ +package org.fbme.ide.richediting.actions + +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.lib.common.Declaration +import org.fbme.lib.common.StringIdentifier +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.ecc.StateDeclaration +import org.fbme.lib.iec61499.fbnetwork.* +import org.fbme.lib.st.STFactory +import org.fbme.lib.st.expressions.* +import org.jetbrains.mps.openapi.model.SModel + +class SwitchGenerator( + private val factory: IEC61499Factory, + private val stFactory: STFactory, +) { + private val createdEvents = mutableListOf() + private val createdParams = mutableListOf() + + private data class EventCopyAndConnectObject( + val sourceEvent: EventDeclaration, + val createdEvent: EventDeclaration, + val input: Boolean, + val source: FunctionBlockDeclarationBase?, + val target: FunctionBlockDeclarationBase?, + ) + + private data class ParameterCopyAndConnectObject( + val sourceParam: ParameterDeclaration, + val createdParam: ParameterDeclaration, + val input: Boolean, + val source: FunctionBlockDeclarationBase?, + val target: FunctionBlockDeclarationBase?, + ) + + fun addSocketPlugsSwitch( + adapterName: String, + model: SModel, + root: CompositeFBTypeDeclaration, + socket: SocketDeclaration, + plugs: List, + socketToPlug: Boolean, + routerName: String, + ): FunctionBlockDeclaration { + createdEvents.clear() + createdParams.clear() + + val switchFBIdentifier = StringIdentifier(adapterName) + val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) + val switchBlock = factory.createFunctionBlockDeclaration(switchFBIdentifier) + switchBlock.typeReference.setTarget(switchDeclaration) + + val socketAdapterType = checkNotNull(socket.typeReference.getTarget()) + val eventsToCopy = if (socketToPlug) socketAdapterType.outputEvents else socketAdapterType.inputEvents + val paramsToCopy = + (if (socketToPlug) socketAdapterType.outputParameters else socketAdapterType.inputParameters) +// .filter { it.name != routerName } + createConnections( + sourceEvents = eventsToCopy, + sourceParameters = paramsToCopy, + switchType = switchDeclaration, + sourceBlockDeclaration = socket, + targetBlockDeclaration = switchBlock, + input = socketToPlug, + ) + for (i in plugs.indices) { + val plugDeclaration = plugs[i] + createConnections( + sourceEvents = eventsToCopy, + sourceParameters = paramsToCopy, + switchType = switchDeclaration, + sourceBlockDeclaration = plugDeclaration, + targetBlockDeclaration = switchBlock, + input = !socketToPlug, + nameSuffix = "_$i", + ) + } + configureEcc( + switchDeclaration = switchDeclaration, + eventDeclarations = createdEvents.groupBy { it.sourceEvent.name } + .flatMap { (_, value) -> + val (inputs, outputs) = value.partition { it.input } + inputs.map { input -> input.createdEvent to outputs.map { it.createdEvent } } + }, + parameterDeclarations = createdParams.map { it.sourceParam to it.createdParam }, + createdParams.firstOrNull { it.sourceParam.name == routerName }?.sourceParam, + ) + + model.addRootNode((switchDeclaration as PlatformElement).node) + root.network.functionBlocks.add(switchBlock) + createdEvents.asSequence() + .map { it.toNetworkConnection() } + .toCollection(root.network.eventConnections) + createdParams.asSequence() + .map { it.toNetworkConnection() } + .toCollection(root.network.dataConnections) + + return switchBlock + } + + private fun createConnections( + sourceEvents: List, + sourceParameters: List, + switchType: BasicFBTypeDeclaration, + sourceBlockDeclaration: FunctionBlockDeclarationBase?, + targetBlockDeclaration: FunctionBlockDeclarationBase?, + input: Boolean, + nameSuffix: String? = null, + ) { + val nameToParameterDeclaration = sourceParameters.associate { sourceDeclaration -> + val newDeclaration = sourceDeclaration.copy(nameSuffix = nameSuffix) + sourceDeclaration.name to ParameterCopyAndConnectObject( + sourceParam = sourceDeclaration, + createdParam = newDeclaration, + input = input, + source = sourceBlockDeclaration, + target = targetBlockDeclaration, + ) + } + val nameToCreatedParameterDeclaration = nameToParameterDeclaration.mapValues { it.value.createdParam } + val newEventDeclarations = sourceEvents.map { eventDeclaration -> + val newDeclaration = eventDeclaration.copy( + nameSuffix = nameSuffix, + nameToParameterDeclaration = nameToCreatedParameterDeclaration + ) + EventCopyAndConnectObject( + sourceEvent = eventDeclaration, + createdEvent = newDeclaration, + input = input, + source = sourceBlockDeclaration, + target = targetBlockDeclaration, + ) + } + createdEvents.addAll(newEventDeclarations) + createdParams.addAll(nameToParameterDeclaration.values) + val eventDeclarations = if (input) switchType.inputEvents else switchType.outputEvents + eventDeclarations.addAll(newEventDeclarations.map { it.createdEvent }) + val paramsDeclarations = if (input) switchType.inputParameters else switchType.outputParameters + paramsDeclarations.addAll(nameToCreatedParameterDeclaration.values) + } + + private fun configureEcc( + switchDeclaration: BasicFBTypeDeclaration, + eventDeclarations: List>>, + parameterDeclarations: List>, + routerParameterDeclaration: ParameterDeclaration?, + ) { + val startState = factory.createStateDeclaration(StringIdentifier("Start")) + switchDeclaration.ecc.states.add(startState) + for (i in eventDeclarations.indices) { + val (inputEvent, outputEvents) = eventDeclarations[i] + for (j in outputEvents.indices) { + val outputEvent = outputEvents[j] + addState( + switchDeclaration = switchDeclaration, + start = startState, + inputEventDeclaration = inputEvent, + outputEventDeclaration = outputEvent, + parameterDeclarations = parameterDeclarations, + number = j, + routeVariableDeclaration = routerParameterDeclaration, + ) + } + } + } + + private fun addState( + switchDeclaration: BasicFBTypeDeclaration, + start: StateDeclaration, + inputEventDeclaration: EventDeclaration, + outputEventDeclaration: EventDeclaration, + parameterDeclarations: List>, + number: Int, + routeVariableDeclaration: VariableDeclaration?, + ) { + val state = factory.createStateDeclaration( + StringIdentifier(outputEventDeclaration.name + "_state") + ) + val backTransition = factory.createStateTransition() + backTransition.sourceReference.setTarget(state) + backTransition.targetReference.setTarget(start) + val toNewStateTransition = factory.createStateTransition() + toNewStateTransition.sourceReference.setTarget(start) + toNewStateTransition.targetReference.setTarget(state) + toNewStateTransition.condition.eventReference.setFQName(inputEventDeclaration.identifier.toString()) + if (routeVariableDeclaration != null) { + val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) + + val numberLiteral = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal + + numberLiteral.value = number + equality.rightExpression = numberLiteral + + equality.leftExpression = createVariable(routeVariableDeclaration) + toNewStateTransition.condition.setGuardCondition(equality) + } + + val stateAction = factory.createStateAction() + + val algorithmDeclaration = factory.createAlgorithmDeclaration( + StringIdentifier(outputEventDeclaration.name + "_algorithm") + ) + val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST) + for ((assignable, variable) in parameterDeclarations) { + val assignment = stFactory.createAssignmentStatement() + assignment.variable = createVariable(variable) + assignment.expression = createVariable(assignable) + algorithmBody.statements.add(assignment) + algorithmDeclaration.body = algorithmBody + stateAction.algorithm.setTarget(algorithmDeclaration) + } + stateAction.event.setFQName(outputEventDeclaration.identifier.toString()) + + state.actions.add(stateAction) + switchDeclaration.algorithms.add(algorithmDeclaration) + switchDeclaration.ecc.states.add(state) + switchDeclaration.ecc.transitions.add(toNewStateTransition) + switchDeclaration.ecc.transitions.add(backTransition) + } + + private fun createVariable(routeVariableDeclaration: VariableDeclaration): VariableReference { + val variableReference = stFactory.createVariableReference() + variableReference.reference.setTarget(routeVariableDeclaration) + return variableReference + } + + private fun createNetworkConnection( + kind: EntryKind, + source: FunctionBlockDeclarationBase?, + sourcePortTarget: Declaration, + target: FunctionBlockDeclarationBase?, + targetPortTarget: Declaration, + ): FBNetworkConnection { + val connection = factory.createFBNetworkConnection(kind) + connection.sourceReference.setTarget(PortPath.createPortPath(source, kind, sourcePortTarget)) + connection.targetReference.setTarget(PortPath.createPortPath(target, kind, targetPortTarget)) + return connection + } + + private fun EventCopyAndConnectObject.toNetworkConnection() = if (input) createNetworkConnection( + kind = EntryKind.EVENT, + source = source, + sourcePortTarget = sourceEvent, + target = target, + targetPortTarget = createdEvent, + ) else createNetworkConnection( + kind = EntryKind.EVENT, + source = target, + sourcePortTarget = createdEvent, + target = source, + targetPortTarget = sourceEvent, + ) + + private fun ParameterCopyAndConnectObject.toNetworkConnection() = if (input) createNetworkConnection( + kind = EntryKind.DATA, + source = source, + sourcePortTarget = sourceParam, + target = target, + targetPortTarget = createdParam, + ) else createNetworkConnection( + kind = EntryKind.DATA, + source = target, + sourcePortTarget = createdParam, + target = source, + targetPortTarget = sourceParam, + ) + + private fun EventDeclaration.copy( + nameSuffix: String?, + nameToParameterDeclaration: Map + ): EventDeclaration { + val declaration = factory.createEventDeclaration(identifier) + declaration.name = name.withSuffix(nameSuffix) + declaration.associations.addAll(associations.map { eventAssociation -> + val association = factory.createEventAssociation() + eventAssociation.parameterReference.getTarget() + ?.let { nameToParameterDeclaration[it.name] } + ?.run { association.parameterReference.setTarget(this) } + association + }) + return declaration + } + + private fun ParameterDeclaration.copy(nameSuffix: String?): ParameterDeclaration { + val declaration = factory.createParameterDeclaration(identifier) + declaration.name = name.withSuffix(nameSuffix) + declaration.type = type + return declaration + } + + private fun String.withSuffix(nameSuffix: String?) = if (nameSuffix == null) { + this + } else { + this + nameSuffix + } +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/ecc/ECCEditors.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/ecc/ECCEditors.kt index cbc5332bb..e375105ba 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/ecc/ECCEditors.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/ecc/ECCEditors.kt @@ -13,9 +13,6 @@ import org.fbme.ide.richediting.editor.RichEditorStyleAttributes import org.fbme.ide.richediting.inspections.ECCInspectionsFacility import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.IEC61499Factory -import org.fbme.lib.iec61499.declarations.AlgorithmDeclaration -import org.fbme.lib.iec61499.declarations.BasicFBTypeDeclaration -import org.fbme.lib.iec61499.declarations.EventDeclaration import org.fbme.lib.iec61499.ecc.ECC import org.fbme.lib.iec61499.ecc.StateAction import org.fbme.lib.iec61499.ecc.StateDeclaration @@ -154,7 +151,8 @@ object ECCEditors { return object : ConnectionControllerFactory { override fun create( context: EditorContext, - view: StateTransition + view: StateTransition, + sourceConnectionNumber: Int? ): ConnectionController { val transitionNode = (view as PlatformElement).node val cell = createTransitionCell(context, transitionNode) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt index 27fe94e7c..72fee548a 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt @@ -24,8 +24,11 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min -class FBConnectionController(context: EditorContext, view: NetworkConnectionView) : - ConnectionController { +class FBConnectionController( + context: EditorContext, + view: NetworkConnectionView, + private val sourceConnectionNumber: Int?, +) : ConnectionController { private val kind: EntryKind private val isEditable: Boolean private val fakeCell: EditorCell_Collection @@ -54,6 +57,15 @@ class FBConnectionController(context: EditorContext, view: NetworkConnectionView FBConnectionPathPainter.setupRegularPathPaint(g, scale(1).toFloat()) } painter.paint(g, selected) + val printConnectionNumber = when (kind) { + EntryKind.EVENT, EntryKind.DATA -> false + EntryKind.ADAPTER -> true + } + if (sourceConnectionNumber != null && printConnectionNumber) { + val point = path.targetPosition + g.color = g.color.brighter() + g.drawString(sourceConnectionNumber.toString(), point.x - 10, point.y) + } } override fun paintTrace(path: FBConnectionPath, graphics: Graphics) { diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBNetworkEditors.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBNetworkEditors.kt index f1c24488d..12de05a91 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBNetworkEditors.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBNetworkEditors.kt @@ -39,11 +39,13 @@ object FBNetworkEditors { object : ConnectionControllerFactory { override fun create( context: EditorContext, - view: NetworkConnectionView + view: NetworkConnectionView, + sourceConnectionNumber: Int?, ): ConnectionController { return FBConnectionController( context, - view + view, + sourceConnectionNumber, ) } } diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/FunctionBlockView.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/FunctionBlockView.kt index 1c6b08c02..34f4a570c 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/FunctionBlockView.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/FunctionBlockView.kt @@ -5,10 +5,12 @@ import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase import org.jetbrains.mps.openapi.model.SNode -class FunctionBlockView(val component: FunctionBlockDeclarationBase, isEditable: Boolean) : NetworkComponentView { +class FunctionBlockView( + val component: FunctionBlockDeclarationBase, + override val isEditable: Boolean, val associatedNode: SNode = (component as PlatformElement).node +) : NetworkComponentView { private val myTypeDescriptor: TypeDescriptorAdapter - override val isEditable: Boolean val type: FBTypeDescriptor get() = myTypeDescriptor @@ -31,6 +33,5 @@ class FunctionBlockView(val component: FunctionBlockDeclarationBase, isEditable: init { myTypeDescriptor = TypeDescriptorAdapter(component.type) - this.isEditable = isEditable } } diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/NetworkView.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/NetworkView.kt index ce3af9c34..297ab5393 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/NetworkView.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/viewmodel/NetworkView.kt @@ -1,5 +1,6 @@ package org.fbme.ide.richediting.viewmodel +import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.lib.common.Declaration import org.fbme.lib.common.Element import org.fbme.lib.iec61499.IEC61499Factory @@ -16,7 +17,7 @@ class NetworkView(private val myFactory: IEC61499Factory, private val myNetwork: private val myAuxComponents: MutableMap> = HashMap() private val myComponentToPorts: MutableMap> = HashMap() private val myPorts: MutableMap = HashMap() - private val myConnectionSources: MutableMap = HashMap() + private val myConnectionSources: MutableMap = LinkedHashMap() private val myConnectionDestinations: MutableMap = HashMap() private val myElementModelMap: MutableMap = HashMap() private val myPortModelMap: MutableMap?, NetworkPortView> = HashMap() @@ -44,6 +45,16 @@ class NetworkView(private val myFactory: IEC61499Factory, private val myNetwork: } private fun initFBs(network: FBNetwork, editable: Boolean) { + for (component in network.customNetworkComponents) { + addFunctionBlock( + functionBlock = component.block, + view = FunctionBlockView( + component = component.block, + isEditable = component.editable, + associatedNode = (component.associatedElement as PlatformElement).node + ) + ) + } for (functionBlock in network.functionBlocks) { addFunctionBlock(functionBlock, editable) } @@ -140,8 +151,15 @@ class NetworkView(private val myFactory: IEC61499Factory, private val myNetwork: myComponentToPorts[view] = setOf(view) } - private fun addFunctionBlock(functionBlock: FunctionBlockDeclarationBase, editable: Boolean) { - val view = FunctionBlockView(functionBlock, editable) + private fun addFunctionBlock( + functionBlock: FunctionBlockDeclarationBase, + editable: Boolean + ) = addFunctionBlock(functionBlock, FunctionBlockView(functionBlock, editable)) + + private fun addFunctionBlock( + functionBlock: FunctionBlockDeclarationBase, + view: FunctionBlockView, + ) { myElementModelMap[functionBlock] = view myMainComponents.add(view) val type = functionBlock.type diff --git a/code/richediting/src/main/resources/META-INF/plugin.xml b/code/richediting/src/main/resources/META-INF/plugin.xml index 561b0538d..79321e4fa 100644 --- a/code/richediting/src/main/resources/META-INF/plugin.xml +++ b/code/richediting/src/main/resources/META-INF/plugin.xml @@ -59,6 +59,9 @@ + diff --git a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionControllerFactory.kt b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionControllerFactory.kt index bf5304ffa..c0dec9507 100644 --- a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionControllerFactory.kt +++ b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionControllerFactory.kt @@ -3,5 +3,5 @@ package org.fbme.scenes.controllers.diagram import jetbrains.mps.openapi.editor.EditorContext interface ConnectionControllerFactory { - fun create(context: EditorContext, view: ViewT): ConnectionController + fun create(context: EditorContext, view: ViewT, sourceConnectionNumber: Int?): ConnectionController } diff --git a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionEntry.kt b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionEntry.kt index 057c0c25e..6b06e3ee3 100644 --- a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionEntry.kt +++ b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionEntry.kt @@ -5,10 +5,15 @@ import java.awt.Rectangle internal class ConnectionEntry( private val connectionsFacility: ConnectionsFacility, - val connection: ConnT + val connection: ConnT, + sourceConnectionNumber: Int?, ) { val controller: ConnectionController = - connectionsFacility.controllerFactory.create(connectionsFacility.scene.editorContext, connection) + connectionsFacility.controllerFactory.create( + context = connectionsFacility.scene.editorContext, + view = connection, + sourceConnectionNumber = sourceConnectionNumber, + ) private val pathProvider: (Point, Point) -> PathT = connectionsFacility.connectionSynchronizer.getPath(connection) var modelPath: PathT var transformedPath: PathT? = null diff --git a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionsFacility.kt b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionsFacility.kt index 02bf1050c..92611650d 100644 --- a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionsFacility.kt +++ b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/ConnectionsFacility.kt @@ -59,9 +59,16 @@ class ConnectionsFacility( } private fun init() { - val viewConnections = diagramController.connections - for (connection in viewConnections) { - connections[connection] = ConnectionEntry(this@ConnectionsFacility, connection) + val sourcesConnectionsCount = diagramController.connections.groupBy { connection -> + diagramController.getSource(connection) + } + sourcesConnectionsCount.forEach { (_, controllerConnections) -> + val hasMultipleConnections = controllerConnections.size > 1 + for (i in controllerConnections.indices) { + val number = if (hasMultipleConnections) i else null + val connection = controllerConnections[i] + connections[connection] = ConnectionEntry(this@ConnectionsFacility, connection, number) + } } } diff --git a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/DiagramFacility.kt b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/DiagramFacility.kt index 0b25ab078..8883cafe0 100644 --- a/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/DiagramFacility.kt +++ b/code/scenes/src/main/kotlin/org/fbme/scenes/controllers/diagram/DiagramFacility.kt @@ -9,7 +9,7 @@ class DiagramFacility( private val componentSettings: DiagramComponentSettingProvider ) { private val components: MutableSet = HashSet() - private val edges: MutableSet = HashSet() + private val edges: MutableSet = LinkedHashSet() private val componentToPorts: MutableMap> = HashMap() private val portToComponent: MutableMap = HashMap() private val connectionToSource: MutableMap = HashMap() diff --git a/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt b/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt index f00608d4a..094958154 100644 --- a/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt @@ -17,6 +17,7 @@ + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps index 6beb1b7fa..f759edda8 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps @@ -11,9 +11,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -28,6 +53,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -38,6 +104,19 @@ + + + + + + + + + + + + + @@ -49,8 +128,29 @@ + + + + + + + + + + + + + + + + + + + + + @@ -81,6 +181,10 @@ + + + + @@ -101,15 +205,34 @@ + + + + + + + + + + + + + + + + + + + @@ -187,8 +310,8 @@ - - + + @@ -232,8 +355,8 @@ - - + + @@ -267,8 +390,8 @@ - - + + @@ -280,8 +403,8 @@ - - + + @@ -303,10 +426,10 @@ - - - - + + + + @@ -318,10 +441,10 @@ - - - - + + + + @@ -333,7 +456,9 @@ - + + + @@ -344,13 +469,15 @@ - + + + - + @@ -364,8 +491,8 @@ - - + + @@ -385,11 +512,4110 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt index b2c24c4dd..922518936 100755 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt @@ -37,12 +37,12 @@ - + - + - + @@ -65,19 +65,19 @@ - - - - + + + + - + - + - + @@ -86,7 +86,7 @@ - + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt index 8093ffd19..de59a9d31 100755 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt @@ -30,27 +30,27 @@ - + - + - + - + - - - - - - + + + + + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps index 1388ae2ec..fb36eccae 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps @@ -347,8 +347,8 @@ - - + + @@ -360,8 +360,8 @@ - - + + @@ -373,8 +373,8 @@ - - + + @@ -385,8 +385,8 @@ - - + + @@ -413,8 +413,8 @@ - - + + @@ -427,8 +427,8 @@ - - + + @@ -441,8 +441,8 @@ - - + + @@ -450,8 +450,8 @@ - - + + @@ -459,8 +459,8 @@ - - + + @@ -478,8 +478,8 @@ - - + + @@ -492,8 +492,8 @@ - - + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps index 67b9e3804..485c44a68 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps @@ -12,7 +12,7 @@ - + @@ -134,6 +134,10 @@ + + + + @@ -226,6 +230,7 @@ + @@ -285,8 +290,8 @@ - - + + @@ -306,8 +311,8 @@ - - + + @@ -387,8 +392,8 @@ - - + + @@ -396,8 +401,8 @@ - - + + @@ -792,8 +797,8 @@ - - + + @@ -805,8 +810,8 @@ - - + + @@ -1040,6 +1045,7 @@ + @@ -1117,7 +1123,9 @@ - + + + @@ -1128,27 +1136,38 @@ - + + + + + - - + + - - + + + + + + + + + @@ -1157,8 +1176,8 @@ - - + + @@ -1169,16 +1188,16 @@ - - + + - - + + @@ -1244,8 +1263,8 @@ - - + + From e062e9eea9f334396112e9ffac0150f37be28600 Mon Sep 17 00:00:00 2001 From: "radimir.sorokin" Date: Tue, 26 Mar 2024 01:05:06 +0100 Subject: [PATCH 02/14] Fix build-bootstrap.xml --- build-bootstrap.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-bootstrap.xml b/build-bootstrap.xml index 4f552809e..2416f0bd9 100644 --- a/build-bootstrap.xml +++ b/build-bootstrap.xml @@ -148,6 +148,9 @@ + + + From fc8c3295318a54b40e6e3313bd63b290f825e848 Mon Sep 17 00:00:00 2001 From: klinachev Date: Wed, 3 Apr 2024 13:11:37 +0300 Subject: [PATCH 03/14] Extended adapter separated, reveal action added --- .../org.fbme.ide.iec61499.lang.behavior.mps | 2020 +++- ...org.fbme.ide.iec61499.lang.constraints.mps | 47 - .../org.fbme.ide.iec61499.lang.editor.mps | 207 +- .../org.fbme.ide.iec61499.lang.structure.mps | 114 +- ...rg.fbme.ide.iec61499.adapter.fbnetwork.mps | 3 + ...bme.ide.iec61499.adapter.interfacepart.mps | 9350 ++++++++--------- .../org.fbme.ide.iec61499.repository.mps | 119 +- .../org/fbme/lib/iec61499/IEC61499Factory.kt | 2 + .../declarations/AdapterTypeDeclaration.kt | 82 +- .../declarations/FBInterfaceDeclaration.kt | 3 +- .../extention/AdapterNetworkDeclaration.kt | 106 + .../ExtendedAdapterTypeDeclaration.kt | 133 + .../iec61499/descriptors/FBTypeDescriptor.kt | 2 + .../fbme/lib/iec61499/fbnetwork/FBNetwork.kt | 10 +- .../lib/iec61499/instances/NetworkInstance.kt | 13 +- .../iec61499/parser/AdapterTypeConverter.kt | 33 +- .../parser/ExtendedAdapterTypeConverter.kt | 48 + .../iec61499/stringify/AdapterTypePrinter.kt | 35 - .../stringify/ExtendedAdapterTypePrinter.kt | 46 + .../platform/adapters/IEC61499FactoryTest.kt | 8 + .../org.fbme.ide.richediting.lang.editor.mps | 309 +- .../actions/GenerateAdapterRouterAction.kt | 44 +- .../actions/RevealExtendedAdapterAction.kt | 48 + .../fbnetwork/FBConnectionController.kt | 9 +- .../adapters/fbnetwork/fb/DiagramColors.kt | 3 + .../RichAdapterNetworkProjectionController.kt | 57 + .../AdapterLeftNetworkControllerProvider.kt | 23 + .../AdapterRightNetworkControllerProvider.kt | 23 + .../ide/richediting/utils/AdapterGenerator.kt | 98 + .../richediting/utils/ExtendedAdapterUtils.kt | 388 + .../{actions => utils}/SwitchGenerator.kt | 84 +- .../src/main/resources/META-INF/plugin.xml | 7 + ....fbme.ide.iec61499.lang.sandbox.blinky.mps | 650 +- 33 files changed, 8658 insertions(+), 5466 deletions(-) create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/RichAdapterNetworkProjectionController.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterLeftNetworkControllerProvider.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterRightNetworkControllerProvider.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt rename code/richediting/src/main/kotlin/org/fbme/ide/richediting/{actions => utils}/SwitchGenerator.kt (81%) diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps index 782dcbb2a..d4e445f92 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps @@ -116,6 +116,7 @@ + @@ -239,6 +240,7 @@ + @@ -941,6 +943,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -967,6 +993,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -990,6 +1063,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1013,6 +1133,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1036,6 +1260,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1058,11 +1386,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1086,6 +1485,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1109,6 +1555,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1132,6 +1625,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4601,61 +5141,263 @@ - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4672,42 +5414,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -4719,42 +5484,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -4766,42 +5554,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -4813,42 +5624,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -4857,26 +5691,36 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + @@ -4887,42 +5731,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -4934,42 +5801,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -4981,42 +5871,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -5028,42 +5941,65 @@ - - - - - - - - - + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + @@ -5105,5 +6041,607 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps index 95cd88cec..e9b69780b 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.constraints.mps @@ -212,7 +212,6 @@ - @@ -579,52 +578,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps index a98a7384f..91e6ef231 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps @@ -1441,85 +1441,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4474,7 +4395,7 @@ - + @@ -4498,5 +4419,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps index 7b1050aef..42cf34ec2 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps @@ -542,37 +542,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1590,8 +1559,8 @@ - - + + @@ -1617,7 +1586,7 @@ - + @@ -1631,29 +1600,29 @@ - + - + - - + + - + - - + + - + @@ -1662,5 +1631,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps index 695f80b9a..9b4187a55 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.fbnetwork.mps @@ -236,6 +236,7 @@ + @@ -6344,6 +6345,7 @@ + @@ -6448,6 +6450,7 @@ + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps index bc53b2102..d5b14595a 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps @@ -27,8 +27,8 @@ + - @@ -161,7 +161,6 @@ - @@ -243,6 +242,7 @@ + @@ -260,7 +260,6 @@ - @@ -973,11 +972,15 @@ + + + + - - + + @@ -1000,8 +1003,8 @@ - - + + @@ -1011,3741 +1014,2821 @@ - - - - - - + + + + - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + - - + + + + + + + + + + - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + - - + + - - + + + + + + + + - - + + - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + - - + + - - - - - + + - - - - + + - - + + - + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + - - + + - - - - - + + - - - - + + - - + + - - + + - - - - - + + + + + - - + + + + + - - - - - - - - - - + + + + + + + - - + + - + + + + + + + - - + + - - - - - - + + + + + + - - + + + + + - - - - - - - - - - + + + + + + + - - + + - - + + + + + + + + - - + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + - - + + - - - - - - + - + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + - - + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + - - - - - - - - - + + + + - - - - - + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + - - - - - - + + - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - + + + + + + - - - - + + + + + + + + + + + + + + + - - + + - + - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + - + - - - - + - - - - - - - - + + + + + + + + - + - - - - - - - - - - - - + + + + + + + - - + + + - + + + + - - + + - + - - - - - - - - + + + + + - - + + - - - - + + + + + + + - - + + + + + - - + + - - + + - + - - - - + - - - - - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + - - + + + + + - - + + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - - - - - + + + + + - + - - + + - - - - + + + + - - + + - - + + - - + + - + - - + + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - + + - - - - - - - - - - - - - - + + - - - + + + - - - + + + - - + + - + + + + - - - - - + + + + + - - + + - - - - + + + + - + - - - - - - + + + - - + + - + + + + - + - + - - - - - - - - - + + + + + + - - - - - - - + + + + + + + + + + - - + + - - - - - - - - + + - + - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + - - + + - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - + - - - - - - - + + + + + + + + - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + - - + + - + - - + + - - - + + + - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - + + + + + + + + + + + - + - - + + - - - - - - - - - - - - + + + + + + - - - - - - - + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + - + + + + + + - - + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + - - + + - + - - - - + + + + + + + + + - - - - - - - + + - - - - - - - - + + + + + + + + - - + + - + - - + + - - - + + + - - - - - - - - - + + + + + - - - - - - + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - + + + + + + + + + + + - + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + - - + + - - + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - - + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - + + + + + + + + + + + + + + + + - - + + - + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + - - + + - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + - - + + - + - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + - - + + - - + + + + + + - + - - - - + + + + + - - - - - - - - - - - - + + + + + - - - - - + + + - - - - - + + + + + + - - + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + + - + - - - - - - - - - - + + + + + + + + + + - + - + - - - - + + + + - - + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - + + - + - - + + - - - + + + - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + - + - - + + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + - - + + + + + - - + + + + + - - - - - - - - - + + + + + + - - - - - - - + + + + + + + + + - + - - - - - - + + + + + + - - + + + + + - - - - - - - - - - - - - + + + + + + + - - + + + + + + + + + + + + - + - - - - - - - - - + + + + - - + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + - + + + + - - - - - + + + + + - - + + - - - - + + + + - - + + - - - + + - - + + + - - + + - + - + - + - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - + - - + + - - + + - - - + + + - - + + - + - - - - - - + + + + + + + + + + - - - - - - - - + + + + + + - - - - - + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + - - + + + + + - - + + + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - + + + + + + - - - - - - + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + + + + + - - + + + + + + + + + - - - - - - + + + + + + + - + - - - - - - - - - - - - + + + + + + - - - + + - - - - - + + + + + + - - - - - - + + + - - + + + + + + + + + - - - - - + + + + - - - - - - - - - - - - - + + - + - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -6608,2336 +5691,3207 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + - - - - + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + - - + + - - - - + + + + + - - - - - - - - - - - - - + + - - - - - - + + + + + + + + + + + + - + - - - - - - - - - - - - - - + + + - - + + - - - - + + + + + + + + + + + + - - - - - - + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + - - - + + + + + + + + + + + + + - - + + - + + + + + + + - + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + - - - - - - - - + + + + + + + + + - - + + + + + + + + + + + + + + - - - - + + + + + - - + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + - - - - - + + - - - - + + + + - + - + + - - - - - - - - - - - - - + + + + + + - + - - - + + + + + + + + + + + + + + - - - - - + + + + + + + + + + - - + + - - - - - + + + + + - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - + + + + + + + - - - - - - + + + + + + + + + - - - - - - - - - - + + - - + + + + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + + + - - + + - - - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + - - + + + + + - - - + + + + + + + + - + - - - - + + + + + - - - - - - + + + + + + + + - - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - + - + - - + + - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + - - + + - - - - - + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - + + + - - + + - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + - - - - + + + + + + + - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - + + - - + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - + + + + + + - + + + - - - - - + + + + + + + + + - - - - - + + + + + + + - - + + - + - + - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + + + + - + - - + + - - - - + + + + - + - - - + + + - - + + - + - - + + - + - - - - - - - - + + - - + + - - + + - - - + + + - - + + - + - - - - - - + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + - - + + - + - + - - - - - - - - - - - - - - - - - - - + + + + + - - + + - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - + - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + - + + + + + + - - - - + + + + - - - - - - - + + + + + + + + - - - + + + + + + + - + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - + + - - + + - - - - + + + + - - + + - + - + - - + + - - + + - - + + - - - - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + - - - + + + + + + + + + + + + - - + + - + - - - - + - - + + - - + + - - + + - - - + + + - - + + - + - - - - - + + + + + - - + + - - + + - - - - + + + + - - + + - + - + - - + + - - + + - - + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + - - + + + + + + + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - + + - - + + - - - + + + - - + + - + - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - - - - - + + + + + - - + + - - - - - + + + + - - + + - - + + - - + + - - + + - + - + + + + - - + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + - - + + - + - + - - - - - + + + + + - - + + - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - + + - - - - - - + + + + + + + + - + - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + - - + + - + - - - - - - + + + + + + - - + + - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + - - - - + + - + - - - - - - - - - + + + + + + + + + - - + + - - - - - - + + + + + + + + + + + + + + + + + + + - - + + + + + - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + + - - + + - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + + + + - - - - - + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + - - - - - - + + + + + + + + - - + + + + + + + + + + + + + - + - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - - + + + + + - - + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - + + + + + + - - + + + + + - - - - - + + + + + + - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + - - - - - - + + + + + + + + + + + + + + + - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - - - - - - + + + + + + - - - - + + + + + + + + + + + + + + + - - + + - + - + - - - - - + + + + + - - + + - - - - - + + + + + - - + + - - - - - - - - + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + - - + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + - - + + - diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps index 021c6d218..e4d2b9bb4 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps @@ -29,6 +29,7 @@ + @@ -310,6 +311,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4317,6 +4364,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4424,7 +4535,7 @@ - + @@ -4443,7 +4554,7 @@ - + @@ -4457,7 +4568,7 @@ - + @@ -4476,7 +4587,7 @@ - + diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt index 8a20badbd..14e450fc0 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt @@ -2,6 +2,7 @@ package org.fbme.lib.iec61499 import org.fbme.lib.common.Identifier import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.fbme.lib.iec61499.ecc.StateAction import org.fbme.lib.iec61499.ecc.StateDeclaration import org.fbme.lib.iec61499.ecc.StateTransition @@ -13,6 +14,7 @@ import org.fbme.lib.iec61499.fbnetwork.subapp.SubapplicationDeclaration interface IEC61499Factory { fun createAdapterTypeDeclaration(identifier: Identifier?): AdapterTypeDeclaration + fun createExtendedAdapterTypeDeclaration(identifier: Identifier?): ExtendedAdapterTypeDeclaration fun createAlgorithmDeclaration(identifier: Identifier?): AlgorithmDeclaration fun createAlgorithmBody(language: AlgorithmLanguage): BodyT fun createApplicationDeclaration(identifier: Identifier?): ApplicationDeclaration diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt index 2b5642172..adc62ed90 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt @@ -1,86 +1,14 @@ package org.fbme.lib.iec61499.declarations import org.fbme.lib.common.RootElement -import org.fbme.lib.iec61499.descriptors.* -import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider -import org.fbme.lib.iec61499.fbnetwork.EntryKind +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor +import org.fbme.lib.iec61499.descriptors.PlugType +import org.fbme.lib.iec61499.descriptors.SocketType -interface AdapterTypeDeclaration : CustomNetworkComponentProvider, FBInterfaceDeclaration, DeclarationWithNetwork, +interface AdapterTypeDeclaration : FBInterfaceDeclaration, RootElement { val plugTypeDescriptor: FBTypeDescriptor get() = PlugType(this) val socketTypeDescriptor: FBTypeDescriptor get() = SocketType(this) - - val internalFbPlugDescriptor: FBTypeDescriptor - get() = FBTypeDescriptorImpl( - typeName = this.name, - declaration = this, - eventInputPorts = outputEvents.asSequence() - .plus(fbToPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = true) - .toList(), - eventOutputPorts = inputEvents.asSequence() - .plus(fbToPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = false) - .toList(), - dataInputPorts = outputParameters.asSequence() - .plus(fbToPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) - .toParametersPortDescriptors(isInput = true) - .toList(), - dataOutputPorts = inputParameters.asSequence() - .plus(fbToPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) - .toParametersPortDescriptors(isInput = false) - .toList(), - ) - - val internalFbSocketDescriptor: FBTypeDescriptor - get() = FBTypeDescriptorImpl( - typeName = this.name, - declaration = this, - eventInputPorts = inputEvents.asSequence() - .plus(socketToFbInterface?.outputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = true) - .toList(), - eventOutputPorts = outputEvents.asSequence() - .plus(socketToFbInterface?.inputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = false) - .toList(), - dataInputPorts = inputParameters.asSequence() - .plus(socketToFbInterface?.outputParameters?.asSequence() ?: sequenceOf()) - .toParametersPortDescriptors(isInput = true) - .toList(), - dataOutputPorts = outputParameters.asSequence() - .plus(socketToFbInterface?.inputParameters?.asSequence() ?: sequenceOf()) - .toParametersPortDescriptors(isInput = false) - .toList(), - ) - - var inputRouter: ParameterDeclaration? - var outputRouter: ParameterDeclaration? - var socketToFbInterface: DeclarationWithInterfaceSection? - var fbToPlugInterface: DeclarationWithInterfaceSection? -} - -private fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> - FBPortDescriptor( - name = event.name, - connectionKind = EntryKind.EVENT, - position = index, - isInput = isInput, - isValid = true, - declaration = event, - ) -} - -private fun Sequence.toParametersPortDescriptors(isInput: Boolean) = - mapIndexed { index, event -> - FBPortDescriptor( - name = event.name, - connectionKind = EntryKind.DATA, - position = index, - isInput = isInput, - isValid = true, - declaration = event, - ) - } +} \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt index 14be726c8..255796b75 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt @@ -2,6 +2,7 @@ package org.fbme.lib.iec61499.declarations import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor +import org.fbme.lib.iec61499.descriptors.SocketType interface FBInterfaceDeclaration : Declaration { val inputEvents: MutableList @@ -17,7 +18,7 @@ interface FBInterfaceDeclaration : Declaration { return this.typeDescriptor } if (this is AdapterTypeDeclaration) { - return this.socketTypeDescriptor + return SocketType(this) } throw IllegalArgumentException("Unknown declaration with FB interface: " + this.javaClass.name) } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt new file mode 100644 index 000000000..38ca793ba --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt @@ -0,0 +1,106 @@ +package org.fbme.lib.iec61499.declarations.extention + +import org.fbme.lib.common.Declaration +import org.fbme.lib.iec61499.declarations.DeclarationWithNetwork +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptorImpl +import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider + +interface AdapterNetworkDeclaration : Declaration, CustomNetworkComponentProvider, DeclarationWithNetwork { + override val container: ExtendedAdapterTypeDeclaration + + fun internalFbPlugDescriptor(): FBTypeDescriptor { + val adapter = container + val extendPlug = networkType().extendPlug + return FBTypeDescriptorImpl( + typeName = this.name, + declaration = this, + eventInputPorts = adapter.outputEvents.asSequence() + .plus(adapter.takeIf { extendPlug } + ?.fbToPlugInterface?.outputEvents?.asSequence() + ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = true) + .toList(), + eventOutputPorts = adapter.inputEvents.asSequence() + .plus(adapter.takeIf { extendPlug } + ?.fbToPlugInterface?.inputEvents?.asSequence() + ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = false) + .toList(), + dataInputPorts = adapter.outputParameters.asSequence() + .plus(adapter.takeIf { extendPlug } + ?.fbToPlugInterface?.outputParameters?.asSequence() + ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = true) + .toList(), + dataOutputPorts = adapter.inputParameters.asSequence() + .plus(adapter.takeIf { extendPlug } + ?.fbToPlugInterface?.inputParameters?.asSequence() + ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = false) + .toList(), + ) + } + + fun internalFbSocketDescriptor(): FBTypeDescriptor { + val adapter = container + val extendSocket = networkType().extendSocket + return FBTypeDescriptorImpl( + typeName = this.name, + declaration = this, + eventInputPorts = adapter.inputEvents.asSequence() + .plus(adapter.takeIf { extendSocket } + ?.socketToFbInterface?.outputEvents?.asSequence() + ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = true) + .toList(), + eventOutputPorts = adapter.outputEvents.asSequence() + .plus(adapter.takeIf { extendSocket } + ?.socketToFbInterface?.inputEvents?.asSequence() + ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = false) + .toList(), + dataInputPorts = adapter.inputParameters.asSequence() + .plus(adapter.takeIf { extendSocket } + ?.socketToFbInterface?.outputParameters?.asSequence() + ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = true) + .toList(), + dataOutputPorts = adapter.outputParameters.asSequence() + .plus(adapter.takeIf { extendSocket } + ?.socketToFbInterface?.inputParameters?.asSequence() + ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = false) + .toList(), + ) + } + + fun networkType(): NetworkType = + if (container.leftNetwork == null || container.rightNetwork == null) { + NetworkType.SINGLE + } else if (container.rightNetwork == this) { + NetworkType.RIGHT + } else if (container.leftNetwork == this) { + NetworkType.LEFT + } else { + error("Container doesn't contain this network") + } + + enum class NetworkType( + val extendSocket: Boolean, + val extendPlug: Boolean, + ) { + LEFT(false, true), + RIGHT(true, false), + SINGLE(true, true), + } +} + diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt new file mode 100644 index 000000000..a240a496e --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt @@ -0,0 +1,133 @@ +package org.fbme.lib.iec61499.declarations.extention + +import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.descriptors.FBPortDescriptor +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptorImpl +import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider +import org.fbme.lib.iec61499.fbnetwork.EntryKind + +interface ExtendedAdapterTypeDeclaration : CustomNetworkComponentProvider, AdapterTypeDeclaration, + DeclarationWithNetwork { + val leftNetwork: AdapterNetworkDeclaration? + val rightNetwork: AdapterNetworkDeclaration? + + override val plugTypeDescriptor: FBTypeDescriptor + get() = FBTypeDescriptorImpl( + typeName = this.name, + declaration = this, + eventInputPorts = outputEvents.asSequence() + .plus(socketToFbInterface?.inputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = true) + .toList(), + eventOutputPorts = inputEvents.asSequence() + .plus(socketToFbInterface?.outputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = false) + .toList(), + dataInputPorts = outputParameters.asSequence() + .plus(socketToFbInterface?.inputParameters?.asSequence() ?: sequenceOf()) + .run { outputRouter?.let { plus(it) } ?: this } + .toParametersPortDescriptors(isInput = true) + .toList(), + dataOutputPorts = inputParameters.asSequence() + .plus(socketToFbInterface?.outputParameters?.asSequence() ?: sequenceOf()) + .run { inputRouter?.let { plus(it) } ?: this } + .toParametersPortDescriptors(isInput = false) + .toList(), + ) + + override val socketTypeDescriptor: FBTypeDescriptor + get() = FBTypeDescriptorImpl( + typeName = this.name, + declaration = this, + eventInputPorts = inputEvents.asSequence() + .plus(fbToPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = true) + .toList(), + eventOutputPorts = outputEvents.asSequence() + .plus(fbToPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = false) + .toList(), + dataInputPorts = inputParameters.asSequence() + .plus(fbToPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = true) + .toList(), + dataOutputPorts = outputParameters.asSequence() + .plus(fbToPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = false) + .toList(), + ) + +// val internalFbPlugDescriptor: FBTypeDescriptor +// get() = FBTypeDescriptorImpl( +// typeName = this.name, +// declaration = this, +// eventInputPorts = outputEvents.asSequence() +// .plus(fbToPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) +// .toEventPortDescriptors(isInput = true) +// .toList(), +// eventOutputPorts = inputEvents.asSequence() +// .plus(fbToPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) +// .toEventPortDescriptors(isInput = false) +// .toList(), +// dataInputPorts = outputParameters.asSequence() +// .plus(fbToPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) +// .toParametersPortDescriptors(isInput = true) +// .toList(), +// dataOutputPorts = inputParameters.asSequence() +// .plus(fbToPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) +// .toParametersPortDescriptors(isInput = false) +// .toList(), +// ) +// +// val internalFbSocketDescriptor: FBTypeDescriptor +// get() = FBTypeDescriptorImpl( +// typeName = this.name, +// declaration = this, +// eventInputPorts = inputEvents.asSequence() +// .plus(socketToFbInterface?.outputEvents?.asSequence() ?: sequenceOf()) +// .toEventPortDescriptors(isInput = true) +// .toList(), +// eventOutputPorts = outputEvents.asSequence() +// .plus(socketToFbInterface?.inputEvents?.asSequence() ?: sequenceOf()) +// .toEventPortDescriptors(isInput = false) +// .toList(), +// dataInputPorts = inputParameters.asSequence() +// .plus(socketToFbInterface?.outputParameters?.asSequence() ?: sequenceOf()) +// .toParametersPortDescriptors(isInput = true) +// .toList(), +// dataOutputPorts = outputParameters.asSequence() +// .plus(socketToFbInterface?.inputParameters?.asSequence() ?: sequenceOf()) +// .toParametersPortDescriptors(isInput = false) +// .toList(), +// ) + + var inputRouter: ParameterDeclaration? + var outputRouter: ParameterDeclaration? + + var socketToFbInterface: DeclarationWithInterfaceSection? + var fbToPlugInterface: DeclarationWithInterfaceSection? +} + +fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> + FBPortDescriptor( + name = event.name, + connectionKind = EntryKind.EVENT, + position = index, + isInput = isInput, + isValid = true, + declaration = event, + ) +} + +fun Sequence.toParametersPortDescriptors(isInput: Boolean) = + mapIndexed { index, event -> + FBPortDescriptor( + name = event.name, + connectionKind = EntryKind.DATA, + position = index, + isInput = isInput, + isValid = true, + declaration = event, + ) + } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt index 1cf5cee74..1225e3964 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt @@ -27,6 +27,8 @@ data class FBTypeDescriptorImpl( ) : FBTypeDescriptor { override fun getAssociatedVariablesForInputEvent(eventNumber: Int): List = listOf() +// FBTypeDescriptorUtils.getAssociatedVariablesForInputEvent(myDeclaration, eventNumber) override fun getAssociatedVariablesForOutputEvent(eventNumber: Int): List = listOf() +// FBTypeDescriptorUtils.getAssociatedVariablesForOutputEvent(myDeclaration, eventNumber) } \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt index 7c7beb049..b5f228e29 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt @@ -49,6 +49,14 @@ interface FBNetwork : Element { return components } + fun copyElements(network: FBNetwork) { + functionBlocks += network.functionBlocks.map { it.copy() as FunctionBlockDeclaration } + adapterConnections += network.adapterConnections.map { it.copy() as FBNetworkConnection } + dataConnections += network.dataConnections.map { it.copy() as FBNetworkConnection } + eventConnections += network.eventConnections.map { it.copy() as FBNetworkConnection } + endpointCoordinates += network.endpointCoordinates.map { it.copy() as EndpointCoordinate } + } + companion object { @JvmStatic fun extractNetwork(declaration: Declaration?): FBNetwork? { @@ -66,6 +74,6 @@ interface CustomNetworkComponentProvider { data class CustomNetworkComponent( val block: FunctionBlockDeclarationBase, - val associatedElement: Element, // adapter node + val associatedElement: Element, val editable: Boolean = false, ) \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt index 5c6136cf8..55fe8814f 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt @@ -2,6 +2,8 @@ package org.fbme.lib.iec61499.instances import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.fbme.lib.iec61499.fbnetwork.FBNetwork import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase @@ -62,11 +64,17 @@ interface NetworkInstance : Instance { } @JvmStatic - fun createForAdapter(adapter: AdapterTypeDeclaration, parent: Instance?): NetworkInstance { + fun createForAdapter(adapter: ExtendedAdapterTypeDeclaration, parent: Instance?): NetworkInstance { val networkDeclaration = adapter.network return RegularNetworkInstance(parent, networkDeclaration, adapter) } + @JvmStatic + fun createForAdapterNetwork(adapterNetworkDeclaration: AdapterNetworkDeclaration, parent: Instance?): NetworkInstance { + val networkDeclaration = adapterNetworkDeclaration.network + return RegularNetworkInstance(parent, networkDeclaration, adapterNetworkDeclaration) + } + @JvmStatic @JvmOverloads fun createForDeclaration(declaration: Declaration, parent: Instance? = null): NetworkInstance { @@ -82,7 +90,8 @@ interface NetworkInstance : Instance { is ApplicationDeclaration -> createForApplication(decl, parent) is ResourceDeclaration -> createForResource(decl, parent) is DeviceDeclaration -> createForImplicitResourceOfDevice(decl, parent) - is AdapterTypeDeclaration -> createForAdapter(decl, parent) + is AdapterNetworkDeclaration -> createForAdapterNetwork(decl, parent) + is ExtendedAdapterTypeDeclaration -> createForAdapter(decl, parent) else -> throw IllegalArgumentException("Unknown kind of declaration: " + decl!!.javaClass) } } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt index 4554e183c..70023c835 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/AdapterTypeConverter.kt @@ -2,43 +2,12 @@ package org.fbme.lib.iec61499.parser import org.fbme.lib.common.Identifier import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration -import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection -import org.jdom.Element -class AdapterTypeConverter(arguments: ConverterArguments) : +open class AdapterTypeConverter(arguments: ConverterArguments) : DeclarationConverterBase(arguments) { override fun extractDeclarationBody(identifier: Identifier?): AdapterTypeDeclaration { val declaration = factory.createAdapterTypeDeclaration(identifier) FBInterfaceConverter(this, declaration).extractInterface() - val element = checkNotNull(element) - FBNetworkConverter(with(element.getChild("FBNetwork")), declaration.network).extractNetwork() - extractRouters(element, declaration) - extractInternalInterfaces(element, declaration) return declaration } - - private fun extractInternalInterfaces(element: Element, declaration: AdapterTypeDeclaration) { - declaration.socketToFbInterface = - extractDeclarationWithInterfaceSection(element.getChild("socketToFbInterface")) - declaration.fbToPlugInterface = - extractDeclarationWithInterfaceSection(element.getChild("fbToPlugInterface")) - } - - private fun extractRouters(element: Element, declaration: AdapterTypeDeclaration) { - declaration.inputRouter = extractParameter(element, "inputRouter") - declaration.outputRouter = extractParameter(element, "outputRouter") - } - - private fun extractParameter(element: Element, fieldName: String) = element.getChild(fieldName)?.let { - ParameterDeclarationConverter(with(it)).extract() - } - - private fun extractDeclarationWithInterfaceSection( - element: Element?, - ): DeclarationWithInterfaceSection? = element?.let { - val identifier = identifierLocus.onDeclarationEntered(it) - val interfaceSection = factory.createDeclarationWithInterfaceSection(identifier) - FBInterfaceConverter(this.with(it), interfaceSection).extractInterface() - interfaceSection - } } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt new file mode 100644 index 000000000..1c2d70fcc --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt @@ -0,0 +1,48 @@ +package org.fbme.lib.iec61499.parser + +import org.fbme.lib.common.Identifier +import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.jdom.Element + +class ExtendedAdapterTypeConverter(arguments: ConverterArguments) + : DeclarationConverterBase(arguments) { + override fun extractDeclarationBody(identifier: Identifier?): ExtendedAdapterTypeDeclaration { + val declaration = factory.createExtendedAdapterTypeDeclaration(identifier) + FBInterfaceConverter(this, declaration).extractInterface() + val element = checkNotNull(element) + FBNetworkConverter(with(element.getChild("FBNetwork")), declaration.network).extractNetwork() + extractRouters(element, declaration) + extractInternalInterfaces(element, declaration) + return declaration + } + + private fun extractInternalInterfaces(element: Element, declaration: ExtendedAdapterTypeDeclaration) { + declaration.socketToFbInterface = + extractDeclarationWithInterfaceSection(element.getChild("socketToFbInterface")) + declaration.fbToPlugInterface = + extractDeclarationWithInterfaceSection(element.getChild("fbToPlugInterface")) + } + + private fun extractRouters(element: Element, declaration: ExtendedAdapterTypeDeclaration) { + declaration.inputRouter = extractParameter(element, "inputRouter") + declaration.outputRouter = extractParameter(element, "outputRouter") + } + + private fun extractParameter(element: Element, fieldName: String) = element.getChild(fieldName)?.let { + ParameterDeclarationConverter(with(it)).extract() + } + + private fun extractDeclarationWithInterfaceSection( + element: Element?, + ): DeclarationWithInterfaceSection? = element?.let { + val identifier = identifierLocus.onDeclarationEntered(it) + try { + val interfaceSection = factory.createDeclarationWithInterfaceSection(identifier) + FBInterfaceConverter(this.with(it), interfaceSection).extractInterface() + interfaceSection + } finally { + identifierLocus.onDeclarationLeaved() + } + } +} \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt index 2d66f7fc5..5d95649a2 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/AdapterTypePrinter.kt @@ -1,46 +1,11 @@ package org.fbme.lib.iec61499.stringify import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration -import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection -import org.fbme.lib.iec61499.declarations.ParameterDeclaration import org.jdom.Element class AdapterTypePrinter(declaration: AdapterTypeDeclaration) : DeclarationPrinterBase(declaration, "AdapterType") { override fun printDeclarationBody(element: Element) { element.addContent(FBInterfacePrinter(this.element, false).print()) - element.addContent(FBNetworkPrinter(this.element.network).print()) - printRouters(element) - printInternalInterfaces(element, this.element) } - - private fun printRouters(element: Element) { - printParameter(root = element, parameterElementName = "inputRouter", declaration = this.element.inputRouter) - printParameter(root = element, parameterElementName = "outputRouter", declaration = this.element.outputRouter) - } - - private fun printInternalInterfaces(element: Element, declaration: AdapterTypeDeclaration) { - addNullableContent( - element, - printDeclarationWithInterface(declaration.socketToFbInterface, "socketToFbInterface"), - ) - addNullableContent( - element, - printDeclarationWithInterface(declaration.fbToPlugInterface, "fbToPlugInterface"), - ) - } - - private fun printDeclarationWithInterface( - fbInterface: DeclarationWithInterfaceSection?, - name: String, - ) = fbInterface?.let { - val child = Element(name) - child.addContent(FBInterfacePrinter(fbInterface, false).print()) - } - - private fun printParameter(root: Element, parameterElementName: String, declaration: ParameterDeclaration?) { - val createdElement = ParameterDeclarationPrinter.printAll(parameterElementName, listOfNotNull(declaration)) - addNullableContent(root, createdElement) - } - } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt new file mode 100644 index 000000000..98535e5a2 --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt @@ -0,0 +1,46 @@ +package org.fbme.lib.iec61499.stringify + +import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection +import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.jdom.Element + +class ExtendedAdapterTypePrinter(declaration: ExtendedAdapterTypeDeclaration) : + DeclarationPrinterBase(declaration, "ExtendedAdapterType") { + override fun printDeclarationBody(element: Element) { + element.addContent(FBInterfacePrinter(this.element, false).print()) + element.addContent(FBNetworkPrinter(this.element.network).print()) + printRouters(element) + printInternalInterfaces(element, this.element) + } + + private fun printRouters(element: Element) { + printParameter(root = element, parameterElementName = "inputRouter", declaration = this.element.inputRouter) + printParameter(root = element, parameterElementName = "outputRouter", declaration = this.element.outputRouter) + } + + private fun printInternalInterfaces(element: Element, declaration: ExtendedAdapterTypeDeclaration) { + addNullableContent( + element, + printDeclarationWithInterface(declaration.socketToFbInterface, "socketToFbInterface"), + ) + addNullableContent( + element, + printDeclarationWithInterface(declaration.fbToPlugInterface, "fbToPlugInterface"), + ) + } + + private fun printDeclarationWithInterface( + fbInterface: DeclarationWithInterfaceSection?, + name: String, + ) = fbInterface?.let { + val child = Element(name) + child.addContent(FBInterfacePrinter(fbInterface, false).print()) + } + + private fun printParameter(root: Element, parameterElementName: String, declaration: ParameterDeclaration?) { + val createdElement = ParameterDeclarationPrinter.printAll(parameterElementName, listOfNotNull(declaration)) + addNullableContent(root, createdElement) + } + +} diff --git a/code/platform/src/test/kotlin/org/fbme/ide/platform/adapters/IEC61499FactoryTest.kt b/code/platform/src/test/kotlin/org/fbme/ide/platform/adapters/IEC61499FactoryTest.kt index 45805a3fd..3dfca06e2 100644 --- a/code/platform/src/test/kotlin/org/fbme/ide/platform/adapters/IEC61499FactoryTest.kt +++ b/code/platform/src/test/kotlin/org/fbme/ide/platform/adapters/IEC61499FactoryTest.kt @@ -16,6 +16,14 @@ class IEC61499FactoryTest : PlatformTestBase() { Assert.assertEquals("TestAdapterTypeDeclaration", element.name) } + @Test + fun createExtendedAdapterTypeDeclaration() { + val identifier = StringIdentifier("TestExtendedAdapterTypeDeclaration") + val element = factory.createExtendedAdapterTypeDeclaration(identifier) + Assert.assertNotNull(element) + Assert.assertEquals("TestExtendedAdapterTypeDeclaration", element.name) + } + @Test fun createAlgorithmDeclaration() { val identifier = StringIdentifier("TestAlgorithmDeclaration") diff --git a/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps b/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps index 3e1170110..c81353cb6 100644 --- a/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps +++ b/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps @@ -3111,57 +3111,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3318,5 +3268,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt index cd2ba088b..2070ef058 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt @@ -9,8 +9,7 @@ import com.intellij.openapi.ui.DialogWrapper import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.* import jetbrains.mps.ide.actions.MPSCommonDataKeys -import org.fbme.ide.iec61499.repository.PlatformElement -import org.fbme.lib.common.StringIdentifier +import org.fbme.ide.richediting.utils.SwitchGenerator import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.fbnetwork.* import org.fbme.lib.st.expressions.* @@ -47,44 +46,17 @@ class GenerateAdapterRouterAction : AnAction(), DumbAware { } ?: return event.executeWriteActionInEditor { val repository = event.repository - val factory = repository.iec61499Factory val node = event.getRequiredData(MPSCommonDataKeys.NODE) val adapterTypeDeclaration = repository.adapter(node) val adapterName = adapterTypeDeclaration.name val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) - - val routerDeclaration = factory.createCompositeFBTypeDeclaration( - StringIdentifier("${adapterName}_router") - ) - model.addRootNode((routerDeclaration as PlatformElement).node) - - val socket = factory.createSocketDeclaration(StringIdentifier("${adapterName}_socket")) - socket.typeReference.setTarget(adapterTypeDeclaration) - routerDeclaration.sockets.add(socket) - - for (i in 0 until outputsCount) { - val plug = factory.createPlugDeclaration(StringIdentifier("${adapterName}_plug$i")) - plug.typeReference.setTarget(adapterTypeDeclaration) - routerDeclaration.plugs.add(plug) - } - val switchGenerator = SwitchGenerator(factory, repository.stFactory) - switchGenerator.addSocketPlugsSwitch( - adapterName = "${adapterName}_internalLeftSwitch", - model = model, - root = routerDeclaration, - socket = socket, - plugs = routerDeclaration.plugs, - socketToPlug = true, - routerName = routerName, - ) - switchGenerator.addSocketPlugsSwitch( - adapterName = "${adapterName}_internalRightSwitch", - model = model, - root = routerDeclaration, - socket = socket, - plugs = routerDeclaration.plugs, - socketToPlug = false, - routerName = routerName, + val switchGenerator = SwitchGenerator(repository.iec61499Factory, repository.stFactory) + switchGenerator.generateRouter( + adapterName, + model, + adapterTypeDeclaration, + outputsCount, + routerName ) } } diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt new file mode 100644 index 000000000..18c1d43c1 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt @@ -0,0 +1,48 @@ +package org.fbme.ide.richediting.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAware +import jetbrains.mps.extapi.persistence.ModelFactoryService +import jetbrains.mps.ide.actions.MPSCommonDataKeys +import jetbrains.mps.ide.dialogs.project.creation.ModelCreateHelper +import jetbrains.mps.project.AbstractModule +import jetbrains.mps.project.MPSProject +import org.fbme.ide.richediting.utils.ExtendedAdapterUtils +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.jetbrains.mps.openapi.model.SModelName + +class RevealExtendedAdapterAction : AnAction(), DumbAware { + override fun update(event: AnActionEvent) = event.executeReadAction { + val repository = event.repository + val node = event.getData(MPSCommonDataKeys.NODE) + val adapterTypeDeclaration = node?.let { + repository.adapterOrNull(node) + } + event.presentation.isEnabledAndVisible = adapterTypeDeclaration != null + } + + override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { + val repository = event.repository + val factory = repository.iec61499Factory + val node = event.getData(MPSCommonDataKeys.NODE) + val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) + val project: MPSProject = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) + + val extendedAdapter = node?.let { + repository.adapterOrNull(node) + } ?: return@executeWriteActionInEditor + val extendedAdapterUtils = ExtendedAdapterUtils(factory, repository.stFactory, project) + val modelCopy = ModelCreateHelper( + project, + model.module as AbstractModule, + SModelName("${model.name}_clone"), + model.modelRoot, + project.getComponent(ModelFactoryService::class.java).factoryTypes.first(), + ) + .setClone(model, false) + .createModel() + extendedAdapterUtils.revealAdapter(extendedAdapter, modelCopy) + } + +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt index 72fee548a..8a34da0a3 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt @@ -8,6 +8,7 @@ import jetbrains.mps.nodeEditor.cells.EditorCell_Collection import jetbrains.mps.openapi.editor.EditorContext import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory import org.fbme.ide.richediting.adapters.fbnetwork.fb.DiagramColors +import org.fbme.ide.richediting.utils.ExtendedAdapterUtils import org.fbme.ide.richediting.viewmodel.NetworkConnectionView import org.fbme.lib.iec61499.fbnetwork.ConnectionPath import org.fbme.lib.iec61499.fbnetwork.EntryKind @@ -29,6 +30,7 @@ class FBConnectionController( view: NetworkConnectionView, private val sourceConnectionNumber: Int?, ) : ConnectionController { + private val isExtendedAdapter: Boolean private val kind: EntryKind private val isEditable: Boolean private val fakeCell: EditorCell_Collection @@ -50,7 +52,11 @@ class FBConnectionController( g.color = highlightColor painter.paint(g, false) } - g.color = DiagramColors.getColorFor(kind, isEditable) + g.color = if (isExtendedAdapter) { + DiagramColors.EXTENDED_ADAPTER + } else { + DiagramColors.getColorFor(kind, isEditable) + } if (selected) { FBConnectionPathPainter.setupSelectedPathPaint(g, scale(1).toFloat()) } else { @@ -470,6 +476,7 @@ class FBConnectionController( kind = view.kind isEditable = view.isEditable val associatedNode = view.associatedNode + isExtendedAdapter = view.connection?.let { ExtendedAdapterUtils.isExtendedAdapterConnection(it) } ?: false fakeCell = FakeCells.createCollection(context, associatedNode) val connectionPaths: Iterator = SNodeOperations.ofConcept(SNodeOperations.getChildren(associatedNode), CONCEPTS.`ConnectionPath$IA`) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/fb/DiagramColors.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/fb/DiagramColors.kt index 834e16790..7c3270a00 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/fb/DiagramColors.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/fb/DiagramColors.kt @@ -20,6 +20,9 @@ object DiagramColors { @JvmField val ADAPTER = Color(0xEE8822) + @JvmField + val EXTENDED_ADAPTER: Color = Color.RED + @JvmField val EVENT_RO = Color(0x88AACC) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/RichAdapterNetworkProjectionController.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/RichAdapterNetworkProjectionController.kt new file mode 100644 index 000000000..725fb4092 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/RichAdapterNetworkProjectionController.kt @@ -0,0 +1,57 @@ +package org.fbme.ide.richediting.editor + +import jetbrains.mps.project.Project +import org.fbme.ide.iec61499.fbnetwork.MPSNetworkInstanceReference +import org.fbme.ide.iec61499.repository.PlatformRepository +import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider +import org.fbme.ide.platform.editor.EditorProjection +import org.fbme.ide.platform.editor.EditorProjectionController +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.fbme.lib.iec61499.instances.NetworkInstance +import org.jdom.Element +import org.jetbrains.mps.openapi.model.SNode + +class RichAdapterNetworkProjectionController( + private val node: SNode, + private val project: Project, + left: Boolean, + override val id: String, +) : EditorProjectionController { + private val repository: PlatformRepository = PlatformRepositoryProvider.getInstance(project) + private val adapterType: ExtendedAdapterTypeDeclaration = repository.adapter(node) + private val networkDeclaration: AdapterNetworkDeclaration? = + if (left) adapterType.leftNetwork else adapterType.rightNetwork + + override val priority: Int get() = 1 + + override fun createProjection(name: String): EditorProjection { + return if (name == id) { + createProjectionInternal(name) + } else { + throw IllegalArgumentException("Unsupported projection") + } + } + + override fun restoreProjection(name: String, e: Element): EditorProjection { + if (name == id) { + val ref = e.getAttributeValue(NetworkInstanceEditorProjection.PERSISTENCE_KEY) + val declaration = networkDeclaration + val instance = when { + ref != null -> MPSNetworkInstanceReference.deserialize(ref).resolve(repository) + declaration != null -> NetworkInstance.createForDeclaration(declaration) + else -> error("Declaration is null") + } + return NetworkInstanceEditorProjection(node, this, name, instance!!, project) + } else { + throw IllegalArgumentException("Unsupported projection") + } + } + + private fun createProjectionInternal(name: String): NetworkInstanceEditorProjection { + val instance = NetworkInstance.createForDeclaration( + networkDeclaration ?: error("Declaration is null") + ) + return NetworkInstanceEditorProjection(node, this, name, instance, project) + } +} diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterLeftNetworkControllerProvider.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterLeftNetworkControllerProvider.kt new file mode 100644 index 000000000..8df66a2f1 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterLeftNetworkControllerProvider.kt @@ -0,0 +1,23 @@ +package org.fbme.ide.richediting.editor.providers + +import jetbrains.mps.project.Project +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.ide.platform.editor.EditorProjectionController +import org.fbme.ide.platform.editor.EditorProjectionControllerProvider +import org.fbme.ide.richediting.editor.RichAdapterNetworkProjectionController +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + +class AdapterLeftNetworkControllerProvider: EditorProjectionControllerProvider("Left Network") { + override fun isApplicable(element: PlatformElement): Boolean { + return (element as? ExtendedAdapterTypeDeclaration)?.leftNetwork != null + } + + override fun create(element: PlatformElement, project: Project): EditorProjectionController { + return RichAdapterNetworkProjectionController( + element.node, + project, + left = true, + id = "Left Network", + ) + } +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterRightNetworkControllerProvider.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterRightNetworkControllerProvider.kt new file mode 100644 index 000000000..82dafc335 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/editor/providers/AdapterRightNetworkControllerProvider.kt @@ -0,0 +1,23 @@ +package org.fbme.ide.richediting.editor.providers + +import jetbrains.mps.project.Project +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.ide.platform.editor.EditorProjectionController +import org.fbme.ide.platform.editor.EditorProjectionControllerProvider +import org.fbme.ide.richediting.editor.RichAdapterNetworkProjectionController +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + +class AdapterRightNetworkControllerProvider: EditorProjectionControllerProvider("Left Network") { + override fun isApplicable(element: PlatformElement): Boolean { + return (element as? ExtendedAdapterTypeDeclaration)?.rightNetwork != null + } + + override fun create(element: PlatformElement, project: Project): EditorProjectionController { + return RichAdapterNetworkProjectionController( + element.node, + project, + left = false, + id = "Right Network", + ) + } +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt new file mode 100644 index 000000000..e9c68520f --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt @@ -0,0 +1,98 @@ +package org.fbme.ide.richediting.utils + +import org.fbme.lib.common.Identifier +import org.fbme.lib.common.StringIdentifier +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.EventDeclaration +import org.fbme.lib.iec61499.declarations.FBInterfaceDeclaration +import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor + +class AdapterGenerator( + private val factory: IEC61499Factory, +) { + + fun generateAdapterFromDeclaration( + declaration: FBInterfaceDeclaration, + name: String? = null, + identifier: Identifier? = null, + reversed: Boolean = false, + ): AdapterTypeDeclaration { + val adapterTypeDeclaration = factory.createAdapterTypeDeclaration( + identifier ?: name?.let { StringIdentifier(name) } + ) + if (name != null) { + adapterTypeDeclaration.name = name + } + copyPorts(adapterTypeDeclaration, declaration, reversed) + return adapterTypeDeclaration + } + + fun generateAdapterFromDescriptor( + fbTypeDescriptor: FBTypeDescriptor, + name: String? = null, + identifier: Identifier? = null, + reversed: Boolean = false, + ): AdapterTypeDeclaration { + val adapterTypeDeclaration = factory.createAdapterTypeDeclaration( + identifier ?: name?.let { StringIdentifier(name) } + ) + if (name != null) { + adapterTypeDeclaration.name = name + } + copyPorts(adapterTypeDeclaration, fbTypeDescriptor, reversed) + return adapterTypeDeclaration + } + + private fun copyPorts( + adapterTypeDeclaration: FBInterfaceDeclaration, + declaration: FBInterfaceDeclaration, + reversed: Boolean, + ) = copy( + reversed = reversed, + adapterTypeDeclaration = adapterTypeDeclaration, + outputEvents = declaration.outputEvents.map { it.copy() as EventDeclaration }, + inputEvents = declaration.inputEvents.map { it.copy() as EventDeclaration }, + parameterOutputs = declaration.outputParameters.map { it.copy() as ParameterDeclaration }, + parametersInputs = declaration.inputParameters.map { it.copy() as ParameterDeclaration }, + ) + + private fun copyPorts( + adapterTypeDeclaration: FBInterfaceDeclaration, + fbTypeDescriptor: FBTypeDescriptor, + reversed: Boolean, + ) = copy( + reversed = reversed, + adapterTypeDeclaration = adapterTypeDeclaration, + outputEvents = fbTypeDescriptor.eventOutputPorts + .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, + inputEvents = fbTypeDescriptor.eventInputPorts + .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, + parameterOutputs = fbTypeDescriptor.dataOutputPorts + .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) }, + parametersInputs = fbTypeDescriptor.dataInputPorts + .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) } + ) + + private fun copy( + reversed: Boolean, + adapterTypeDeclaration: FBInterfaceDeclaration, + outputEvents: List, + inputEvents: List, + parameterOutputs: List, + parametersInputs: List + ) { + if (reversed) { + adapterTypeDeclaration.inputEvents += outputEvents + adapterTypeDeclaration.outputEvents += inputEvents + adapterTypeDeclaration.inputParameters += parameterOutputs + adapterTypeDeclaration.outputParameters += parametersInputs + return + } + adapterTypeDeclaration.inputEvents += inputEvents + adapterTypeDeclaration.outputEvents += outputEvents + adapterTypeDeclaration.inputParameters += parametersInputs + adapterTypeDeclaration.outputParameters += parameterOutputs + } +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt new file mode 100644 index 000000000..2d81caa84 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt @@ -0,0 +1,388 @@ +package org.fbme.ide.richediting.utils + +import jetbrains.mps.project.MPSProject +import jetbrains.mps.smodel.SNodeUtil +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider +import org.fbme.lib.common.Declaration +import org.fbme.lib.common.Identifier +import org.fbme.lib.common.StringIdentifier +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.fbme.lib.iec61499.fbnetwork.* +import org.fbme.lib.st.STFactory +import org.jetbrains.mps.openapi.model.SModel + + +class ExtendedAdapterUtils( + private val factory: IEC61499Factory, + private val stFactory: STFactory, + private val project: MPSProject, +) { + private val switchGenerator = SwitchGenerator(factory, stFactory) + private val owner = PlatformRepositoryProvider.getInstance(project) + private val declarationsScope = owner.declarationsScope + + companion object { + fun isExtendedAdapterConnection(connection: FBNetworkConnection) = + getExtendedAdapterType(connection) != null + + fun getExtendedAdapterType(connection: FBNetworkConnection) = + getSourceAdapterDeclaration(connection) as? ExtendedAdapterTypeDeclaration + + fun getExtendedAdapterType(declaration: Declaration?) = + getSourceAdapterDeclaration(declaration) as? ExtendedAdapterTypeDeclaration + + fun getSourceAdapterDeclaration(connection: FBNetworkConnection) = + getSourceAdapterDeclaration(connection.sourceReference.getTarget()?.portTarget) + + fun getSourceAdapterDeclaration(declaration: Declaration?) = + (declaration as? PlugDeclaration)?.typeReference?.getTarget() + } + + data class RevealDeclarationsResult( + val extendedAdapter: ExtendedAdapterTypeDeclaration, + val leftAdapter: AdapterTypeDeclaration, + val middleAdapter: AdapterTypeDeclaration?, + val rightAdapter: AdapterTypeDeclaration?, + val leftBlockDeclaration: CompositeFBTypeDeclaration, + val rightBlockDeclaration: CompositeFBTypeDeclaration?, + val routers: MutableMap, + ) { + fun getFarRightAdapter(): AdapterTypeDeclaration = rightAdapter ?: middleAdapter ?: leftAdapter + } + + fun revealAdapter( + extendedAdapter: ExtendedAdapterTypeDeclaration, + currentModel: SModel, + ): RevealDeclarationsResult { + val revealDeclarations = revealDeclarations(extendedAdapter, currentModel) + val declarationsResults = listOf(revealDeclarations) + val identifiersToRevealResult = + declarationsResults.associateBy { it.extendedAdapter.identifier } + + revealAdapterInNetwork( + currentModel = currentModel, + modelToCheck = currentModel, + identifiersToRevealResult = identifiersToRevealResult, + ) + return revealDeclarations + } + + private fun revealAdapterInAllNetworks( + currentModel: SModel, + identifiersToRevealResult: Map + ) { + for (module in currentModel.repository.modules) { + for (model in module.models) { + revealAdapterInNetwork( + currentModel = currentModel, + modelToCheck = model, + identifiersToRevealResult = identifiersToRevealResult + ) + } + } + } + + private fun revealAdapterInNetwork( + currentModel: SModel, + modelToCheck: SModel, + identifiersToRevealResult: Map + ) { + val rootNodes = modelToCheck.rootNodes.toList() + for (node in rootNodes) { + val fbTypeDeclaration = owner.adapterOrNull(node) + if (fbTypeDeclaration != null) { + fbTypeDeclaration.sockets.forEach { + val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] + if (revealDeclarationsResult != null) { + it.typeReference.setTarget(revealDeclarationsResult.getFarRightAdapter()) + } + } + fbTypeDeclaration.plugs.forEach { + val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] + if (revealDeclarationsResult != null) { + it.typeReference.setTarget(revealDeclarationsResult.leftAdapter) + } + } + } + } + for (node in rootNodes) { + val declarationWithNetwork = owner.adapterOrNull(node) + if (declarationWithNetwork != null) { + val sourceIdentifiersToRevealResult = + identifiersToRevealResult.mapKeys { it.value.leftAdapter.identifier } + revealExtendedAdaptersInNetwork( + network = declarationWithNetwork.network, + identifiersToRevealResult = sourceIdentifiersToRevealResult, + model = currentModel, + ) + } + } + } + + private fun revealDeclarations( + extendedAdapter: ExtendedAdapterTypeDeclaration, + model: SModel, + ): RevealDeclarationsResult { + val name = extendedAdapter.name + val adapterGenerator = AdapterGenerator(factory) + val leftAdapter = adapterGenerator.generateAdapterFromDescriptor( + name = "Left_$name", + fbTypeDescriptor = extendedAdapter.plugTypeDescriptor, + reversed = true, + ) + val middleAdapter = adapterGenerator.generateAdapterFromDeclaration( + name = "Middle_$name", + identifier = extendedAdapter.identifier, + declaration = extendedAdapter, + ) + val rightAdapter = adapterGenerator.generateAdapterFromDescriptor( + name = "Right_$name", + fbTypeDescriptor = extendedAdapter.socketTypeDescriptor, + ) + + val block = createCompositeFB(factory, "InternalFB_$name", leftAdapter, rightAdapter, extendedAdapter.network) + // val leftBlock = createCompositeFB(factory, name, leftAdapter, middleAdapter, extendedAdapter.network) + // val rightBlock = createCompositeFB(factory, name, middleAdapter, rightAdapter, extendedAdapter.rightNetwork) + model.addRootNodes(leftAdapter, middleAdapter, rightAdapter, block, virtualPackage = "generated") + return RevealDeclarationsResult( + extendedAdapter = extendedAdapter, + leftAdapter = leftAdapter, + middleAdapter = middleAdapter, + rightAdapter = rightAdapter, + leftBlockDeclaration = block, + rightBlockDeclaration = null, + routers = mutableMapOf(), + ) + } + + private fun revealExtendedAdaptersInNetwork( + network: FBNetwork, + identifiersToRevealResult: Map, + model: SModel, + ) { + val adapterConnections = network.adapterConnections + .filter { getSourceAdapterDeclaration(it)?.identifier in identifiersToRevealResult } + .mapNotNull { connection -> (connection.sourceReference.getTarget())?.let { it to connection } } + .groupBy({ it.first }, { it.second }) + network.adapterConnections.removeIf { it.sourceReference.getTarget() in adapterConnections } + + for ((sourcePort, connections) in adapterConnections) { + val plugAdapterIdentifier = getSourceAdapterDeclaration(sourcePort.portTarget)?.identifier + val revealResult = identifiersToRevealResult[plugAdapterIdentifier] + checkNotNull(revealResult) + val adapterType = revealResult.extendedAdapter + if (connections.isEmpty()) { + continue + } + val routerDeclaration = if (connections.size == 1) { + null + } else { + revealResult.routers.computeIfAbsent(connections.size) { + val outputRouter = adapterType.outputRouter + ?: throw IllegalStateException("Adapter ${adapterType.name} doesn't have routing field") + switchGenerator.generateRouter( + name = "${adapterType.name}_${connections.size}", + model = model, + adapterTypeDeclaration = revealResult.leftAdapter, + outputsCount = connections.size, + routerName = outputRouter.name, + virtualPackage = "generated", + ) + } + } + // plugBlock -> leftBlock -> [router ->] [rightBlock ->] socketBlock + + val leftBlock = addFunctionalBlock(revealResult.leftBlockDeclaration, network) + network.adapterConnections += createConnection( + source = sourcePort, + target = leftBlock.getPort(leftBlock.type.socketPorts.first()), + entryKind = EntryKind.ADAPTER, + ) + + val blockBeforeRightBlock = if (routerDeclaration == null) { + leftBlock + } else { + val routerBlock = addFunctionalBlock(routerDeclaration, network) + network.adapterConnections += createConnection( + source = leftBlock.getPort(leftBlock.type.plugPorts.first()), + target = routerBlock.getPort(routerBlock.type.socketPorts.first()), + entryKind = EntryKind.ADAPTER, + ) + routerBlock + } + + for ((connection, connectionSourcePort) in connections + .zip(blockBeforeRightBlock.type.plugPorts.map { blockBeforeRightBlock.getPort(it) })) { + val portBeforeSocketBlock = if (revealResult.rightBlockDeclaration == null) { + connectionSourcePort + } else { + val addedBlock = addFunctionalBlock(revealResult.rightBlockDeclaration, network) + network.adapterConnections += createConnection( + source = connectionSourcePort, + target = addedBlock.getPort(addedBlock.type.socketPorts.first()), + entryKind = EntryKind.ADAPTER, + ) + addedBlock.getPort(addedBlock.type.plugPorts.first()) + } + + network.adapterConnections += createConnection( + source = portBeforeSocketBlock, + target = checkNotNull(connection.targetReference.getTarget()), + entryKind = EntryKind.ADAPTER, + ) + } + } + } + + private fun addBlockOrElseSource( + blockToAdd: CompositeFBTypeDeclaration?, + network: FBNetwork, + sourcePort: PortPath, + ) = if (blockToAdd != null) { + val addedBlock = addFunctionalBlock(blockToAdd, network) + network.adapterConnections += createConnection( + source = sourcePort, + target = addedBlock.getPort(addedBlock.type.socketPorts.first()), + entryKind = EntryKind.ADAPTER, + ) + addedBlock.getPort(addedBlock.type.plugPorts.first()) + } else { + sourcePort + } + + private fun addFunctionalBlock( + source: FunctionBlockDeclaration, + target: FunctionBlockDeclaration, + network: FBNetwork, + ) { + val outputEvents = source.typeReference.getTarget()?.outputEvents + if (outputEvents == null) { + return + } + for ((eventDeclaration, eventPort) in outputEvents.zip(source.type.eventOutputPorts)) { + val size = eventDeclaration.associations.size + if (size > 10) { + throw IllegalArgumentException("Size more than 10") + } + val identifier = StringIdentifier("PUBLISH_$size") + val typeDeclaration = declarationsScope.findServiceFBTypeDeclaration(identifier) + ?: throw IllegalStateException("${identifier.value} block not found") + val block = factory.createFunctionBlockDeclaration(identifier) + block.name = "${eventDeclaration.name}c" + block.typeReference.setTarget(typeDeclaration) + + createConnection( + source = source.getPort(eventPort), + target = block.getPort(typeDeclaration.typeDescriptor.eventInputPorts.first { it.name == "REQ" }), + entryKind = EntryKind.EVENT, + ) + for (i in eventDeclaration.associations.indices) { + val association = eventDeclaration.associations[i] + val parameter = association.parameterReference.getTarget() + checkNotNull(parameter) + createConnection( + source = source.getPort(source.type.dataOutputPorts.first { it.name == parameter.name }), + target = block.getPort(typeDeclaration.typeDescriptor.dataInputPorts.first { it.name == "SD_${i + 1}" }), + entryKind = EntryKind.DATA, + ) + } + network.functionBlocks += block + } + val inputEvents = source.typeReference.getTarget()?.inputEvents + if (inputEvents == null) { + return + } + for ((eventDeclaration, eventPort) in inputEvents.zip(source.type.eventInputPorts)) { + val size = eventDeclaration.associations.size + if (size > 10) { + throw IllegalArgumentException("Size more than 10") + } + val identifier = StringIdentifier("SUBSCRIBE_$size") + val typeDeclaration = declarationsScope.findServiceFBTypeDeclaration(identifier) + ?: throw IllegalStateException("${identifier.value} block not found") + val block = factory.createFunctionBlockDeclaration(identifier) + block.name = "${eventDeclaration.name}c" + block.typeReference.setTarget(typeDeclaration) + + createConnection( + source = source.getPort(eventPort), + target = block.getPort(typeDeclaration.typeDescriptor.eventInputPorts.first { it.name == "IND" }), + entryKind = EntryKind.EVENT, + ) + for (i in eventDeclaration.associations.indices) { + val association = eventDeclaration.associations[i] + val parameter = association.parameterReference.getTarget() + checkNotNull(parameter) + createConnection( + source = source.getPort(source.type.dataOutputPorts.first { it.name == parameter.name }), + target = block.getPort(typeDeclaration.typeDescriptor.dataInputPorts.first { it.name == "RD_${i + 1}" }), + entryKind = EntryKind.DATA, + ) + } + network.functionBlocks += block + } + } + + private fun addFunctionalBlock( + blockType: CompositeFBTypeDeclaration, + network: FBNetwork, + ): FunctionBlockDeclaration { + val block = factory.createFunctionBlockDeclaration(blockType.identifier) + block.name = blockType.name + block.typeReference.setTarget(blockType) + network.functionBlocks += block + return block + } + + private fun createConnection( + source: PortPath<*>, + target: PortPath<*>, + entryKind: EntryKind, + ): FBNetworkConnection { + val connection = factory.createFBNetworkConnection(entryKind) + connection.sourceReference.setTarget(source) + connection.targetReference.setTarget(target) + return connection + } + + private fun createCompositeFB( + factory: IEC61499Factory, + name: String, + leftAdapter: AdapterTypeDeclaration, + rightAdapter: AdapterTypeDeclaration, + network: FBNetwork, + ): CompositeFBTypeDeclaration { + val composite = factory.createCompositeFBTypeDeclaration(StringIdentifier(name)) + + val plug = factory.createPlugDeclaration(rightAdapter.identifier) + plug.typeReference.setTarget(rightAdapter) + plug.name = "Plug Connection" + composite.plugs += plug + + val socket = factory.createSocketDeclaration(leftAdapter.identifier) + socket.typeReference.setTarget(leftAdapter) + socket.name = "Socket Connection" + composite.sockets += socket + + // add port connections + composite.network.copyElements(network) + return composite + } + +} + +fun SModel.addRootNodes( + vararg declarations: Declaration, + virtualPackage: String? = null, +) { + for (declaration in declarations) { + val node = (declaration as PlatformElement).node + if (!virtualPackage.isNullOrEmpty()) { + node.setProperty(SNodeUtil.property_BaseConcept_virtualPackage, virtualPackage) + } + addRootNode(node) + } +} diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SwitchGenerator.kt similarity index 81% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt rename to code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SwitchGenerator.kt index 805ee9f45..2e874789e 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SwitchGenerator.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SwitchGenerator.kt @@ -1,7 +1,7 @@ -package org.fbme.ide.richediting.actions +package org.fbme.ide.richediting.utils -import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.lib.common.Declaration +import org.fbme.lib.common.Identifier import org.fbme.lib.common.StringIdentifier import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.* @@ -34,6 +34,51 @@ class SwitchGenerator( val target: FunctionBlockDeclarationBase?, ) + fun generateRouter( + name: String, + model: SModel, + adapterTypeDeclaration: AdapterTypeDeclaration, + outputsCount: Int, + routerName: String, + virtualPackage: String? = null, + ): CompositeFBTypeDeclaration { + val routerDeclaration = factory.createCompositeFBTypeDeclaration( + StringIdentifier("${name}_router") + ) + model.addRootNodes(routerDeclaration, virtualPackage = virtualPackage) + + val socket = factory.createSocketDeclaration(StringIdentifier("socket")) + socket.typeReference.setTarget(adapterTypeDeclaration) + routerDeclaration.sockets.add(socket) + + for (i in 0 until outputsCount) { + val plug = factory.createPlugDeclaration(StringIdentifier("plug_$i")) + plug.typeReference.setTarget(adapterTypeDeclaration) + routerDeclaration.plugs.add(plug) + } + addSocketPlugsSwitch( + adapterName = "${name}_leftSwitch", + model = model, + root = routerDeclaration, + socket = socket, + plugs = routerDeclaration.plugs, + socketToPlug = true, + routerName = routerName, + virtualPackage = virtualPackage, + ) + addSocketPlugsSwitch( + adapterName = "${name}_rightSwitch", + model = model, + root = routerDeclaration, + socket = socket, + plugs = routerDeclaration.plugs, + socketToPlug = false, + routerName = routerName, + virtualPackage = virtualPackage, + ) + return routerDeclaration + } + fun addSocketPlugsSwitch( adapterName: String, model: SModel, @@ -42,6 +87,7 @@ class SwitchGenerator( plugs: List, socketToPlug: Boolean, routerName: String, + virtualPackage: String? = null, ): FunctionBlockDeclaration { createdEvents.clear() createdParams.clear() @@ -87,7 +133,7 @@ class SwitchGenerator( createdParams.firstOrNull { it.sourceParam.name == routerName }?.sourceParam, ) - model.addRootNode((switchDeclaration as PlatformElement).node) + model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) root.network.functionBlocks.add(switchBlock) createdEvents.asSequence() .map { it.toNetworkConnection() } @@ -110,7 +156,7 @@ class SwitchGenerator( ) { val nameToParameterDeclaration = sourceParameters.associate { sourceDeclaration -> val newDeclaration = sourceDeclaration.copy(nameSuffix = nameSuffix) - sourceDeclaration.name to ParameterCopyAndConnectObject( + sourceDeclaration.identifier to ParameterCopyAndConnectObject( sourceParam = sourceDeclaration, createdParam = newDeclaration, input = input, @@ -132,12 +178,12 @@ class SwitchGenerator( target = targetBlockDeclaration, ) } - createdEvents.addAll(newEventDeclarations) - createdParams.addAll(nameToParameterDeclaration.values) + createdEvents += newEventDeclarations + createdParams += nameToParameterDeclaration.values val eventDeclarations = if (input) switchType.inputEvents else switchType.outputEvents - eventDeclarations.addAll(newEventDeclarations.map { it.createdEvent }) + eventDeclarations += newEventDeclarations.map { it.createdEvent } val paramsDeclarations = if (input) switchType.inputParameters else switchType.outputParameters - paramsDeclarations.addAll(nameToCreatedParameterDeclaration.values) + paramsDeclarations += nameToCreatedParameterDeclaration.values } private fun configureEcc( @@ -177,13 +223,15 @@ class SwitchGenerator( val state = factory.createStateDeclaration( StringIdentifier(outputEventDeclaration.name + "_state") ) + switchDeclaration.ecc.states += state val backTransition = factory.createStateTransition() + switchDeclaration.ecc.transitions += backTransition backTransition.sourceReference.setTarget(state) backTransition.targetReference.setTarget(start) val toNewStateTransition = factory.createStateTransition() toNewStateTransition.sourceReference.setTarget(start) toNewStateTransition.targetReference.setTarget(state) - toNewStateTransition.condition.eventReference.setFQName(inputEventDeclaration.identifier.toString()) + toNewStateTransition.condition.eventReference.setFQName(inputEventDeclaration.name) if (routeVariableDeclaration != null) { val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) @@ -195,6 +243,7 @@ class SwitchGenerator( equality.leftExpression = createVariable(routeVariableDeclaration) toNewStateTransition.condition.setGuardCondition(equality) } + switchDeclaration.ecc.transitions += toNewStateTransition val stateAction = factory.createStateAction() @@ -206,17 +255,16 @@ class SwitchGenerator( val assignment = stFactory.createAssignmentStatement() assignment.variable = createVariable(variable) assignment.expression = createVariable(assignable) - algorithmBody.statements.add(assignment) + algorithmBody.statements += assignment algorithmDeclaration.body = algorithmBody stateAction.algorithm.setTarget(algorithmDeclaration) } - stateAction.event.setFQName(outputEventDeclaration.identifier.toString()) + stateAction.event.setFQName(outputEventDeclaration.name) + + state.actions += stateAction + switchDeclaration.algorithms += algorithmDeclaration + - state.actions.add(stateAction) - switchDeclaration.algorithms.add(algorithmDeclaration) - switchDeclaration.ecc.states.add(state) - switchDeclaration.ecc.transitions.add(toNewStateTransition) - switchDeclaration.ecc.transitions.add(backTransition) } private fun createVariable(routeVariableDeclaration: VariableDeclaration): VariableReference { @@ -268,14 +316,14 @@ class SwitchGenerator( private fun EventDeclaration.copy( nameSuffix: String?, - nameToParameterDeclaration: Map + nameToParameterDeclaration: Map ): EventDeclaration { val declaration = factory.createEventDeclaration(identifier) declaration.name = name.withSuffix(nameSuffix) declaration.associations.addAll(associations.map { eventAssociation -> val association = factory.createEventAssociation() eventAssociation.parameterReference.getTarget() - ?.let { nameToParameterDeclaration[it.name] } + ?.let { nameToParameterDeclaration[it.identifier] } ?.run { association.parameterReference.setTarget(this) } association }) diff --git a/code/richediting/src/main/resources/META-INF/plugin.xml b/code/richediting/src/main/resources/META-INF/plugin.xml index 79321e4fa..00885315f 100644 --- a/code/richediting/src/main/resources/META-INF/plugin.xml +++ b/code/richediting/src/main/resources/META-INF/plugin.xml @@ -62,11 +62,18 @@ + + + - + + + + + + + + + @@ -93,7 +101,7 @@ - + @@ -111,11 +119,11 @@ - - - - - + + + + + @@ -148,7 +156,7 @@ - + @@ -295,12 +303,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -381,6 +430,22 @@ + + + + + + + + + + + + + + + + @@ -407,6 +472,19 @@ + + + + + + + + + + + + + @@ -417,6 +495,19 @@ + + + + + + + + + + + + + @@ -516,6 +607,22 @@ + + + + + + + + + + + + + + + + @@ -538,26 +645,26 @@ - + - + - + - + - + @@ -678,24 +785,24 @@ - - + + - - + + - - + + @@ -718,24 +825,24 @@ - - + + - - + + - - + + @@ -751,7 +858,7 @@ - + @@ -763,8 +870,8 @@ - - + + @@ -776,8 +883,8 @@ - - + + @@ -789,47 +896,91 @@ - - + + - - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - - - - + + + + - + - + - - + + - - - - + + + + - + - + - - + + @@ -2352,7 +2503,7 @@ - + @@ -2426,6 +2577,14 @@ + + + + + + + + @@ -2434,6 +2593,14 @@ + + + + + + + + @@ -2487,7 +2654,7 @@ - + @@ -2500,7 +2667,7 @@ - + @@ -2528,7 +2695,7 @@ - + @@ -2643,7 +2810,7 @@ - + @@ -2697,7 +2864,7 @@ - + @@ -2791,7 +2958,7 @@ - + @@ -2805,7 +2972,7 @@ - + @@ -2813,7 +2980,7 @@ - + @@ -2821,7 +2988,7 @@ - + @@ -2829,7 +2996,7 @@ - + @@ -2905,7 +3072,7 @@ - + @@ -2913,7 +3080,7 @@ - + @@ -2921,7 +3088,7 @@ - + @@ -2929,7 +3096,7 @@ - + @@ -2964,7 +3131,7 @@ - + @@ -2987,7 +3154,7 @@ - + @@ -2995,7 +3162,7 @@ - + @@ -3003,7 +3170,7 @@ - + @@ -3011,7 +3178,7 @@ - + @@ -3046,7 +3213,7 @@ - + @@ -3327,6 +3494,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3345,16 +3546,53 @@ - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3374,7 +3612,7 @@ - + @@ -3422,16 +3660,16 @@ - + - + - - + + @@ -3475,22 +3713,24 @@ - + + + - - + + - - + + @@ -3679,7 +3919,7 @@ - + @@ -3716,19 +3956,19 @@ - + - + - + - - + + @@ -3854,7 +4094,7 @@ - + @@ -4126,15 +4366,15 @@ - + - + - + @@ -4150,7 +4390,7 @@ - + @@ -4617,5 +4857,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0768ba66a627e96dcd1f8deaf0e4dab915d3b5ce Mon Sep 17 00:00:00 2001 From: klinachev Date: Mon, 15 Apr 2024 02:20:13 +0300 Subject: [PATCH 04/14] Fix adapter network --- .../org.fbme.ide.iec61499.lang.behavior.mps | 374 ++++++++++++++---- .../org.fbme.ide.iec61499.lang.editor.mps | 32 +- .../org.fbme.ide.iec61499.lang.structure.mps | 14 +- ...bme.ide.iec61499.adapter.interfacepart.mps | 221 ++++++++++- .../org.fbme.ide.iec61499.repository.mps | 127 ++++++ .../org/fbme/lib/iec61499/IEC61499Factory.kt | 2 + .../declarations/FBInterfaceDeclaration.kt | 15 + .../extention/AdapterNetworkDeclaration.kt | 84 +--- .../iec61499/descriptors/ExtendedPlugType.kt | 28 ++ .../descriptors/ExtendedSocketType.kt | 26 ++ .../iec61499/descriptors/FBTypeDescriptor.kt | 18 - .../descriptors/FBTypeDescriptorUtils.kt | 25 ++ .../iec61499/descriptors/InternalPlugType.kt | 57 +++ .../descriptors/InternalSocketType.kt | 56 +++ .../fbme/lib/iec61499/descriptors/PlugType.kt | 2 +- .../lib/iec61499/descriptors/SocketType.kt | 2 +- .../persistence/Iec61499ModelFactory.kt | 5 + .../org.fbme.ide.richediting.lang.editor.mps | 209 ++++------ .../ide/richediting/utils/AdapterGenerator.kt | 98 ----- ...Generator.kt => AdapterSwitchGenerator.kt} | 268 +++++++++---- .../utils/FBInterfaceDeclarationUtils.kt | 100 +++++ .../richediting/utils/IEC61499FactoryUtils.kt | 117 ++++++ .../ide/richediting/utils/STFactoryUtils.kt | 41 ++ 23 files changed, 1437 insertions(+), 484 deletions(-) create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedSocketType.kt create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalPlugType.kt create mode 100644 code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalSocketType.kt delete mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt rename code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/{SwitchGenerator.kt => AdapterSwitchGenerator.kt} (53%) create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps index d4e445f92..697067d12 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.behavior.mps @@ -943,6 +943,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -967,27 +988,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -1022,11 +1022,11 @@ - + - + @@ -1092,11 +1092,11 @@ - + - + @@ -1192,11 +1192,11 @@ - + - + @@ -1319,11 +1319,11 @@ - + - + @@ -1444,11 +1444,11 @@ - + - + @@ -1514,11 +1514,11 @@ - + - + @@ -1584,11 +1584,11 @@ - + - + @@ -1654,11 +1654,11 @@ - + - + @@ -5397,7 +5397,7 @@ - + @@ -5454,11 +5454,11 @@ - + - + @@ -5468,6 +5468,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5524,11 +5553,11 @@ - + - + @@ -5538,6 +5567,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5594,11 +5652,11 @@ - + - + @@ -5608,6 +5666,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5664,11 +5751,11 @@ - + - + @@ -5678,6 +5765,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5771,11 +5887,11 @@ - + - + @@ -5785,6 +5901,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5841,11 +5986,11 @@ - + - + @@ -5855,6 +6000,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5911,11 +6085,11 @@ - + - + @@ -5925,6 +6099,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5981,11 +6184,11 @@ - + - + @@ -5995,6 +6198,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6028,7 +6260,7 @@ - + @@ -6154,11 +6386,11 @@ - + - + @@ -6201,11 +6433,11 @@ - + - + @@ -6248,11 +6480,11 @@ - + - + @@ -6295,11 +6527,11 @@ - + - + @@ -6369,11 +6601,11 @@ - + - + @@ -6416,11 +6648,11 @@ - + - + @@ -6493,11 +6725,11 @@ - + - + @@ -6597,11 +6829,11 @@ - + - + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps index 91e6ef231..27c791e63 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps @@ -4451,12 +4451,18 @@ + + + + + + @@ -4473,10 +4479,13 @@ - + + + + @@ -4486,10 +4495,29 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps index 42cf34ec2..d926864cf 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps @@ -1614,14 +1614,14 @@ - + - + @@ -1653,13 +1653,19 @@ - + - + + + + + + + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps index d5b14595a..678968d96 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps @@ -7984,7 +7984,7 @@ - + @@ -8045,7 +8045,7 @@ - + @@ -8342,6 +8342,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8375,9 +8430,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -8398,7 +8508,7 @@ - + @@ -8414,7 +8524,7 @@ - + @@ -8458,7 +8568,7 @@ - + @@ -8470,7 +8580,7 @@ - + @@ -8491,7 +8601,7 @@ - + @@ -8507,7 +8617,7 @@ - + @@ -8551,7 +8661,7 @@ - + @@ -8561,6 +8671,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps index e4d2b9bb4..9684afd45 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps @@ -357,6 +357,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt index 14e450fc0..12b58a4e7 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt @@ -2,6 +2,7 @@ package org.fbme.lib.iec61499 import org.fbme.lib.common.Identifier import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.fbme.lib.iec61499.ecc.StateAction import org.fbme.lib.iec61499.ecc.StateDeclaration @@ -15,6 +16,7 @@ import org.fbme.lib.iec61499.fbnetwork.subapp.SubapplicationDeclaration interface IEC61499Factory { fun createAdapterTypeDeclaration(identifier: Identifier?): AdapterTypeDeclaration fun createExtendedAdapterTypeDeclaration(identifier: Identifier?): ExtendedAdapterTypeDeclaration + fun createAdapterNetworkDeclaration(identifier: Identifier?): AdapterNetworkDeclaration fun createAlgorithmDeclaration(identifier: Identifier?): AlgorithmDeclaration fun createAlgorithmBody(language: AlgorithmLanguage): BodyT fun createApplicationDeclaration(identifier: Identifier?): ApplicationDeclaration diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt index 255796b75..560a7ea42 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/FBInterfaceDeclaration.kt @@ -22,4 +22,19 @@ interface FBInterfaceDeclaration : Declaration { } throw IllegalArgumentException("Unknown declaration with FB interface: " + this.javaClass.name) } + + fun isEmpty(): Boolean = inputEvents.isEmpty() && + outputEvents.isEmpty() && + inputParameters.isEmpty() && + outputParameters.isEmpty() + + fun copyEvents(declaration: FBInterfaceDeclaration) { + inputEvents += declaration.inputEvents.map { it.copy() as EventDeclaration } + outputEvents += declaration.outputEvents.map { it.copy() as EventDeclaration } + } + + fun copyParameters(declaration: FBInterfaceDeclaration) { + inputParameters += declaration.inputParameters.map { it.copy() as ParameterDeclaration } + outputParameters += declaration.outputParameters.map { it.copy() as ParameterDeclaration } + } } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt index 38ca793ba..233f67d19 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt @@ -2,86 +2,17 @@ package org.fbme.lib.iec61499.declarations.extention import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.DeclarationWithNetwork -import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor -import org.fbme.lib.iec61499.descriptors.FBTypeDescriptorImpl +import org.fbme.lib.iec61499.descriptors.* import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider interface AdapterNetworkDeclaration : Declaration, CustomNetworkComponentProvider, DeclarationWithNetwork { override val container: ExtendedAdapterTypeDeclaration - fun internalFbPlugDescriptor(): FBTypeDescriptor { - val adapter = container - val extendPlug = networkType().extendPlug - return FBTypeDescriptorImpl( - typeName = this.name, - declaration = this, - eventInputPorts = adapter.outputEvents.asSequence() - .plus(adapter.takeIf { extendPlug } - ?.fbToPlugInterface?.outputEvents?.asSequence() - ?: sequenceOf() - ) - .toEventPortDescriptors(isInput = true) - .toList(), - eventOutputPorts = adapter.inputEvents.asSequence() - .plus(adapter.takeIf { extendPlug } - ?.fbToPlugInterface?.inputEvents?.asSequence() - ?: sequenceOf() - ) - .toEventPortDescriptors(isInput = false) - .toList(), - dataInputPorts = adapter.outputParameters.asSequence() - .plus(adapter.takeIf { extendPlug } - ?.fbToPlugInterface?.outputParameters?.asSequence() - ?: sequenceOf() - ) - .toParametersPortDescriptors(isInput = true) - .toList(), - dataOutputPorts = adapter.inputParameters.asSequence() - .plus(adapter.takeIf { extendPlug } - ?.fbToPlugInterface?.inputParameters?.asSequence() - ?: sequenceOf() - ) - .toParametersPortDescriptors(isInput = false) - .toList(), - ) - } + val internalFbPlugDescriptor: FBTypeDescriptor + get() = InternalPlugType(container, this) - fun internalFbSocketDescriptor(): FBTypeDescriptor { - val adapter = container - val extendSocket = networkType().extendSocket - return FBTypeDescriptorImpl( - typeName = this.name, - declaration = this, - eventInputPorts = adapter.inputEvents.asSequence() - .plus(adapter.takeIf { extendSocket } - ?.socketToFbInterface?.outputEvents?.asSequence() - ?: sequenceOf() - ) - .toEventPortDescriptors(isInput = true) - .toList(), - eventOutputPorts = adapter.outputEvents.asSequence() - .plus(adapter.takeIf { extendSocket } - ?.socketToFbInterface?.inputEvents?.asSequence() - ?: sequenceOf() - ) - .toEventPortDescriptors(isInput = false) - .toList(), - dataInputPorts = adapter.inputParameters.asSequence() - .plus(adapter.takeIf { extendSocket } - ?.socketToFbInterface?.outputParameters?.asSequence() - ?: sequenceOf() - ) - .toParametersPortDescriptors(isInput = true) - .toList(), - dataOutputPorts = adapter.outputParameters.asSequence() - .plus(adapter.takeIf { extendSocket } - ?.socketToFbInterface?.inputParameters?.asSequence() - ?: sequenceOf() - ) - .toParametersPortDescriptors(isInput = false) - .toList(), - ) - } + val internalFbSocketDescriptor: FBTypeDescriptor + get() = InternalSocketType(container, this) fun networkType(): NetworkType = if (container.leftNetwork == null || container.rightNetwork == null) { @@ -98,9 +29,8 @@ interface AdapterNetworkDeclaration : Declaration, CustomNetworkComponentProvide val extendSocket: Boolean, val extendPlug: Boolean, ) { - LEFT(false, true), - RIGHT(true, false), + LEFT(true, false), + RIGHT(false, true), SINGLE(true, true), } } - diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt new file mode 100644 index 000000000..e79844af9 --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt @@ -0,0 +1,28 @@ +package org.fbme.lib.iec61499.descriptors + +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + +class ExtendedPlugType(override val myDeclaration: ExtendedAdapterTypeDeclaration) : PlugType(myDeclaration) { + override val eventInputPorts: List + get() = myDeclaration.outputEvents.asSequence() + .plus(myDeclaration.internalFbSocketInterface?.outputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = true) + .toList() + override val eventOutputPorts: List + get() = myDeclaration.inputEvents.asSequence() + .plus(myDeclaration.internalFbSocketInterface?.inputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = false) + .toList() + override val dataInputPorts: List + get() = myDeclaration.outputParameters.asSequence() + .plus(myDeclaration.internalFbSocketInterface?.outputParameters?.asSequence() ?: sequenceOf()) + .run { myDeclaration.outputRouter?.let { plus(it) } ?: this } + .toParametersPortDescriptors(isInput = true) + .toList() + override val dataOutputPorts: List + get() = myDeclaration.inputParameters.asSequence() + .plus(myDeclaration.internalFbSocketInterface?.inputParameters?.asSequence() ?: sequenceOf()) + .run { myDeclaration.inputRouter?.let { plus(it) } ?: this } + .toParametersPortDescriptors(isInput = false) + .toList() +} diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedSocketType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedSocketType.kt new file mode 100644 index 000000000..6f86121b3 --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedSocketType.kt @@ -0,0 +1,26 @@ +package org.fbme.lib.iec61499.descriptors + +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + +class ExtendedSocketType(override val myDeclaration: ExtendedAdapterTypeDeclaration) : SocketType(myDeclaration) { + override val eventInputPorts: List + get() = myDeclaration.inputEvents.asSequence() + .plus(myDeclaration.internalFbPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = true) + .toList() + override val eventOutputPorts: List + get() = myDeclaration.outputEvents.asSequence() + .plus(myDeclaration.internalFbPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) + .toEventPortDescriptors(isInput = false) + .toList() + override val dataInputPorts: List + get() = myDeclaration.inputParameters.asSequence() + .plus(myDeclaration.internalFbPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = true) + .toList() + override val dataOutputPorts: List + get() = myDeclaration.outputParameters.asSequence() + .plus(myDeclaration.internalFbPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) + .toParametersPortDescriptors(isInput = false) + .toList() +} diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt index 1225e3964..9f5185a74 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptor.kt @@ -14,21 +14,3 @@ interface FBTypeDescriptor { fun getAssociatedVariablesForInputEvent(eventNumber: Int): List fun getAssociatedVariablesForOutputEvent(eventNumber: Int): List } - -data class FBTypeDescriptorImpl( - override val typeName: String, - override val declaration: Declaration?, - override val eventInputPorts: List = listOf(), - override val eventOutputPorts: List = listOf(), - override val dataInputPorts: List = listOf(), - override val dataOutputPorts: List = listOf(), - override val socketPorts: List = listOf(), - override val plugPorts: List = listOf(), -) : FBTypeDescriptor { - - override fun getAssociatedVariablesForInputEvent(eventNumber: Int): List = listOf() -// FBTypeDescriptorUtils.getAssociatedVariablesForInputEvent(myDeclaration, eventNumber) - - override fun getAssociatedVariablesForOutputEvent(eventNumber: Int): List = listOf() -// FBTypeDescriptorUtils.getAssociatedVariablesForOutputEvent(myDeclaration, eventNumber) -} \ No newline at end of file diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt index b18a481af..597e54852 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt @@ -1,7 +1,9 @@ package org.fbme.lib.iec61499.descriptors +import org.fbme.lib.iec61499.declarations.EventDeclaration import org.fbme.lib.iec61499.declarations.FBInterfaceDeclaration import org.fbme.lib.iec61499.declarations.FBInterfaceDeclarationWithAdapters +import org.fbme.lib.iec61499.declarations.ParameterDeclaration import org.fbme.lib.iec61499.fbnetwork.EntryKind internal object FBTypeDescriptorUtils { @@ -93,3 +95,26 @@ internal object FBTypeDescriptorUtils { return list } } + +fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> + FBPortDescriptor( + name = event.name, + connectionKind = EntryKind.EVENT, + position = index, + isInput = isInput, + isValid = true, + declaration = event, + ) +} + +fun Sequence.toParametersPortDescriptors(isInput: Boolean) = + mapIndexed { index, event -> + FBPortDescriptor( + name = event.name, + connectionKind = EntryKind.DATA, + position = index, + isInput = isInput, + isValid = true, + declaration = event, + ) + } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalPlugType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalPlugType.kt new file mode 100644 index 000000000..1248f916e --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalPlugType.kt @@ -0,0 +1,57 @@ +package org.fbme.lib.iec61499.descriptors + +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + +class InternalPlugType( + override val myDeclaration: ExtendedAdapterTypeDeclaration, + private val networkDeclaration: AdapterNetworkDeclaration, +) : PlugType(myDeclaration) { + override val eventInputPorts: List + get() = myDeclaration.outputEvents.asSequence() + .plus( + if (extendPlug()) { + myDeclaration.internalFbPlugInterface?.inputEvents + } else { + myDeclaration.internalNetworksInterface?.inputEvents + }?.asSequence() ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = true) + .toList() + + override val eventOutputPorts: List + get() = myDeclaration.inputEvents.asSequence() + .plus( + if (extendPlug()) { + myDeclaration.internalFbPlugInterface?.outputEvents + } else { + myDeclaration.internalNetworksInterface?.outputEvents + }?.asSequence() ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = false) + .toList() + override val dataInputPorts: List + get() = myDeclaration.outputParameters.asSequence() + .plus( + if (extendPlug()) { + myDeclaration.internalFbPlugInterface?.inputParameters + } else { + myDeclaration.internalNetworksInterface?.inputParameters + }?.asSequence() ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = true) + .toList() + override val dataOutputPorts: List + get() = myDeclaration.inputParameters.asSequence() + .plus( + if (extendPlug()) { + myDeclaration.internalFbPlugInterface?.outputParameters + } else { + myDeclaration.internalNetworksInterface?.outputParameters + }?.asSequence() ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = false) + .toList() + + private fun extendPlug() = networkDeclaration.networkType().extendPlug +} diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalSocketType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalSocketType.kt new file mode 100644 index 000000000..58d53236f --- /dev/null +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/InternalSocketType.kt @@ -0,0 +1,56 @@ +package org.fbme.lib.iec61499.descriptors + +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + +class InternalSocketType( + override val myDeclaration: ExtendedAdapterTypeDeclaration, + private val networkDeclaration: AdapterNetworkDeclaration, +) : SocketType(myDeclaration) { + override val eventInputPorts: List + get() = myDeclaration.inputEvents.asSequence() + .plus( + if (extendSocket()) { + myDeclaration.internalFbSocketInterface?.inputEvents + } else { + myDeclaration.internalNetworksInterface?.outputEvents + }?.asSequence() ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = true) + .toList() + override val eventOutputPorts: List + get() = myDeclaration.outputEvents.asSequence() + .plus( + if (extendSocket()) { + myDeclaration.internalFbSocketInterface?.outputEvents + } else { + myDeclaration.internalNetworksInterface?.inputEvents + }?.asSequence() ?: sequenceOf() + ) + .toEventPortDescriptors(isInput = false) + .toList() + override val dataInputPorts: List + get() = myDeclaration.inputParameters.asSequence() + .plus( + if (extendSocket()) { + myDeclaration.internalFbSocketInterface?.inputParameters + } else { + myDeclaration.internalNetworksInterface?.outputParameters + }?.asSequence() ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = true) + .toList() + override val dataOutputPorts: List + get() = myDeclaration.outputParameters.asSequence() + .plus( + if (extendSocket()) { + myDeclaration.internalFbSocketInterface?.outputParameters + } else { + myDeclaration.internalNetworksInterface?.inputParameters + }?.asSequence() ?: sequenceOf() + ) + .toParametersPortDescriptors(isInput = false) + .toList() + + private fun extendSocket() = networkDeclaration.networkType().extendSocket +} diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/PlugType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/PlugType.kt index 5a22afb0a..1ebe97ca6 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/PlugType.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/PlugType.kt @@ -3,7 +3,7 @@ package org.fbme.lib.iec61499.descriptors import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration -class PlugType(private val myDeclaration: AdapterTypeDeclaration) : FBTypeDescriptor { +open class PlugType(protected open val myDeclaration: AdapterTypeDeclaration) : FBTypeDescriptor { override val typeName: String get() = myDeclaration.name override val declaration: Declaration diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/SocketType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/SocketType.kt index c74bb4f87..50e2ecaa1 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/SocketType.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/SocketType.kt @@ -3,7 +3,7 @@ package org.fbme.lib.iec61499.descriptors import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration -class SocketType(private val myDeclaration: AdapterTypeDeclaration) : FBTypeDescriptor { +open class SocketType(protected open val myDeclaration: AdapterTypeDeclaration) : FBTypeDescriptor { override val declaration: Declaration get() = myDeclaration override val typeName: String diff --git a/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt b/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt index 02e5e0ebc..3bd71abf5 100644 --- a/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt +++ b/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt @@ -27,6 +27,7 @@ import org.fbme.ide.platform.converter.PlatformConverter.create import org.fbme.lib.common.Declaration import org.fbme.lib.common.RootElement import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.fbme.lib.iec61499.stringify.RootDeclarationPrinter import org.jdom.Document import org.jetbrains.mps.openapi.model.SModel @@ -48,6 +49,7 @@ import java.util.stream.Collectors class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory { private fun supportedFileExtension(fileExt: String): Boolean { return fileExt == FBT_FILE_EXT + || fileExt == EXT_ADP_FILE_EXT || fileExt == ADP_FILE_EXT || fileExt == SUB_FILE_EXT || fileExt == RES_FILE_EXT @@ -306,6 +308,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory { const val FBT_FILE_EXT = "fbt" const val ADP_FILE_EXT = "adp" + const val EXT_ADP_FILE_EXT = "eadp" const val SUB_FILE_EXT = "app" const val RES_FILE_EXT = "res" const val DEV_FILE_EXT = "dev" @@ -338,6 +341,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory { val converter = create(owner, reference, doc) return when (fileExtension) { FBT_FILE_EXT -> (converter.convertFBType() as PlatformElement).node + EXT_ADP_FILE_EXT -> (converter.convertExtendedAdapterType() as PlatformElement).node ADP_FILE_EXT -> (converter.convertAdapterType() as PlatformElement).node SUB_FILE_EXT -> (converter.convertSubapplicationType() as PlatformElement).node RES_FILE_EXT -> (converter.convertResourceType() as PlatformElement).node @@ -352,6 +356,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory { val element = owner.getAdapter(node, RootElement::class.java) return when (element) { is FBTypeDeclaration -> FBT_FILE_EXT + is ExtendedAdapterTypeDeclaration -> EXT_ADP_FILE_EXT is AdapterTypeDeclaration -> ADP_FILE_EXT is SubapplicationTypeDeclaration -> SUB_FILE_EXT is ResourceTypeDeclaration -> RES_FILE_EXT diff --git a/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps b/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps index c81353cb6..12556403f 100644 --- a/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps +++ b/code/richediting/languages/org.fbme.ide.richediting.lang/models/org.fbme.ide.richediting.lang.editor.mps @@ -3114,61 +3114,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3353,32 +3298,21 @@ - - + - - - - - + - + - - - - - - @@ -3388,7 +3322,7 @@ - + @@ -3402,112 +3336,144 @@ - + + + + + + + + + + + + + + + + + - + - - + + - - - - - - - + + + + + + + - - + + - - + + - + - + - - - + + + - - + + - - - + + + - + - + - + - + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - + + - - + + - + - + - - - + + + - - + + - - + + @@ -3515,16 +3481,13 @@ - + - - - diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt deleted file mode 100644 index e9c68520f..000000000 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterGenerator.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.fbme.ide.richediting.utils - -import org.fbme.lib.common.Identifier -import org.fbme.lib.common.StringIdentifier -import org.fbme.lib.iec61499.IEC61499Factory -import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration -import org.fbme.lib.iec61499.declarations.EventDeclaration -import org.fbme.lib.iec61499.declarations.FBInterfaceDeclaration -import org.fbme.lib.iec61499.declarations.ParameterDeclaration -import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor - -class AdapterGenerator( - private val factory: IEC61499Factory, -) { - - fun generateAdapterFromDeclaration( - declaration: FBInterfaceDeclaration, - name: String? = null, - identifier: Identifier? = null, - reversed: Boolean = false, - ): AdapterTypeDeclaration { - val adapterTypeDeclaration = factory.createAdapterTypeDeclaration( - identifier ?: name?.let { StringIdentifier(name) } - ) - if (name != null) { - adapterTypeDeclaration.name = name - } - copyPorts(adapterTypeDeclaration, declaration, reversed) - return adapterTypeDeclaration - } - - fun generateAdapterFromDescriptor( - fbTypeDescriptor: FBTypeDescriptor, - name: String? = null, - identifier: Identifier? = null, - reversed: Boolean = false, - ): AdapterTypeDeclaration { - val adapterTypeDeclaration = factory.createAdapterTypeDeclaration( - identifier ?: name?.let { StringIdentifier(name) } - ) - if (name != null) { - adapterTypeDeclaration.name = name - } - copyPorts(adapterTypeDeclaration, fbTypeDescriptor, reversed) - return adapterTypeDeclaration - } - - private fun copyPorts( - adapterTypeDeclaration: FBInterfaceDeclaration, - declaration: FBInterfaceDeclaration, - reversed: Boolean, - ) = copy( - reversed = reversed, - adapterTypeDeclaration = adapterTypeDeclaration, - outputEvents = declaration.outputEvents.map { it.copy() as EventDeclaration }, - inputEvents = declaration.inputEvents.map { it.copy() as EventDeclaration }, - parameterOutputs = declaration.outputParameters.map { it.copy() as ParameterDeclaration }, - parametersInputs = declaration.inputParameters.map { it.copy() as ParameterDeclaration }, - ) - - private fun copyPorts( - adapterTypeDeclaration: FBInterfaceDeclaration, - fbTypeDescriptor: FBTypeDescriptor, - reversed: Boolean, - ) = copy( - reversed = reversed, - adapterTypeDeclaration = adapterTypeDeclaration, - outputEvents = fbTypeDescriptor.eventOutputPorts - .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, - inputEvents = fbTypeDescriptor.eventInputPorts - .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, - parameterOutputs = fbTypeDescriptor.dataOutputPorts - .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) }, - parametersInputs = fbTypeDescriptor.dataInputPorts - .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) } - ) - - private fun copy( - reversed: Boolean, - adapterTypeDeclaration: FBInterfaceDeclaration, - outputEvents: List, - inputEvents: List, - parameterOutputs: List, - parametersInputs: List - ) { - if (reversed) { - adapterTypeDeclaration.inputEvents += outputEvents - adapterTypeDeclaration.outputEvents += inputEvents - adapterTypeDeclaration.inputParameters += parameterOutputs - adapterTypeDeclaration.outputParameters += parametersInputs - return - } - adapterTypeDeclaration.inputEvents += inputEvents - adapterTypeDeclaration.outputEvents += outputEvents - adapterTypeDeclaration.inputParameters += parametersInputs - adapterTypeDeclaration.outputParameters += parameterOutputs - } -} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SwitchGenerator.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterSwitchGenerator.kt similarity index 53% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SwitchGenerator.kt rename to code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterSwitchGenerator.kt index 2e874789e..54a8bf1e6 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SwitchGenerator.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterSwitchGenerator.kt @@ -1,20 +1,25 @@ package org.fbme.ide.richediting.utils -import org.fbme.lib.common.Declaration import org.fbme.lib.common.Identifier import org.fbme.lib.common.StringIdentifier import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.ecc.StateAction import org.fbme.lib.iec61499.ecc.StateDeclaration -import org.fbme.lib.iec61499.fbnetwork.* +import org.fbme.lib.iec61499.fbnetwork.EntryKind +import org.fbme.lib.iec61499.fbnetwork.FBNetwork +import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration +import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase import org.fbme.lib.st.STFactory -import org.fbme.lib.st.expressions.* +import org.fbme.lib.st.expressions.VariableDeclaration import org.jetbrains.mps.openapi.model.SModel -class SwitchGenerator( +class AdapterSwitchGenerator( private val factory: IEC61499Factory, - private val stFactory: STFactory, + stFactory: STFactory, ) { + private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) + private val stFactoryUtils: STFactoryUtils = STFactoryUtils(stFactory) private val createdEvents = mutableListOf() private val createdParams = mutableListOf() @@ -37,9 +42,11 @@ class SwitchGenerator( fun generateRouter( name: String, model: SModel, - adapterTypeDeclaration: AdapterTypeDeclaration, + socketAdapterTypeDeclaration: AdapterTypeDeclaration, outputsCount: Int, - routerName: String, + outputRouterName: String, + plugAdapterTypeDeclaration: AdapterTypeDeclaration? = null, + inputRouterName: String? = null, virtualPackage: String? = null, ): CompositeFBTypeDeclaration { val routerDeclaration = factory.createCompositeFBTypeDeclaration( @@ -48,38 +55,164 @@ class SwitchGenerator( model.addRootNodes(routerDeclaration, virtualPackage = virtualPackage) val socket = factory.createSocketDeclaration(StringIdentifier("socket")) - socket.typeReference.setTarget(adapterTypeDeclaration) - routerDeclaration.sockets.add(socket) + socket.typeReference.setTarget(socketAdapterTypeDeclaration) + routerDeclaration.sockets += socket for (i in 0 until outputsCount) { val plug = factory.createPlugDeclaration(StringIdentifier("plug_$i")) - plug.typeReference.setTarget(adapterTypeDeclaration) - routerDeclaration.plugs.add(plug) + plug.typeReference.setTarget(plugAdapterTypeDeclaration ?: socketAdapterTypeDeclaration) + routerDeclaration.plugs += plug } - addSocketPlugsSwitch( + addSocketToPlugsSwitch( adapterName = "${name}_leftSwitch", model = model, - root = routerDeclaration, - socket = socket, - plugs = routerDeclaration.plugs, - socketToPlug = true, - routerName = routerName, + network = routerDeclaration.network, + source = socket, + targets = routerDeclaration.plugs, + routerName = outputRouterName, virtualPackage = virtualPackage, ) - addSocketPlugsSwitch( + addPlugsToSocketSwitch( adapterName = "${name}_rightSwitch", model = model, - root = routerDeclaration, - socket = socket, - plugs = routerDeclaration.plugs, - socketToPlug = false, - routerName = routerName, + network = routerDeclaration.network, + sources = routerDeclaration.plugs, + target = socket, + routerName = inputRouterName, virtualPackage = virtualPackage, ) return routerDeclaration } - fun addSocketPlugsSwitch( + private fun addSocketToPlugsSwitch( + adapterName: String, + model: SModel, + network: FBNetwork, + source: FunctionBlockDeclarationBase, + targets: List, + routerName: String, + virtualPackage: String? = null, + ): FunctionBlockDeclaration { + val switchFBIdentifier = StringIdentifier(adapterName) + val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) + model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) + val switchBlock = factoryUtils.addFunctionalBlock(switchDeclaration, network) + val inputParameters = factoryUtils.copyParametersAndConnect( + destination = switchDeclaration.inputParameters, + destinationBlock = switchBlock, + source = source.type.dataOutputPorts.map { it.declaration as ParameterDeclaration }, + sourceBlock = source, + network = network, + ).map { it.second } + val routerParameterDeclaration = inputParameters.first { it.name == routerName } + val inputEvents = factoryUtils.copyEventsAndConnect( + destination = switchDeclaration.inputEvents, + destinationBlock = switchBlock, + source = source.type.eventOutputPorts.map { it.declaration as EventDeclaration }, + sourceBlock = source, + network = network, + ).associateBy { it.second.name } + val startState = factory.createStateDeclaration(StringIdentifier("Start")) + switchDeclaration.ecc.states += startState + for ((i, plug) in targets.withIndex()) { + val parametersAndCopies = factoryUtils.copyParametersAndConnect( + destination = switchDeclaration.outputParameters, + destinationBlock = switchBlock, + source = plug.type.dataInputPorts.map { it.declaration as ParameterDeclaration }, + sourceBlock = plug, + network = network, + outputToInput = false, + ) + parametersAndCopies.forEach { it.second.name += "_$i" } + factoryUtils.copyEventsAndConnect( + destination = switchDeclaration.outputEvents, + destinationBlock = switchBlock, + source = plug.type.eventInputPorts.map { it.declaration as EventDeclaration }, + sourceBlock = plug, + network = network, + outputToInput = false, + ).forEach { (sourceEvent, createdEvent) -> + createdEvent.name += "_$i" + addState( + switchDeclaration = switchDeclaration, + start = startState, + inputEventDeclaration = checkNotNull(inputEvents[sourceEvent.name]?.second), + outputEventDeclaration = createdEvent, + assignableToVariableParameters = inputParameters.zip(parametersAndCopies.map { it.second }), + number = i, + outputRouteVariable = routerParameterDeclaration, + inputRouteVariable = null, + ) + } + } + return switchBlock + } + + private fun addPlugsToSocketSwitch( + adapterName: String, + model: SModel, + network: FBNetwork, + sources: List, + target: FunctionBlockDeclarationBase, + routerName: String?, + virtualPackage: String? = null, + ): FunctionBlockDeclaration { + val switchFBIdentifier = StringIdentifier(adapterName) + val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) + model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) + val switchBlock = factoryUtils.addFunctionalBlock(switchDeclaration, network) + val outputParameters = factoryUtils.copyParametersAndConnect( + destination = switchDeclaration.outputParameters, + destinationBlock = switchBlock, + source = target.type.dataInputPorts.map { it.declaration as ParameterDeclaration }, + sourceBlock = target, + network = network, + outputToInput = false, + ).map { it.second } + val outputEvents = factoryUtils.copyEventsAndConnect( + destination = switchDeclaration.outputEvents, + destinationBlock = switchBlock, + source = target.type.eventInputPorts.map { it.declaration as EventDeclaration }, + sourceBlock = target, + network = network, + outputToInput = false, + ).associateBy { it.second.name } + val routerParameterDeclaration = outputParameters.first { it.name == routerName } + val startState = factory.createStateDeclaration(StringIdentifier("Start")) + switchDeclaration.ecc.states += startState + for ((i, plug) in sources.withIndex()) { + val parametersAndCopies = factoryUtils.copyParametersAndConnect( + destination = switchDeclaration.inputParameters, + destinationBlock = switchBlock, + source = plug.type.dataOutputPorts.map { it.declaration as ParameterDeclaration }, + sourceBlock = plug, + network = network, + ) + parametersAndCopies.forEach { it.second.name += "_$i" } + factoryUtils.copyEventsAndConnect( + destination = switchDeclaration.inputEvents, + destinationBlock = switchBlock, + source = plug.type.eventOutputPorts.map { it.declaration as EventDeclaration }, + sourceBlock = plug, + network = network, + ).forEach { (sourceEvent, createdEvent) -> + createdEvent.name += "_$i" + addState( + switchDeclaration = switchDeclaration, + start = startState, + inputEventDeclaration = createdEvent, + outputEventDeclaration = checkNotNull(outputEvents[sourceEvent.name]?.second), + assignableToVariableParameters = parametersAndCopies.map { it.second }.zip(outputParameters), + number = i, + outputRouteVariable = null, + inputRouteVariable = routerParameterDeclaration, + ) + } + } + return switchBlock + } + + private fun addSocketPlugsSwitch( adapterName: String, model: SModel, root: CompositeFBTypeDeclaration, @@ -94,8 +227,7 @@ class SwitchGenerator( val switchFBIdentifier = StringIdentifier(adapterName) val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) - val switchBlock = factory.createFunctionBlockDeclaration(switchFBIdentifier) - switchBlock.typeReference.setTarget(switchDeclaration) + val switchBlock = factoryUtils.addFunctionalBlock(switchDeclaration, root.network) val socketAdapterType = checkNotNull(socket.typeReference.getTarget()) val eventsToCopy = if (socketToPlug) socketAdapterType.outputEvents else socketAdapterType.inputEvents @@ -134,7 +266,6 @@ class SwitchGenerator( ) model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) - root.network.functionBlocks.add(switchBlock) createdEvents.asSequence() .map { it.toNetworkConnection() } .toCollection(root.network.eventConnections) @@ -193,7 +324,7 @@ class SwitchGenerator( routerParameterDeclaration: ParameterDeclaration?, ) { val startState = factory.createStateDeclaration(StringIdentifier("Start")) - switchDeclaration.ecc.states.add(startState) + switchDeclaration.ecc.states += startState for (i in eventDeclarations.indices) { val (inputEvent, outputEvents) = eventDeclarations[i] for (j in outputEvents.indices) { @@ -203,9 +334,10 @@ class SwitchGenerator( start = startState, inputEventDeclaration = inputEvent, outputEventDeclaration = outputEvent, - parameterDeclarations = parameterDeclarations, + assignableToVariableParameters = parameterDeclarations, number = j, - routeVariableDeclaration = routerParameterDeclaration, + outputRouteVariable = routerParameterDeclaration, + inputRouteVariable = null, ) } } @@ -216,10 +348,11 @@ class SwitchGenerator( start: StateDeclaration, inputEventDeclaration: EventDeclaration, outputEventDeclaration: EventDeclaration, - parameterDeclarations: List>, + assignableToVariableParameters: List>, number: Int, - routeVariableDeclaration: VariableDeclaration?, - ) { + outputRouteVariable: VariableDeclaration?, + inputRouteVariable: VariableDeclaration?, + ): StateAction { val state = factory.createStateDeclaration( StringIdentifier(outputEventDeclaration.name + "_state") ) @@ -232,16 +365,10 @@ class SwitchGenerator( toNewStateTransition.sourceReference.setTarget(start) toNewStateTransition.targetReference.setTarget(state) toNewStateTransition.condition.eventReference.setFQName(inputEventDeclaration.name) - if (routeVariableDeclaration != null) { - val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) - - val numberLiteral = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal - - numberLiteral.value = number - equality.rightExpression = numberLiteral - - equality.leftExpression = createVariable(routeVariableDeclaration) - toNewStateTransition.condition.setGuardCondition(equality) + if (outputRouteVariable != null) { + toNewStateTransition.condition.setGuardCondition( + stFactoryUtils.intEquality(outputRouteVariable, number) + ) } switchDeclaration.ecc.transitions += toNewStateTransition @@ -251,67 +378,50 @@ class SwitchGenerator( StringIdentifier(outputEventDeclaration.name + "_algorithm") ) val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST) - for ((assignable, variable) in parameterDeclarations) { - val assignment = stFactory.createAssignmentStatement() - assignment.variable = createVariable(variable) - assignment.expression = createVariable(assignable) - algorithmBody.statements += assignment - algorithmDeclaration.body = algorithmBody - stateAction.algorithm.setTarget(algorithmDeclaration) + for ((assignable, variable) in assignableToVariableParameters) { + algorithmBody.statements += stFactoryUtils.createAssign(variable, stFactoryUtils.createVariable(assignable)) + } + if (inputRouteVariable != null) { + algorithmBody.statements += stFactoryUtils.createAssign( + variable = inputRouteVariable, + assignable = stFactoryUtils.createIntLiteral(number), + ) } + algorithmDeclaration.body = algorithmBody + stateAction.algorithm.setTarget(algorithmDeclaration) stateAction.event.setFQName(outputEventDeclaration.name) state.actions += stateAction switchDeclaration.algorithms += algorithmDeclaration - - - } - - private fun createVariable(routeVariableDeclaration: VariableDeclaration): VariableReference { - val variableReference = stFactory.createVariableReference() - variableReference.reference.setTarget(routeVariableDeclaration) - return variableReference - } - - private fun createNetworkConnection( - kind: EntryKind, - source: FunctionBlockDeclarationBase?, - sourcePortTarget: Declaration, - target: FunctionBlockDeclarationBase?, - targetPortTarget: Declaration, - ): FBNetworkConnection { - val connection = factory.createFBNetworkConnection(kind) - connection.sourceReference.setTarget(PortPath.createPortPath(source, kind, sourcePortTarget)) - connection.targetReference.setTarget(PortPath.createPortPath(target, kind, targetPortTarget)) - return connection + return stateAction } - private fun EventCopyAndConnectObject.toNetworkConnection() = if (input) createNetworkConnection( + private fun EventCopyAndConnectObject.toNetworkConnection() = if (input) factoryUtils.createNetworkConnection( kind = EntryKind.EVENT, source = source, sourcePortTarget = sourceEvent, target = target, - targetPortTarget = createdEvent, - ) else createNetworkConnection( + targetPortTarget = createdEvent + ) else factoryUtils.createNetworkConnection( kind = EntryKind.EVENT, source = target, sourcePortTarget = createdEvent, target = source, - targetPortTarget = sourceEvent, + targetPortTarget = sourceEvent ) - private fun ParameterCopyAndConnectObject.toNetworkConnection() = if (input) createNetworkConnection( + private fun ParameterCopyAndConnectObject.toNetworkConnection() = if (input) factoryUtils.createNetworkConnection( kind = EntryKind.DATA, source = source, sourcePortTarget = sourceParam, target = target, - targetPortTarget = createdParam, - ) else createNetworkConnection( + targetPortTarget = createdParam + ) else factoryUtils.createNetworkConnection( kind = EntryKind.DATA, source = target, sourcePortTarget = createdParam, target = source, - targetPortTarget = sourceParam, + targetPortTarget = sourceParam ) private fun EventDeclaration.copy( diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt new file mode 100644 index 000000000..70754c0d8 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt @@ -0,0 +1,100 @@ +package org.fbme.ide.richediting.utils + +import org.fbme.lib.common.Identifier +import org.fbme.lib.common.StringIdentifier +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.EventDeclaration +import org.fbme.lib.iec61499.declarations.FBInterfaceDeclaration +import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor + +class FBInterfaceDeclarationUtils( + private val factory: IEC61499Factory, +) { + companion object { + fun copyPorts( + target: FBInterfaceDeclaration, + source: FBInterfaceDeclaration, + reversed: Boolean, + ) = copy( + reversed = reversed, + declaration = target, + outputEvents = source.outputEvents.map { it.copy() as EventDeclaration }, + inputEvents = source.inputEvents.map { it.copy() as EventDeclaration }, + parameterOutputs = source.outputParameters.map { it.copy() as ParameterDeclaration }, + parametersInputs = source.inputParameters.map { it.copy() as ParameterDeclaration }, + ) + + fun copyPorts( + declaration: FBInterfaceDeclaration, + fbTypeDescriptor: FBTypeDescriptor, + reversed: Boolean, + ) = copy( + reversed = reversed, + declaration = declaration, + outputEvents = fbTypeDescriptor.eventOutputPorts + .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, + inputEvents = fbTypeDescriptor.eventInputPorts + .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, + parameterOutputs = fbTypeDescriptor.dataOutputPorts + .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) }, + parametersInputs = fbTypeDescriptor.dataInputPorts + .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) } + ) + + fun copy( + reversed: Boolean, + declaration: FBInterfaceDeclaration, + outputEvents: List, + inputEvents: List, + parameterOutputs: List, + parametersInputs: List + ) { + if (reversed) { + declaration.inputEvents += outputEvents + declaration.outputEvents += inputEvents + declaration.inputParameters += parameterOutputs + declaration.outputParameters += parametersInputs + return + } + declaration.inputEvents += inputEvents + declaration.outputEvents += outputEvents + declaration.inputParameters += parametersInputs + declaration.outputParameters += parameterOutputs + } + } + + fun generateAdapterFromDeclaration( + declaration: FBInterfaceDeclaration, + name: String? = null, + identifier: Identifier? = null, + reversed: Boolean = false, + ): AdapterTypeDeclaration { + val adapterTypeDeclaration = factory.createAdapterTypeDeclaration( + identifier ?: name?.let { StringIdentifier(name) } + ) + if (name != null) { + adapterTypeDeclaration.name = name + } + copyPorts(adapterTypeDeclaration, declaration, reversed) + return adapterTypeDeclaration + } + + fun generateAdapterFromDescriptor( + fbTypeDescriptor: FBTypeDescriptor, + name: String? = null, + identifier: Identifier? = null, + reversed: Boolean = false, + ): AdapterTypeDeclaration { + val adapterTypeDeclaration = factory.createAdapterTypeDeclaration( + identifier ?: name?.let { StringIdentifier(name) } + ) + if (name != null) { + adapterTypeDeclaration.name = name + } + copyPorts(adapterTypeDeclaration, fbTypeDescriptor, reversed) + return adapterTypeDeclaration + } + +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt new file mode 100644 index 000000000..43f0918b9 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt @@ -0,0 +1,117 @@ +package org.fbme.ide.richediting.utils + +import org.fbme.lib.common.Declaration +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.EventDeclaration +import org.fbme.lib.iec61499.declarations.FBTypeDeclaration +import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.fbnetwork.* + +class IEC61499FactoryUtils( + private val factory: IEC61499Factory, +) { + fun createNetworkConnection( + kind: EntryKind, + source: FunctionBlockDeclarationBase?, + sourcePortTarget: Declaration, + target: FunctionBlockDeclarationBase?, + targetPortTarget: Declaration, + ): FBNetworkConnection = createConnection( + source = PortPath.createPortPath(source, kind, sourcePortTarget), + target = PortPath.createPortPath(target, kind, targetPortTarget), + entryKind = kind, + ) + + fun createConnection( + source: PortPath<*>, + target: PortPath<*>, + entryKind: EntryKind, + ): FBNetworkConnection { + val connection = factory.createFBNetworkConnection(entryKind) + connection.sourceReference.setTarget(source) + connection.targetReference.setTarget(target) + return connection + } + + fun addFunctionalBlock( + blockType: FBTypeDeclaration, + network: FBNetwork, + name: String? = null, + ): FunctionBlockDeclaration { + val block = factory.createFunctionBlockDeclaration(blockType.identifier) + block.name = name ?: blockType.name + block.typeReference.setTarget(blockType) + network.functionBlocks += block + return block + } + + fun copyEventsAndConnect( + destination: MutableList, + destinationBlock: FunctionBlockDeclarationBase?, + source: List, + sourceBlock: FunctionBlockDeclarationBase?, + network: FBNetwork, + outputToInput: Boolean = true, + ): List> { + val eventsAndCopies = source.map { + it to it.copy() as EventDeclaration + } + destination += eventsAndCopies.map { it.second } + + network.eventConnections += eventsAndCopies.map { + if (outputToInput) { + createNetworkConnection( + kind = EntryKind.EVENT, + source = sourceBlock, + sourcePortTarget = it.first, + target = destinationBlock, + targetPortTarget = it.second, + ) + } else { + createNetworkConnection( + kind = EntryKind.EVENT, + source = destinationBlock, + sourcePortTarget = it.second, + target = sourceBlock, + targetPortTarget = it.first, + ) + } + } + return eventsAndCopies + } + + fun copyParametersAndConnect( + destination: MutableList, + destinationBlock: FunctionBlockDeclarationBase?, + source: List, + sourceBlock: FunctionBlockDeclarationBase?, + network: FBNetwork, + outputToInput: Boolean = true, + ): List> { + val inputsAndCopies = source.map { + it to it.copy() as ParameterDeclaration + } + destination += inputsAndCopies.map { it.second } + + network.dataConnections += inputsAndCopies.map { + if (outputToInput) { + createNetworkConnection( + kind = EntryKind.DATA, + source = sourceBlock, + sourcePortTarget = it.first, + target = destinationBlock, + targetPortTarget = it.second, + ) + } else { + createNetworkConnection( + kind = EntryKind.DATA, + source = destinationBlock, + sourcePortTarget = it.second, + target = sourceBlock, + targetPortTarget = it.first, + ) + } + } + return inputsAndCopies + } +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt new file mode 100644 index 000000000..a85cec39c --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt @@ -0,0 +1,41 @@ +package org.fbme.ide.richediting.utils + +import org.fbme.lib.st.STFactory +import org.fbme.lib.st.expressions.* +import org.fbme.lib.st.statements.AssignmentStatement + +class STFactoryUtils( + private val stFactory: STFactory, +) { + fun intEquality(variable: VariableDeclaration, number: Int): BinaryExpression { + val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) + + val numberLiteral = createIntLiteral(number) + equality.rightExpression = numberLiteral + + equality.leftExpression = createVariable(variable) + return equality + } + + fun createIntLiteral(number: Int): Literal { + val numberLiteral = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal + numberLiteral.value = number + return numberLiteral + } + + fun createAssign( + variable: VariableDeclaration, + assignable: Expression, + ): AssignmentStatement { + val assignment = stFactory.createAssignmentStatement() + assignment.variable = createVariable(variable) + assignment.expression = assignable + return assignment + } + + fun createVariable(variable: VariableDeclaration): VariableReference { + val variableReference = stFactory.createVariableReference() + variableReference.reference.setTarget(variable) + return variableReference + } +} \ No newline at end of file From 5d25b2664f98bdf939ced0d5b7da3314db3984f7 Mon Sep 17 00:00:00 2001 From: klinachev Date: Mon, 15 Apr 2024 02:21:15 +0300 Subject: [PATCH 05/14] Add serialization --- .../ExtendedAdapterTypeDeclaration.kt | 132 ++---------------- .../parser/ExtendedAdapterTypeConverter.kt | 30 +++- .../fbme/lib/iec61499/parser/RootConverter.kt | 5 + .../stringify/ExtendedAdapterTypePrinter.kt | 28 ++-- .../stringify/RootDeclarationPrinter.kt | 2 + 5 files changed, 64 insertions(+), 133 deletions(-) diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt index a240a496e..9d894303b 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt @@ -1,133 +1,27 @@ package org.fbme.lib.iec61499.declarations.extention -import org.fbme.lib.iec61499.declarations.* -import org.fbme.lib.iec61499.descriptors.FBPortDescriptor +import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection +import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.descriptors.ExtendedPlugType +import org.fbme.lib.iec61499.descriptors.ExtendedSocketType import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor -import org.fbme.lib.iec61499.descriptors.FBTypeDescriptorImpl import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider -import org.fbme.lib.iec61499.fbnetwork.EntryKind -interface ExtendedAdapterTypeDeclaration : CustomNetworkComponentProvider, AdapterTypeDeclaration, - DeclarationWithNetwork { - val leftNetwork: AdapterNetworkDeclaration? - val rightNetwork: AdapterNetworkDeclaration? +interface ExtendedAdapterTypeDeclaration : CustomNetworkComponentProvider, AdapterTypeDeclaration { + var leftNetwork: AdapterNetworkDeclaration? + var rightNetwork: AdapterNetworkDeclaration? override val plugTypeDescriptor: FBTypeDescriptor - get() = FBTypeDescriptorImpl( - typeName = this.name, - declaration = this, - eventInputPorts = outputEvents.asSequence() - .plus(socketToFbInterface?.inputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = true) - .toList(), - eventOutputPorts = inputEvents.asSequence() - .plus(socketToFbInterface?.outputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = false) - .toList(), - dataInputPorts = outputParameters.asSequence() - .plus(socketToFbInterface?.inputParameters?.asSequence() ?: sequenceOf()) - .run { outputRouter?.let { plus(it) } ?: this } - .toParametersPortDescriptors(isInput = true) - .toList(), - dataOutputPorts = inputParameters.asSequence() - .plus(socketToFbInterface?.outputParameters?.asSequence() ?: sequenceOf()) - .run { inputRouter?.let { plus(it) } ?: this } - .toParametersPortDescriptors(isInput = false) - .toList(), - ) + get() = ExtendedPlugType(this) override val socketTypeDescriptor: FBTypeDescriptor - get() = FBTypeDescriptorImpl( - typeName = this.name, - declaration = this, - eventInputPorts = inputEvents.asSequence() - .plus(fbToPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = true) - .toList(), - eventOutputPorts = outputEvents.asSequence() - .plus(fbToPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) - .toEventPortDescriptors(isInput = false) - .toList(), - dataInputPorts = inputParameters.asSequence() - .plus(fbToPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) - .toParametersPortDescriptors(isInput = true) - .toList(), - dataOutputPorts = outputParameters.asSequence() - .plus(fbToPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) - .toParametersPortDescriptors(isInput = false) - .toList(), - ) - -// val internalFbPlugDescriptor: FBTypeDescriptor -// get() = FBTypeDescriptorImpl( -// typeName = this.name, -// declaration = this, -// eventInputPorts = outputEvents.asSequence() -// .plus(fbToPlugInterface?.outputEvents?.asSequence() ?: sequenceOf()) -// .toEventPortDescriptors(isInput = true) -// .toList(), -// eventOutputPorts = inputEvents.asSequence() -// .plus(fbToPlugInterface?.inputEvents?.asSequence() ?: sequenceOf()) -// .toEventPortDescriptors(isInput = false) -// .toList(), -// dataInputPorts = outputParameters.asSequence() -// .plus(fbToPlugInterface?.outputParameters?.asSequence() ?: sequenceOf()) -// .toParametersPortDescriptors(isInput = true) -// .toList(), -// dataOutputPorts = inputParameters.asSequence() -// .plus(fbToPlugInterface?.inputParameters?.asSequence() ?: sequenceOf()) -// .toParametersPortDescriptors(isInput = false) -// .toList(), -// ) -// -// val internalFbSocketDescriptor: FBTypeDescriptor -// get() = FBTypeDescriptorImpl( -// typeName = this.name, -// declaration = this, -// eventInputPorts = inputEvents.asSequence() -// .plus(socketToFbInterface?.outputEvents?.asSequence() ?: sequenceOf()) -// .toEventPortDescriptors(isInput = true) -// .toList(), -// eventOutputPorts = outputEvents.asSequence() -// .plus(socketToFbInterface?.inputEvents?.asSequence() ?: sequenceOf()) -// .toEventPortDescriptors(isInput = false) -// .toList(), -// dataInputPorts = inputParameters.asSequence() -// .plus(socketToFbInterface?.outputParameters?.asSequence() ?: sequenceOf()) -// .toParametersPortDescriptors(isInput = true) -// .toList(), -// dataOutputPorts = outputParameters.asSequence() -// .plus(socketToFbInterface?.inputParameters?.asSequence() ?: sequenceOf()) -// .toParametersPortDescriptors(isInput = false) -// .toList(), -// ) + get() = ExtendedSocketType(this) var inputRouter: ParameterDeclaration? var outputRouter: ParameterDeclaration? - var socketToFbInterface: DeclarationWithInterfaceSection? - var fbToPlugInterface: DeclarationWithInterfaceSection? -} - -fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> - FBPortDescriptor( - name = event.name, - connectionKind = EntryKind.EVENT, - position = index, - isInput = isInput, - isValid = true, - declaration = event, - ) + var internalFbSocketInterface: DeclarationWithInterfaceSection? + var internalFbPlugInterface: DeclarationWithInterfaceSection? + var internalNetworksInterface: DeclarationWithInterfaceSection? } - -fun Sequence.toParametersPortDescriptors(isInput: Boolean) = - mapIndexed { index, event -> - FBPortDescriptor( - name = event.name, - connectionKind = EntryKind.DATA, - position = index, - isInput = isInput, - isValid = true, - declaration = event, - ) - } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt index 1c2d70fcc..10444a089 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/ExtendedAdapterTypeConverter.kt @@ -2,6 +2,7 @@ package org.fbme.lib.iec61499.parser import org.fbme.lib.common.Identifier import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jdom.Element @@ -11,17 +12,34 @@ class ExtendedAdapterTypeConverter(arguments: ConverterArguments) val declaration = factory.createExtendedAdapterTypeDeclaration(identifier) FBInterfaceConverter(this, declaration).extractInterface() val element = checkNotNull(element) - FBNetworkConverter(with(element.getChild("FBNetwork")), declaration.network).extractNetwork() + declaration.leftNetwork = extractNetwork(element.getChild("leftNetwork")) + declaration.rightNetwork = extractNetwork(element.getChild("rightNetwork")) extractRouters(element, declaration) extractInternalInterfaces(element, declaration) return declaration } + private fun extractNetwork(element: Element?): AdapterNetworkDeclaration? { + if (element == null) { + return null + } + val identifier = identifierLocus.onDeclarationEntered(element) + try { + val network = factory.createAdapterNetworkDeclaration(identifier) + FBNetworkConverter(with(element.getChild("FBNetwork")), network.network).extractNetwork() + network.name = element.getAttributeValue("Name") + return network + } finally { + identifierLocus.onDeclarationLeaved() + } + } private fun extractInternalInterfaces(element: Element, declaration: ExtendedAdapterTypeDeclaration) { - declaration.socketToFbInterface = - extractDeclarationWithInterfaceSection(element.getChild("socketToFbInterface")) - declaration.fbToPlugInterface = - extractDeclarationWithInterfaceSection(element.getChild("fbToPlugInterface")) + declaration.internalFbSocketInterface = + extractDeclarationWithInterfaceSection(element.getChild("internalFbSocketInterface")) + declaration.internalFbPlugInterface = + extractDeclarationWithInterfaceSection(element.getChild("internalFbPlugInterface")) + declaration.internalNetworksInterface = + extractDeclarationWithInterfaceSection(element.getChild("internalNetworksInterface")) } private fun extractRouters(element: Element, declaration: ExtendedAdapterTypeDeclaration) { @@ -30,7 +48,7 @@ class ExtendedAdapterTypeConverter(arguments: ConverterArguments) } private fun extractParameter(element: Element, fieldName: String) = element.getChild(fieldName)?.let { - ParameterDeclarationConverter(with(it)).extract() + ParameterDeclarationConverter(with(it.getChildren("VarDeclaration").first())).extract() } private fun extractDeclarationWithInterfaceSection( diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt index d6d5e1139..f9ebd3090 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt @@ -1,6 +1,7 @@ package org.fbme.lib.iec61499.parser import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jdom.Document class RootConverter( @@ -24,6 +25,10 @@ class RootConverter( return AdapterTypeConverter(arguments()).extract() } + fun convertExtendedAdapterType(): ExtendedAdapterTypeDeclaration { + return ExtendedAdapterTypeConverter(arguments()).extract() + } + fun convertSubapplicationType(): SubapplicationTypeDeclaration { return SubappTypeConverter(arguments()).extract() } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt index 98535e5a2..0904c9d4e 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/ExtendedAdapterTypePrinter.kt @@ -2,6 +2,7 @@ package org.fbme.lib.iec61499.stringify import org.fbme.lib.iec61499.declarations.DeclarationWithInterfaceSection import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jdom.Element @@ -9,24 +10,35 @@ class ExtendedAdapterTypePrinter(declaration: ExtendedAdapterTypeDeclaration) : DeclarationPrinterBase(declaration, "ExtendedAdapterType") { override fun printDeclarationBody(element: Element) { element.addContent(FBInterfacePrinter(this.element, false).print()) - element.addContent(FBNetworkPrinter(this.element.network).print()) - printRouters(element) + printNetwork(element, "leftNetwork", this.element.leftNetwork) + printNetwork(element, "rightNetwork", this.element.rightNetwork) + printParameter(element, "inputRouter", this.element.inputRouter) + printParameter(element, "outputRouter", this.element.outputRouter) printInternalInterfaces(element, this.element) } - private fun printRouters(element: Element) { - printParameter(root = element, parameterElementName = "inputRouter", declaration = this.element.inputRouter) - printParameter(root = element, parameterElementName = "outputRouter", declaration = this.element.outputRouter) + private fun printNetwork(element: Element, name: String, network: AdapterNetworkDeclaration?) { + if (network == null) { + return + } + element.addContent( + Element(name).addContent(FBNetworkPrinter(network.network).print()) + .setAttribute("Name", network.name) + ) } private fun printInternalInterfaces(element: Element, declaration: ExtendedAdapterTypeDeclaration) { addNullableContent( element, - printDeclarationWithInterface(declaration.socketToFbInterface, "socketToFbInterface"), + printDeclarationWithInterface(declaration.internalFbSocketInterface, "internalFbSocketInterface"), ) addNullableContent( element, - printDeclarationWithInterface(declaration.fbToPlugInterface, "fbToPlugInterface"), + printDeclarationWithInterface(declaration.internalFbPlugInterface, "internalFbPlugInterface"), + ) + addNullableContent( + element, + printDeclarationWithInterface(declaration.internalNetworksInterface, "internalNetworksInterface"), ) } @@ -36,11 +48,11 @@ class ExtendedAdapterTypePrinter(declaration: ExtendedAdapterTypeDeclaration) : ) = fbInterface?.let { val child = Element(name) child.addContent(FBInterfacePrinter(fbInterface, false).print()) + .setAttribute("Name", name) } private fun printParameter(root: Element, parameterElementName: String, declaration: ParameterDeclaration?) { val createdElement = ParameterDeclarationPrinter.printAll(parameterElementName, listOfNotNull(declaration)) addNullableContent(root, createdElement) } - } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt index 948987ec4..6e5eb4223 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt @@ -2,6 +2,7 @@ package org.fbme.lib.iec61499.stringify import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.* +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jdom.DocType import org.jdom.Document import org.jdom.Element @@ -9,6 +10,7 @@ import org.jdom.Element class RootDeclarationPrinter(private val myDeclaration: Declaration) { fun print(): Document { val rootElement: Element = when (myDeclaration) { + is ExtendedAdapterTypeDeclaration -> ExtendedAdapterTypePrinter(myDeclaration).print() is AdapterTypeDeclaration -> AdapterTypePrinter(myDeclaration).print() is BasicFBTypeDeclaration -> BasicFBTypePrinter(myDeclaration).print() is CompositeFBTypeDeclaration -> CompositeFBTypePrinter(myDeclaration).print() From 0acdaee4b268b27b94e47913d34681f831683dc2 Mon Sep 17 00:00:00 2001 From: klinachev Date: Mon, 15 Apr 2024 02:24:27 +0300 Subject: [PATCH 06/14] Add System synchronization --- .../lib/iec61499/instances/NetworkInstance.kt | 8 - .../actions/SyncSystemResources.kt | 35 + .../richediting/utils/ExtendedAdapterUtils.kt | 824 +++++++++++++----- .../fbme/ide/richediting/utils/SystemUtils.kt | 102 +++ .../src/main/resources/META-INF/plugin.xml | 3 + 5 files changed, 769 insertions(+), 203 deletions(-) create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt index 55fe8814f..2ffd9d019 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/instances/NetworkInstance.kt @@ -3,7 +3,6 @@ package org.fbme.lib.iec61499.instances import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.declarations.extention.AdapterNetworkDeclaration -import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.fbme.lib.iec61499.fbnetwork.FBNetwork import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase @@ -63,12 +62,6 @@ interface NetworkInstance : Instance { return RegularNetworkInstance(parent, networkDeclaration, device) } - @JvmStatic - fun createForAdapter(adapter: ExtendedAdapterTypeDeclaration, parent: Instance?): NetworkInstance { - val networkDeclaration = adapter.network - return RegularNetworkInstance(parent, networkDeclaration, adapter) - } - @JvmStatic fun createForAdapterNetwork(adapterNetworkDeclaration: AdapterNetworkDeclaration, parent: Instance?): NetworkInstance { val networkDeclaration = adapterNetworkDeclaration.network @@ -91,7 +84,6 @@ interface NetworkInstance : Instance { is ResourceDeclaration -> createForResource(decl, parent) is DeviceDeclaration -> createForImplicitResourceOfDevice(decl, parent) is AdapterNetworkDeclaration -> createForAdapterNetwork(decl, parent) - is ExtendedAdapterTypeDeclaration -> createForAdapter(decl, parent) else -> throw IllegalArgumentException("Unknown kind of declaration: " + decl!!.javaClass) } } diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt new file mode 100644 index 000000000..983b2202f --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt @@ -0,0 +1,35 @@ +package org.fbme.ide.richediting.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAware +import jetbrains.mps.ide.actions.MPSCommonDataKeys +import jetbrains.mps.project.MPSProject +import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider +import org.fbme.ide.richediting.utils.SystemUtils +import org.fbme.lib.iec61499.declarations.SystemDeclaration + +class SyncSystemResources : AnAction(), DumbAware { + override fun update(event: AnActionEvent) = event.executeReadAction { + val repository = event.repository + val node = event.getData(MPSCommonDataKeys.NODE) + val applicationDeclaration = node?.let { + repository.adapterOrNull(node) + } + event.presentation.isEnabledAndVisible = applicationDeclaration != null + } + + override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { + val repository = event.repository + val factory = repository.iec61499Factory + val node = event.getData(MPSCommonDataKeys.NODE) + val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) + val project: MPSProject = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) + + val applicationDeclaration = node?.let { + repository.adapterOrNull(node) + } ?: return@executeWriteActionInEditor + val systemUtils = SystemUtils(factory, repository.stFactory, PlatformRepositoryProvider.getInstance(project)) + systemUtils.syncApplicationResources(applicationDeclaration, model) + } +} \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt index 2d81caa84..4b6bddfc0 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt @@ -1,56 +1,77 @@ package org.fbme.ide.richediting.utils -import jetbrains.mps.project.MPSProject +import jetbrains.mps.smodel.ModelImports import jetbrains.mps.smodel.SNodeUtil import org.fbme.ide.iec61499.repository.PlatformElement -import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider +import org.fbme.ide.iec61499.repository.PlatformRepository import org.fbme.lib.common.Declaration import org.fbme.lib.common.Identifier import org.fbme.lib.common.StringIdentifier import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.fbme.lib.iec61499.descriptors.FBPortDescriptor import org.fbme.lib.iec61499.fbnetwork.* import org.fbme.lib.st.STFactory +import org.fbme.lib.st.expressions.BinaryOperation +import org.fbme.lib.st.expressions.Literal +import org.fbme.lib.st.expressions.LiteralKind +import org.fbme.lib.st.types.ElementaryType import org.jetbrains.mps.openapi.model.SModel - class ExtendedAdapterUtils( private val factory: IEC61499Factory, private val stFactory: STFactory, - private val project: MPSProject, + private val owner: PlatformRepository, + private val publishSubscribeProvider: ((name: String) -> FBTypeDeclaration)? = null, ) { - private val switchGenerator = SwitchGenerator(factory, stFactory) - private val owner = PlatformRepositoryProvider.getInstance(project) - private val declarationsScope = owner.declarationsScope + private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) + private val stFactoryUtils: STFactoryUtils = STFactoryUtils(stFactory) + private val switchGenerator = AdapterSwitchGenerator(factory, stFactory) + private val numberToEventFbTypes: MutableMap = mutableMapOf() + private val eventToNumberFbTypes: MutableMap = mutableMapOf() companion object { + private const val VIRTUAL_PACKAGE_NAME = "generated" + fun isExtendedAdapterConnection(connection: FBNetworkConnection) = - getExtendedAdapterType(connection) != null + getSourceExtendedAdapterType(connection) != null && + getTargetSocketAdapterDeclaration(connection) is ExtendedAdapterTypeDeclaration + + fun getSourceExtendedAdapterType(connection: FBNetworkConnection) = + getSourcePlugAdapterDeclaration(connection) as? ExtendedAdapterTypeDeclaration - fun getExtendedAdapterType(connection: FBNetworkConnection) = - getSourceAdapterDeclaration(connection) as? ExtendedAdapterTypeDeclaration + fun getPlugExtendedAdapterType(declaration: Declaration?) = + getPlugAdapterDeclaration(declaration) as? ExtendedAdapterTypeDeclaration - fun getExtendedAdapterType(declaration: Declaration?) = - getSourceAdapterDeclaration(declaration) as? ExtendedAdapterTypeDeclaration + fun getSourcePlugAdapterDeclaration(connection: FBNetworkConnection) = + getPlugAdapterDeclaration(connection.sourceReference.getTarget()?.portTarget) - fun getSourceAdapterDeclaration(connection: FBNetworkConnection) = - getSourceAdapterDeclaration(connection.sourceReference.getTarget()?.portTarget) + fun getTargetSocketAdapterDeclaration(connection: FBNetworkConnection) = + getSocketAdapterDeclaration(connection.targetReference.getTarget()?.portTarget) - fun getSourceAdapterDeclaration(declaration: Declaration?) = + fun getSocketAdapterDeclaration(declaration: Declaration?) = + (declaration as? SocketDeclaration)?.typeReference?.getTarget() + + fun getPlugAdapterDeclaration(declaration: Declaration?) = (declaration as? PlugDeclaration)?.typeReference?.getTarget() } data class RevealDeclarationsResult( val extendedAdapter: ExtendedAdapterTypeDeclaration, + val routerAdapter: AdapterTypeDeclaration?, val leftAdapter: AdapterTypeDeclaration, - val middleAdapter: AdapterTypeDeclaration?, - val rightAdapter: AdapterTypeDeclaration?, - val leftBlockDeclaration: CompositeFBTypeDeclaration, + val middleAdapter: AdapterTypeDeclaration, + val rightAdapter: AdapterTypeDeclaration, + val leftBlockDeclaration: CompositeFBTypeDeclaration?, val rightBlockDeclaration: CompositeFBTypeDeclaration?, - val routers: MutableMap, + var leftPublishSubscribeAdapter: CompositeFBTypeDeclaration? = null, + var rightPublishSubscribeAdapter: CompositeFBTypeDeclaration? = null, + val routers: MutableMap = mutableMapOf(), ) { - fun getFarRightAdapter(): AdapterTypeDeclaration = rightAdapter ?: middleAdapter ?: leftAdapter + fun getFarLeftAdapter(): AdapterTypeDeclaration = routerAdapter ?: leftAdapter + + fun getFarRightAdapter(): AdapterTypeDeclaration = rightAdapter } fun revealAdapter( @@ -62,7 +83,7 @@ class ExtendedAdapterUtils( val identifiersToRevealResult = declarationsResults.associateBy { it.extendedAdapter.identifier } - revealAdapterInNetwork( + revealAdapterInNetworks( currentModel = currentModel, modelToCheck = currentModel, identifiersToRevealResult = identifiersToRevealResult, @@ -70,13 +91,60 @@ class ExtendedAdapterUtils( return revealDeclarations } + fun revealAdapterWithNet( + revealResult: RevealDeclarationsResult, + block: FunctionBlockDeclaration, + port: FBPortDescriptor, + count: Int, + model: SModel, + ) = if (port.isInput) { + val network = checkNotNull(block.container) + revealRightPart( + revealResult = revealResult, + leftPort = createRightPublishSubscribeAdapter( + revealResult = revealResult, + network = network, + number = 1, + model = model, + ), + rightPort = block.getPort(port), + network = network, + number = 1, + ) + } else { + val network = checkNotNull(block.container) + val portPaths = revealLeftPart( + revealResult = revealResult, + sourcePort = block.getPort(port), + network = network, + model = model, + connectionsCount = count, + ) + for ((number, connectionSourcePort) in portPaths.withIndex()) { + val publishSubscribeAdapter = createLeftPublishSubscribeAdapter( + revealResult = revealResult, + network = network, + number = number, + model = model, + ) + + network.adapterConnections += factoryUtils.createConnection( + source = connectionSourcePort, + target = publishSubscribeAdapter.getPort( + publishSubscribeAdapter.type.socketPorts.first() + ), + entryKind = EntryKind.ADAPTER, + ) + } + } + private fun revealAdapterInAllNetworks( currentModel: SModel, identifiersToRevealResult: Map ) { for (module in currentModel.repository.modules) { for (model in module.models) { - revealAdapterInNetwork( + revealAdapterInNetworks( currentModel = currentModel, modelToCheck = model, identifiersToRevealResult = identifiersToRevealResult @@ -85,7 +153,7 @@ class ExtendedAdapterUtils( } } - private fun revealAdapterInNetwork( + private fun revealAdapterInNetworks( currentModel: SModel, modelToCheck: SModel, identifiersToRevealResult: Map @@ -103,7 +171,7 @@ class ExtendedAdapterUtils( fbTypeDeclaration.plugs.forEach { val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] if (revealDeclarationsResult != null) { - it.typeReference.setTarget(revealDeclarationsResult.leftAdapter) + it.typeReference.setTarget(revealDeclarationsResult.getFarLeftAdapter()) } } } @@ -112,48 +180,97 @@ class ExtendedAdapterUtils( val declarationWithNetwork = owner.adapterOrNull(node) if (declarationWithNetwork != null) { val sourceIdentifiersToRevealResult = - identifiersToRevealResult.mapKeys { it.value.leftAdapter.identifier } + identifiersToRevealResult.mapKeys { it.value.getFarLeftAdapter().identifier } revealExtendedAdaptersInNetwork( network = declarationWithNetwork.network, identifiersToRevealResult = sourceIdentifiersToRevealResult, model = currentModel, + withPublishSubscribe = false, ) } } } - private fun revealDeclarations( + fun revealDeclarations( extendedAdapter: ExtendedAdapterTypeDeclaration, - model: SModel, + model: SModel?, ): RevealDeclarationsResult { val name = extendedAdapter.name - val adapterGenerator = AdapterGenerator(factory) - val leftAdapter = adapterGenerator.generateAdapterFromDescriptor( + val FBInterfaceDeclarationUtils = FBInterfaceDeclarationUtils(factory) + val leftAdapter = FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( name = "Left_$name", - fbTypeDescriptor = extendedAdapter.plugTypeDescriptor, - reversed = true, - ) - val middleAdapter = adapterGenerator.generateAdapterFromDeclaration( - name = "Middle_$name", - identifier = extendedAdapter.identifier, - declaration = extendedAdapter, - ) - val rightAdapter = adapterGenerator.generateAdapterFromDescriptor( - name = "Right_$name", - fbTypeDescriptor = extendedAdapter.socketTypeDescriptor, + fbTypeDescriptor = extendedAdapter.getCustomNetworkComponents()[1].block.type, + reversed = false, ) + val routerAdapter = if (extendedAdapter.outputRouter != null) { + FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = "RouterAdapter_$name", + fbTypeDescriptor = extendedAdapter.plugTypeDescriptor, + reversed = true, + ) + } else { + null + } + val leftNetwork = extendedAdapter.leftNetwork + val middleAdapter = if (leftNetwork == null || + (extendedAdapter.internalFbSocketInterface?.isEmpty() != false && + extendedAdapter.internalNetworksInterface?.isEmpty() != false) + ) { + leftAdapter + } else { + FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = "Middle_$name", + identifier = extendedAdapter.identifier, + fbTypeDescriptor = leftNetwork.getCustomNetworkComponents()[0].block.type, + reversed = true, + ) + } + val rightAdapter = if ( + extendedAdapter.rightNetwork == null || + (extendedAdapter.internalFbPlugInterface?.isEmpty() != false && + extendedAdapter.internalNetworksInterface?.isEmpty() != false) + ) { + middleAdapter + } else { + FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = "Right_$name", + fbTypeDescriptor = extendedAdapter.socketTypeDescriptor, + ) + } - val block = createCompositeFB(factory, "InternalFB_$name", leftAdapter, rightAdapter, extendedAdapter.network) - // val leftBlock = createCompositeFB(factory, name, leftAdapter, middleAdapter, extendedAdapter.network) - // val rightBlock = createCompositeFB(factory, name, middleAdapter, rightAdapter, extendedAdapter.rightNetwork) - model.addRootNodes(leftAdapter, middleAdapter, rightAdapter, block, virtualPackage = "generated") + val leftBlock = leftNetwork?.let { + createCompositeFB( + name = "${extendedAdapter.name}_${it.name}", + leftAdapter = leftAdapter, + rightAdapter = middleAdapter, + network = it.network, + ) + } + val rightBlock = extendedAdapter.rightNetwork?.let { + createCompositeFB( + name = "${extendedAdapter.name}_${it.name}", + leftAdapter = middleAdapter, + rightAdapter = rightAdapter, + network = it.network, + ) + } + model?.addRootNodes( + routerAdapter, + leftAdapter, + middleAdapter, + rightAdapter, + leftBlock, + rightBlock, + virtualPackage = VIRTUAL_PACKAGE_NAME, + ) return RevealDeclarationsResult( extendedAdapter = extendedAdapter, + routerAdapter = routerAdapter, leftAdapter = leftAdapter, middleAdapter = middleAdapter, rightAdapter = rightAdapter, - leftBlockDeclaration = block, - rightBlockDeclaration = null, + leftBlockDeclaration = leftBlock, + rightBlockDeclaration = rightBlock, routers = mutableMapOf(), ) } @@ -162,194 +279,505 @@ class ExtendedAdapterUtils( network: FBNetwork, identifiersToRevealResult: Map, model: SModel, + withPublishSubscribe: Boolean, ) { - val adapterConnections = network.adapterConnections - .filter { getSourceAdapterDeclaration(it)?.identifier in identifiersToRevealResult } + val adapterConnections = network.adapterConnections.asSequence() + .filter { getSourcePlugAdapterDeclaration(it)?.identifier in identifiersToRevealResult } .mapNotNull { connection -> (connection.sourceReference.getTarget())?.let { it to connection } } .groupBy({ it.first }, { it.second }) - network.adapterConnections.removeIf { it.sourceReference.getTarget() in adapterConnections } + val connectionsToRemove = network.adapterConnections.asSequence() + .filter { it.sourceReference.getTarget() in adapterConnections } + .toSet() for ((sourcePort, connections) in adapterConnections) { - val plugAdapterIdentifier = getSourceAdapterDeclaration(sourcePort.portTarget)?.identifier + // plugBlock -> router -> leftBlock -> [publish subscribe] -> rightBlock -> socketBlock + val plugAdapterIdentifier = getPlugAdapterDeclaration(sourcePort.portTarget)?.identifier val revealResult = identifiersToRevealResult[plugAdapterIdentifier] checkNotNull(revealResult) - val adapterType = revealResult.extendedAdapter if (connections.isEmpty()) { continue } - val routerDeclaration = if (connections.size == 1) { - null - } else { - revealResult.routers.computeIfAbsent(connections.size) { - val outputRouter = adapterType.outputRouter - ?: throw IllegalStateException("Adapter ${adapterType.name} doesn't have routing field") - switchGenerator.generateRouter( - name = "${adapterType.name}_${connections.size}", + val portsBeforePublishBlocks = revealLeftPart( + revealResult = revealResult, + sourcePort = sourcePort, + network = network, + model = model, + connectionsCount = connections.size, + ) + if (withPublishSubscribe) { + for ((number, connectionSourcePort) in portsBeforePublishBlocks.withIndex()) { + val publishSubscribeAdapter = createLeftPublishSubscribeAdapter( + revealResult = revealResult, + network = network, + number = number, model = model, - adapterTypeDeclaration = revealResult.leftAdapter, - outputsCount = connections.size, - routerName = outputRouter.name, - virtualPackage = "generated", ) - } - } - // plugBlock -> leftBlock -> [router ->] [rightBlock ->] socketBlock - val leftBlock = addFunctionalBlock(revealResult.leftBlockDeclaration, network) - network.adapterConnections += createConnection( - source = sourcePort, - target = leftBlock.getPort(leftBlock.type.socketPorts.first()), - entryKind = EntryKind.ADAPTER, - ) - - val blockBeforeRightBlock = if (routerDeclaration == null) { - leftBlock - } else { - val routerBlock = addFunctionalBlock(routerDeclaration, network) - network.adapterConnections += createConnection( - source = leftBlock.getPort(leftBlock.type.plugPorts.first()), - target = routerBlock.getPort(routerBlock.type.socketPorts.first()), - entryKind = EntryKind.ADAPTER, - ) - routerBlock - } - - for ((connection, connectionSourcePort) in connections - .zip(blockBeforeRightBlock.type.plugPorts.map { blockBeforeRightBlock.getPort(it) })) { - val portBeforeSocketBlock = if (revealResult.rightBlockDeclaration == null) { - connectionSourcePort - } else { - val addedBlock = addFunctionalBlock(revealResult.rightBlockDeclaration, network) - network.adapterConnections += createConnection( + network.adapterConnections += factoryUtils.createConnection( source = connectionSourcePort, - target = addedBlock.getPort(addedBlock.type.socketPorts.first()), + target = publishSubscribeAdapter.getPort( + publishSubscribeAdapter.type.socketPorts.first() + ), entryKind = EntryKind.ADAPTER, ) - addedBlock.getPort(addedBlock.type.plugPorts.first()) } + } - network.adapterConnections += createConnection( - source = portBeforeSocketBlock, - target = checkNotNull(connection.targetReference.getTarget()), - entryKind = EntryKind.ADAPTER, + val connectionsAndPorts = connections.zip(portsBeforePublishBlocks) + for (i in connectionsAndPorts.indices) { + val (connection, connectionSourcePort) = connectionsAndPorts[i] + revealRightPart( + revealResult = revealResult, + leftPort = if (withPublishSubscribe) { + createRightPublishSubscribeAdapter( + revealResult = revealResult, + network = network, + number = i, + model = model, + ) + } else { + connectionSourcePort + }, + rightPort = checkNotNull(connection.targetReference.getTarget()), + network = network, + number = i, ) } } + network.adapterConnections.removeIf { it in connectionsToRemove } } - private fun addBlockOrElseSource( - blockToAdd: CompositeFBTypeDeclaration?, + private fun revealLeftPart( + revealResult: RevealDeclarationsResult, + sourcePort: PortPath<*>, network: FBNetwork, - sourcePort: PortPath, - ) = if (blockToAdd != null) { - val addedBlock = addFunctionalBlock(blockToAdd, network) - network.adapterConnections += createConnection( - source = sourcePort, - target = addedBlock.getPort(addedBlock.type.socketPorts.first()), - entryKind = EntryKind.ADAPTER, + model: SModel, + connectionsCount: Int, + ): List> { + val adapterType = revealResult.extendedAdapter + val outputRouter = adapterType.outputRouter + val routerDeclaration = if (outputRouter == null) { + if (connectionsCount > 1) { + error("Port has more than one connection") + } + null + } else { + revealResult.routers.computeIfAbsent(connectionsCount) { + switchGenerator.generateRouter( + name = "${adapterType.name}_$connectionsCount", + model = model, + socketAdapterTypeDeclaration = checkNotNull(revealResult.routerAdapter), + plugAdapterTypeDeclaration = revealResult.leftAdapter, + outputsCount = connectionsCount, + outputRouterName = outputRouter.name, + inputRouterName = adapterType.inputRouter?.name, + virtualPackage = VIRTUAL_PACKAGE_NAME, + ) + } + } + + val portsBeforePublishBlocks: List> = if (routerDeclaration == null) { + listOf(sourcePort) + } else { + val routerBlock = factoryUtils.addFunctionalBlock(routerDeclaration, network) + network.adapterConnections += factoryUtils.createConnection( + source = sourcePort, + target = routerBlock.getPort(routerBlock.type.socketPorts.first()), + entryKind = EntryKind.ADAPTER, + ) + routerBlock.type.plugPorts.map { routerBlock.getPort(it) } + } + return portsBeforePublishBlocks.mapIndexed { index, it -> + connectBlockReturnPlugPort( + blockDeclaration = revealResult.leftBlockDeclaration, + sourcePort = it, + network = network, + name = revealResult.leftBlockDeclaration?.let { "${it.name}_$index" }, + ) + } + } + + private fun createLeftPublishSubscribeAdapter( + revealResult: RevealDeclarationsResult, + network: FBNetwork, + number: Int, + model: SModel, + ): FunctionBlockDeclaration { + val adapterType = revealResult.extendedAdapter + val leftPublishSubscribeAdapter = revealResult.leftPublishSubscribeAdapter ?: run { + val leftCompositeFBType = factory.createCompositeFBTypeDeclaration( + StringIdentifier("${adapterType.name}_LeftPublishSubscribeAdapter_$number") + ) + val socket = factory.createSocketDeclaration(StringIdentifier("socket")) + socket.typeReference.setTarget(revealResult.middleAdapter) + leftCompositeFBType.sockets += socket + model.addRootNodes(leftCompositeFBType, virtualPackage = VIRTUAL_PACKAGE_NAME) + createPublishSubscribeAdapter( + compositeFBType = leftCompositeFBType, + declaration = socket, + currentModel = model, + name = adapterType.name, + ) + } + revealResult.leftPublishSubscribeAdapter = leftPublishSubscribeAdapter + val leftPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( + blockType = leftPublishSubscribeAdapter, + network = network, ) - addedBlock.getPort(addedBlock.type.plugPorts.first()) - } else { - sourcePort + addPublishSubscribeBlocks( + source = leftPublishSubscribeAdapterBlock, + network = network, + currentModel = model, + name = "Left_${adapterType.name}_$number", + ) + return leftPublishSubscribeAdapterBlock } - private fun addFunctionalBlock( - source: FunctionBlockDeclaration, - target: FunctionBlockDeclaration, + private fun revealRightPart( + revealResult: RevealDeclarationsResult, + leftPort: PortPath, + rightPort: PortPath, network: FBNetwork, + number: Int, ) { - val outputEvents = source.typeReference.getTarget()?.outputEvents - if (outputEvents == null) { - return + val portBeforeSocketBlock = connectBlockReturnPlugPort( + blockDeclaration = revealResult.rightBlockDeclaration, + sourcePort = leftPort, + network = network, + name = revealResult.rightBlockDeclaration?.let { "${it.name}_$number" } + ) + + network.adapterConnections += factoryUtils.createConnection( + source = portBeforeSocketBlock, + target = rightPort, + entryKind = EntryKind.ADAPTER + ) + } + + private fun createRightPublishSubscribeAdapter( + revealResult: RevealDeclarationsResult, + network: FBNetwork, + number: Int, + model: SModel, + ): PortPath { + val adapterType = revealResult.extendedAdapter + val rightPublishSubscribeAdapter = revealResult.rightPublishSubscribeAdapter ?: run { + val rightCompositeFBType = factory.createCompositeFBTypeDeclaration( + StringIdentifier("${adapterType.name}_RightPublishSubscribeAdapter_$number") + ) + val plug = factory.createPlugDeclaration(StringIdentifier("plug")) + plug.typeReference.setTarget(revealResult.middleAdapter) + rightCompositeFBType.plugs += plug + model.addRootNodes(rightCompositeFBType, virtualPackage = VIRTUAL_PACKAGE_NAME) + createPublishSubscribeAdapter( + compositeFBType = rightCompositeFBType, + declaration = plug, + currentModel = model, + name = adapterType.name, + ) } - for ((eventDeclaration, eventPort) in outputEvents.zip(source.type.eventOutputPorts)) { - val size = eventDeclaration.associations.size - if (size > 10) { - throw IllegalArgumentException("Size more than 10") - } - val identifier = StringIdentifier("PUBLISH_$size") - val typeDeclaration = declarationsScope.findServiceFBTypeDeclaration(identifier) - ?: throw IllegalStateException("${identifier.value} block not found") - val block = factory.createFunctionBlockDeclaration(identifier) - block.name = "${eventDeclaration.name}c" - block.typeReference.setTarget(typeDeclaration) - - createConnection( - source = source.getPort(eventPort), - target = block.getPort(typeDeclaration.typeDescriptor.eventInputPorts.first { it.name == "REQ" }), - entryKind = EntryKind.EVENT, + revealResult.rightPublishSubscribeAdapter = rightPublishSubscribeAdapter + val rightPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( + blockType = rightPublishSubscribeAdapter, + network = network, + ) + addPublishSubscribeBlocks( + source = rightPublishSubscribeAdapterBlock, + network = network, + currentModel = model, + name = "Right_${adapterType.name}_$number", + ) + return PortPath.createPlugPortPath( + functionBlock = rightPublishSubscribeAdapterBlock, + portTarget = rightPublishSubscribeAdapter.plugs.first(), + ) + } + + private fun connectBlockReturnPlugPort( + blockDeclaration: CompositeFBTypeDeclaration?, + sourcePort: PortPath<*>, + network: FBNetwork, + name: String? = null, + ): PortPath { + return if (blockDeclaration == null) { + sourcePort + } else { + val leftBlock = factoryUtils.addFunctionalBlock(blockDeclaration, network, name) + network.adapterConnections += factoryUtils.createConnection( + source = sourcePort, + target = leftBlock.getPort(leftBlock.type.socketPorts.first()), + entryKind = EntryKind.ADAPTER ) - for (i in eventDeclaration.associations.indices) { - val association = eventDeclaration.associations[i] - val parameter = association.parameterReference.getTarget() - checkNotNull(parameter) - createConnection( - source = source.getPort(source.type.dataOutputPorts.first { it.name == parameter.name }), - target = block.getPort(typeDeclaration.typeDescriptor.dataInputPorts.first { it.name == "SD_${i + 1}" }), - entryKind = EntryKind.DATA, - ) - } - network.functionBlocks += block + leftBlock.getPort(leftBlock.type.plugPorts.first()) + } + } + + private fun createPublishSubscribeAdapter( + compositeFBType: CompositeFBTypeDeclaration, + declaration: FunctionBlockDeclarationBase, + currentModel: SModel, + name: String, + ): CompositeFBTypeDeclaration { + val typeDescriptor = declaration.type + + val eventToNumberFbType = eventToNumberFbTypes.computeIfAbsent( + typeDescriptor.eventOutputPorts.size + ) { number -> + createEventToNumberConverter( + name = "${name}_EventToNumberAdapter_$number", + inputCount = number, + ).also { currentModel.addRootNodes(it, virtualPackage = VIRTUAL_PACKAGE_NAME) } } - val inputEvents = source.typeReference.getTarget()?.inputEvents - if (inputEvents == null) { - return + + val eventToNumberBlock = factoryUtils.addFunctionalBlock(eventToNumberFbType, compositeFBType.network) + + val numberToEventFbType = numberToEventFbTypes.computeIfAbsent( + typeDescriptor.eventOutputPorts.size + ) { number -> + createNumberToEventConverter( + name = "NumberToEventAdapter_$number", + inputCount = number, + ).also { currentModel.addRootNodes(it, virtualPackage = VIRTUAL_PACKAGE_NAME) } } - for ((eventDeclaration, eventPort) in inputEvents.zip(source.type.eventInputPorts)) { - val size = eventDeclaration.associations.size - if (size > 10) { - throw IllegalArgumentException("Size more than 10") + + val numberToEventBlock = factoryUtils.addFunctionalBlock(numberToEventFbType, compositeFBType.network) + + // events: inputs -> numberToEventBlock -> socket -> eventToNumberBlock -> outputs + // data: inputs -> numberToEventBlock + // data: eventToNumberBlock -> outputs + // data: inputs -> socket -> outputs + factoryUtils.copyEventsAndConnect( + destination = compositeFBType.inputEvents, + destinationBlock = null, + source = numberToEventFbType.inputEvents, + sourceBlock = numberToEventBlock, + network = compositeFBType.network, + outputToInput = false, + ) + factoryUtils.copyParametersAndConnect( + destination = compositeFBType.inputParameters, + destinationBlock = null, + source = numberToEventFbType.inputParameters, + sourceBlock = numberToEventBlock, + network = compositeFBType.network, + outputToInput = false, + ) + compositeFBType.network.eventConnections += typeDescriptor.eventInputPorts + .zip(numberToEventFbType.outputEvents) + .map { + factoryUtils.createConnection( + entryKind = EntryKind.EVENT, + source = PortPath.createEventPortPath(numberToEventBlock, it.second), + target = declaration.getPort(it.first), + ) } - val identifier = StringIdentifier("SUBSCRIBE_$size") - val typeDeclaration = declarationsScope.findServiceFBTypeDeclaration(identifier) - ?: throw IllegalStateException("${identifier.value} block not found") - val block = factory.createFunctionBlockDeclaration(identifier) - block.name = "${eventDeclaration.name}c" - block.typeReference.setTarget(typeDeclaration) - - createConnection( - source = source.getPort(eventPort), - target = block.getPort(typeDeclaration.typeDescriptor.eventInputPorts.first { it.name == "IND" }), - entryKind = EntryKind.EVENT, - ) - for (i in eventDeclaration.associations.indices) { - val association = eventDeclaration.associations[i] - val parameter = association.parameterReference.getTarget() - checkNotNull(parameter) - createConnection( - source = source.getPort(source.type.dataOutputPorts.first { it.name == parameter.name }), - target = block.getPort(typeDeclaration.typeDescriptor.dataInputPorts.first { it.name == "RD_${i + 1}" }), - entryKind = EntryKind.DATA, + compositeFBType.network.eventConnections += typeDescriptor.eventOutputPorts + .zip(eventToNumberFbType.inputEvents) + .map { + factoryUtils.createConnection( + entryKind = EntryKind.EVENT, + source = declaration.getPort(it.first), + target = PortPath.createEventPortPath(eventToNumberBlock, it.second), ) } - network.functionBlocks += block + factoryUtils.copyEventsAndConnect( + destination = compositeFBType.outputEvents, + destinationBlock = null, + source = eventToNumberFbType.outputEvents, + sourceBlock = eventToNumberBlock, + network = compositeFBType.network + ) + factoryUtils.copyParametersAndConnect( + destination = compositeFBType.outputParameters, + destinationBlock = null, + source = eventToNumberFbType.outputParameters, + sourceBlock = eventToNumberBlock, + network = compositeFBType.network + ) + factoryUtils.copyParametersAndConnect( + destination = compositeFBType.inputParameters, + destinationBlock = null, + source = typeDescriptor.dataInputPorts.map { it.declaration as ParameterDeclaration }, + sourceBlock = declaration, + network = compositeFBType.network, + outputToInput = false, + ) + factoryUtils.copyParametersAndConnect( + destination = compositeFBType.outputParameters, + destinationBlock = null, + source = typeDescriptor.dataOutputPorts.map { it.declaration as ParameterDeclaration }, + sourceBlock = declaration, + network = compositeFBType.network + ) + return compositeFBType + } + + private fun createEventToNumberConverter( + name: String, + inputCount: Int, + ): BasicFBTypeDeclaration { + val basicFbType = factory.createBasicFBTypeDeclaration(StringIdentifier(name)) + val events = (0 until inputCount).map { factory.createEventDeclaration(StringIdentifier("I_$it")) } + basicFbType.inputEvents += events + val outputEvent = factory.createEventDeclaration(StringIdentifier("REQ")) + basicFbType.outputEvents += outputEvent + val parameterOutput = factory.createParameterDeclaration(StringIdentifier("I_E_number")) + parameterOutput.type = ElementaryType.INT + basicFbType.outputParameters += parameterOutput + val start = factory.createStateDeclaration(StringIdentifier("Start")) + basicFbType.ecc.states += start + for (i in basicFbType.inputEvents.indices) { + val event = basicFbType.inputEvents[i] + val state = factory.createStateDeclaration(StringIdentifier(event.name)) + basicFbType.ecc.states += state + val startToState = factory.createStateTransition() + basicFbType.ecc.transitions += startToState + startToState.sourceReference.setTarget(start) + startToState.targetReference.setTarget(state) + startToState.condition.eventReference.setFQName(event.name) + val stateToStart = factory.createStateTransition() + basicFbType.ecc.transitions += stateToStart + stateToStart.sourceReference.setTarget(state) + stateToStart.targetReference.setTarget(start) + + val stateAction = factory.createStateAction() + state.actions += stateAction + stateAction.event.setFQName(outputEvent.name) + val algorithmDeclaration = factory.createAlgorithmDeclaration( + StringIdentifier(state.name + "_algorithm") + ) + val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST) + algorithmDeclaration.body = algorithmBody + val assignment = stFactory.createAssignmentStatement() + val variableReference = stFactory.createVariableReference() + variableReference.reference.setTarget(parameterOutput) + assignment.variable = variableReference + val literal = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal + literal.value = i + assignment.expression = literal + + algorithmBody.statements += assignment + stateAction.algorithm.setTarget(algorithmDeclaration) + basicFbType.algorithms += algorithmDeclaration } + return basicFbType } - private fun addFunctionalBlock( - blockType: CompositeFBTypeDeclaration, - network: FBNetwork, - ): FunctionBlockDeclaration { - val block = factory.createFunctionBlockDeclaration(blockType.identifier) - block.name = blockType.name - block.typeReference.setTarget(blockType) - network.functionBlocks += block - return block + private fun createNumberToEventConverter( + name: String, + inputCount: Int, + ): BasicFBTypeDeclaration { + val basicFbType = factory.createBasicFBTypeDeclaration(StringIdentifier(name)) + val events = (0 until inputCount).map { factory.createEventDeclaration(StringIdentifier("O_$it")) } + basicFbType.outputEvents += events + val inputEvent = factory.createEventDeclaration(StringIdentifier("IND")) + basicFbType.inputEvents += inputEvent + val parameterInput = factory.createParameterDeclaration(StringIdentifier("O_E_number")) + parameterInput.type = ElementaryType.INT + basicFbType.inputParameters += parameterInput + val start = factory.createStateDeclaration(StringIdentifier("Start")) + basicFbType.ecc.states += start + for (i in basicFbType.outputEvents.indices) { + val event = basicFbType.outputEvents[i] + val state = factory.createStateDeclaration(StringIdentifier(event.name)) + basicFbType.ecc.states += state + val startToState = factory.createStateTransition() + basicFbType.ecc.transitions += startToState + startToState.condition.setGuardCondition(stFactoryUtils.intEquality(parameterInput, i)) + startToState.sourceReference.setTarget(start) + startToState.targetReference.setTarget(state) + + val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) + val numberLiteral = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal + numberLiteral.value = i + equality.rightExpression = numberLiteral + val variableReference = stFactory.createVariableReference() + variableReference.reference.setTarget(parameterInput) + equality.leftExpression = variableReference + + val stateToStart = factory.createStateTransition() + basicFbType.ecc.transitions += stateToStart + stateToStart.sourceReference.setTarget(state) + stateToStart.targetReference.setTarget(start) + + val stateAction = factory.createStateAction() + state.actions += stateAction + stateAction.event.setFQName(event.name) + } + return basicFbType } - private fun createConnection( - source: PortPath<*>, - target: PortPath<*>, - entryKind: EntryKind, - ): FBNetworkConnection { - val connection = factory.createFBNetworkConnection(entryKind) - connection.sourceReference.setTarget(source) - connection.targetReference.setTarget(target) - return connection + private fun addPublishSubscribeBlocks( + source: FunctionBlockDeclaration, + network: FBNetwork, + currentModel: SModel, + name: String, + ) { + val fbTypeDeclaration = source.typeReference.getTarget() + checkNotNull(fbTypeDeclaration) + val outputEvent = fbTypeDeclaration.outputEvents.first() + val outputParams = fbTypeDeclaration.outputParameters + if (outputParams.size > 10) { + error("Size more than 10") + } + val publishFbType = getFBTypeFrom4diacModel(currentModel, "PUBLISH_${outputParams.size}") + val publishBlock = factoryUtils.addFunctionalBlock(publishFbType, network, name = "Publish_${name}") + network.eventConnections += factoryUtils.createConnection( + source = PortPath.createEventPortPath(source, outputEvent), + target = PortPath.createEventPortPath( + publishBlock, + publishFbType.inputEvents.first { it.name == outputEvent.name } + ), + entryKind = EntryKind.EVENT, + ) + network.dataConnections += fbTypeDeclaration.outputParameters + .zip(publishFbType.inputParameters.filter { it.name.startsWith("SD") }) + .map { (sourceParameter, publishParameter) -> + factoryUtils.createConnection( + source = PortPath.createDataPortPath(source, sourceParameter), + target = PortPath.createDataPortPath(publishBlock, publishParameter), + entryKind = EntryKind.DATA, + ) + } + + val inputEvent = fbTypeDeclaration.inputEvents.first() + val inputParams = fbTypeDeclaration.inputParameters + if (inputParams.size > 10) { + error("Size more than 10") + } + val subscribeFbType = getFBTypeFrom4diacModel(currentModel, "SUBSCRIBE_${inputParams.size}") + val subscribeBlock = factoryUtils.addFunctionalBlock(subscribeFbType, network, name = "Subscribe_${name}") + network.eventConnections += factoryUtils.createConnection( + source = PortPath.createEventPortPath( + subscribeBlock, + subscribeFbType.outputEvents.first { it.name == inputEvent.name }, + ), + target = PortPath.createEventPortPath(source, inputEvent), + entryKind = EntryKind.EVENT, + ) + network.dataConnections += fbTypeDeclaration.inputParameters + .zip(subscribeFbType.outputParameters.filter { it.name.startsWith("RD") }) + .map { (sourceParameter, subscribeParameter) -> + factoryUtils.createConnection( + source = PortPath.createDataPortPath(subscribeBlock, subscribeParameter), + target = PortPath.createDataPortPath(source, sourceParameter), + entryKind = EntryKind.DATA, + ) + } } + private fun getFBTypeFrom4diacModel( + currentModel: SModel, + nodeName: String, + ): FBTypeDeclaration = publishSubscribeProvider?.invoke(nodeName) ?: owner.adapter( + ModelImports(currentModel).importedModels + .first { it.modelName == "iec61499.4diac.stdlib" } + .resolve(owner.mpsRepository) + .rootNodes + .first { it.name == nodeName } + ) + private fun createCompositeFB( - factory: IEC61499Factory, name: String, leftAdapter: AdapterTypeDeclaration, rightAdapter: AdapterTypeDeclaration, @@ -359,26 +787,32 @@ class ExtendedAdapterUtils( val plug = factory.createPlugDeclaration(rightAdapter.identifier) plug.typeReference.setTarget(rightAdapter) - plug.name = "Plug Connection" + plug.name = "Plug_Connection" composite.plugs += plug val socket = factory.createSocketDeclaration(leftAdapter.identifier) socket.typeReference.setTarget(leftAdapter) - socket.name = "Socket Connection" + socket.name = "Socket_Connection" composite.sockets += socket // add port connections composite.network.copyElements(network) return composite } - } fun SModel.addRootNodes( - vararg declarations: Declaration, + vararg declarations: Declaration?, virtualPackage: String? = null, ) { - for (declaration in declarations) { + val distinctDeclarations = declarations.associateBy { it?.name } + rootNodes.filter { distinctDeclarations[it.name] != null } + .forEach { removeRootNode(it) } + + for (declaration in distinctDeclarations.values) { + if (declaration == null) { + continue + } val node = (declaration as PlatformElement).node if (!virtualPackage.isNullOrEmpty()) { node.setProperty(SNodeUtil.property_BaseConcept_virtualPackage, virtualPackage) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt new file mode 100644 index 000000000..52af1518d --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt @@ -0,0 +1,102 @@ +package org.fbme.ide.richediting.utils + +import org.fbme.ide.iec61499.repository.PlatformRepository +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.SystemDeclaration +import org.fbme.lib.iec61499.declarations.hierarchies.ResourceFunctionBlockHierarchy +import org.fbme.lib.iec61499.fbnetwork.EntryKind +import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase +import org.fbme.lib.st.STFactory +import org.jetbrains.mps.openapi.model.SModel + +class SystemUtils( + factory: IEC61499Factory, + stFactory: STFactory, + owner: PlatformRepository, +) { + private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) + private val extendedAdapterUtils = ExtendedAdapterUtils(factory, stFactory, owner) + + fun syncApplicationResources( + systemDeclaration: SystemDeclaration, + model: SModel, + ) { + val applicationBlockToMapping: Map = + systemDeclaration.mappings.asSequence() + .mapNotNull { mapping -> + val functionBlock = mapping.applicationFBReference.getTarget()?.functionBlock + val resource = mapping.resourceFBReference.getTarget() + if (functionBlock != null && resource != null) { + functionBlock to resource + } else { + null + } + } + .associate { it } + val revealDeclarationsResults = mutableMapOf() + for (application in systemDeclaration.applications) { + val adapterConnections = application.network.adapterConnections.asSequence() + .filter { ExtendedAdapterUtils.isExtendedAdapterConnection(it) } + .mapNotNull { connection -> (connection.sourceReference.getTarget())?.let { it to connection } } + .groupBy({ it.first }, { it.second }) + for ((plug, connections) in adapterConnections) { + val adapterType = ExtendedAdapterUtils.getPlugExtendedAdapterType(plug.portTarget) ?: continue + val sourceResource = applicationBlockToMapping[plug.functionBlock] ?: continue + val sourceBlockInResource = sourceResource.functionBlock ?: continue + val plugPort = sourceBlockInResource.type.plugPorts.first { it.name == plug.portTarget.name } + val sourceResourceNetwork = sourceResource.resourceHierarchy.resource.network + val needReveal = sourceResourceNetwork.adapterConnections.none { + it.sourceReference.getTarget()?.functionBlock == sourceBlockInResource && + it.sourceReference.getTarget()?.portTarget == plugPort.declaration + } && connections.any { connection -> + val socket = connection.targetReference.getTarget() + sourceResource != applicationBlockToMapping[socket?.functionBlock] + } + if (needReveal) { + val revealDeclarationsResult = revealDeclarationsResults.computeIfAbsent(adapterType.name) { + extendedAdapterUtils.revealDeclarations(adapterType, model) + } + extendedAdapterUtils.revealAdapterWithNet( + revealResult = revealDeclarationsResult, + block = sourceBlockInResource, + port = plugPort, + count = connections.size, + model = model, + ) + } + for (connection in connections) { + val socket = connection.targetReference.getTarget() ?: continue + val targetResource = applicationBlockToMapping[socket.functionBlock] ?: continue + val targetBlockInResource = targetResource.functionBlock ?: continue + val socketPort = targetBlockInResource.type.socketPorts.first { it.name == socket.portTarget.name } + val targetResourceNetwork = targetResource.resourceHierarchy.resource.network + val connectionExists = targetResourceNetwork.adapterConnections.any { + it.targetReference.getTarget()?.functionBlock == targetBlockInResource && + it.targetReference.getTarget()?.portTarget == socketPort.declaration + } + if (!connectionExists) { + if (sourceResource == targetResource) { + targetResourceNetwork.adapterConnections += factoryUtils.createConnection( + source = sourceBlockInResource.getPort(plugPort), + target = targetBlockInResource.getPort(socketPort), + entryKind = EntryKind.ADAPTER, + ) + continue + } + val revealDeclarationsResult = revealDeclarationsResults.computeIfAbsent(adapterType.name) { + extendedAdapterUtils.revealDeclarations(adapterType, model) + } + extendedAdapterUtils.revealAdapterWithNet( + revealResult = revealDeclarationsResult, + block = targetBlockInResource, + port = socketPort, + count = connections.size, + model = model, + ) + } + } + + } + } + } +} \ No newline at end of file diff --git a/code/richediting/src/main/resources/META-INF/plugin.xml b/code/richediting/src/main/resources/META-INF/plugin.xml index 00885315f..b37bb6d4e 100644 --- a/code/richediting/src/main/resources/META-INF/plugin.xml +++ b/code/richediting/src/main/resources/META-INF/plugin.xml @@ -65,6 +65,9 @@ + From 3637a9d592e177dc61b57bd46b216d2f70e08b05 Mon Sep 17 00:00:00 2001 From: klinachev Date: Mon, 15 Apr 2024 02:25:54 +0300 Subject: [PATCH 07/14] Add unit tests --- .../ide/platform/testing/PlatformTestBase.kt | 4 + code/richediting/build.gradle.kts | 12 + .../utils/ExtendedAdapterUtilsTest.kt | 183 ++ .../EA_LeftPublishSubscribeAdapter_0.fbt | 59 + .../EA_RightPublishSubscribeAdapter_1.fbt | 59 + .../EventToNumberAdapter_4.fbt | 57 + .../LeftCompositeBlockWithoutConnections.fbt | 53 + .../NumberToEventAdapter_4.fbt | 45 + .../RightCompositeBlockWithoutConnections.fbt | 33 + .../testRevealAdapter/CompositeBlock.fbt | 25 + .../testRevealAdapter/EA_2_leftSwitch.fbt | 110 + .../testRevealAdapter/EA_2_rightSwitch.fbt | 110 + .../results/testRevealAdapter/EA_2_router.fbt | 73 + .../results/testRevealAdapter/EA_network1.fbt | 36 + .../results/testRevealAdapter/EA_network2.fbt | 36 + .../results/testRevealAdapter/Left_EA.adp | 32 + .../results/testRevealAdapter/Middle_EA.adp | 32 + .../results/testRevealAdapter/Right_EA.adp | 32 + .../testRevealAdapter/RouterAdapter_EA.adp | 34 + .../testRevealDeclarations/EA_network1.fbt | 23 + .../testRevealDeclarations/EA_network2.fbt | 22 + .../testRevealDeclarations/Left_EA.adp | 26 + .../testRevealDeclarations/Middle_EA.adp | 25 + .../testRevealDeclarations/Right_EA.adp | 26 + .../source/publishSubscribes/PUBLISH_5.fbt | 45 + .../source/publishSubscribes/SUBSCRIBE_5.fbt | 45 + .../source/testRevealAdapter/BaseBlock.fbt | 38 + .../testRevealAdapter/CompositeBlock.fbt | 16 + .../CompositeBlockWithoutConnections.fbt | 10 + .../source/testRevealAdapter/EA.eadp | 138 ++ .../source/testRevealDeclarations/EA.eadp | 97 + ...e.ide.iec61499.lang.sandbox.ea_example.mps | 1809 +++++++++++++++++ 32 files changed, 3345 insertions(+) create mode 100644 code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt create mode 100644 code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt create mode 100644 code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt create mode 100644 code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt create mode 100644 code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt create mode 100644 code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt create mode 100644 code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/EA_2_router.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/EA_network1.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/EA_network2.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/Left_EA.adp create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/Middle_EA.adp create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/Right_EA.adp create mode 100644 code/richediting/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp create mode 100644 code/richediting/src/test/resources/results/testRevealDeclarations/EA_network1.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealDeclarations/EA_network2.fbt create mode 100644 code/richediting/src/test/resources/results/testRevealDeclarations/Left_EA.adp create mode 100644 code/richediting/src/test/resources/results/testRevealDeclarations/Middle_EA.adp create mode 100644 code/richediting/src/test/resources/results/testRevealDeclarations/Right_EA.adp create mode 100644 code/richediting/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt create mode 100644 code/richediting/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt create mode 100644 code/richediting/src/test/resources/source/testRevealAdapter/BaseBlock.fbt create mode 100644 code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt create mode 100644 code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt create mode 100644 code/richediting/src/test/resources/source/testRevealAdapter/EA.eadp create mode 100644 code/richediting/src/test/resources/source/testRevealDeclarations/EA.eadp create mode 100644 samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps diff --git a/code/platform/src/main/kotlin/org/fbme/ide/platform/testing/PlatformTestBase.kt b/code/platform/src/main/kotlin/org/fbme/ide/platform/testing/PlatformTestBase.kt index c72827685..c4e74a838 100644 --- a/code/platform/src/main/kotlin/org/fbme/ide/platform/testing/PlatformTestBase.kt +++ b/code/platform/src/main/kotlin/org/fbme/ide/platform/testing/PlatformTestBase.kt @@ -17,6 +17,7 @@ import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.parser.IdentifierLocus import org.fbme.lib.iec61499.parser.RootConverter import org.fbme.lib.st.STFactory +import org.jdom.Document import org.jdom.Element import org.jdom.JDOMException import org.jetbrains.mps.openapi.model.SModel @@ -44,6 +45,9 @@ abstract class PlatformTestBase { fun rootConverterByPath(input: String, config: PlatformConverter.DefaultConfigurationFactory) = createConverter(requireNotNull(this::class.java.getResourceAsStream(input)), config) + fun createDocumentByPath(input: String): Document = + JDOMUtil.loadDocument(requireNotNull(this::class.java.getResourceAsStream(input))) + private fun createConverter(stream: InputStream): RootConverter { return createConverter(stream, null) } diff --git a/code/richediting/build.gradle.kts b/code/richediting/build.gradle.kts index 38f9d91e8..f12384587 100644 --- a/code/richediting/build.gradle.kts +++ b/code/richediting/build.gradle.kts @@ -18,6 +18,11 @@ dependencies { implementation("org.eclipse.elk:org.eclipse.elk.core:0.7.1") implementation("org.eclipse.elk:org.eclipse.elk.graph:0.7.1") + testImplementation(mpsDistribution()) + testImplementation(project(":code:library")) + testImplementation(project(":code:language")) + testImplementation(project(":code:platform")) + mpsImplementation(project(":code:language", "mps")) mpsImplementation(project(":code:platform", "mps")) mpsImplementation(project(":code:scenes", "mps")) @@ -32,6 +37,13 @@ mps { moduleDependency(project(":code:scenes")) } +val test by tasks.getting(Test::class) { + dependsOn( + ":code:library:buildDistPlugin", + "buildDistPlugin" + ) +} + val compileKotlin by tasks.getting(KotlinCompile::class) { kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all") } \ No newline at end of file diff --git a/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt b/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt new file mode 100644 index 000000000..32beeada9 --- /dev/null +++ b/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt @@ -0,0 +1,183 @@ +package org.fbme.ide.richediting.utils + +import jetbrains.mps.smodel.tempmodel.TempModuleOptions +import jetbrains.mps.smodel.tempmodel.TemporaryModels +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.ide.platform.testing.PlatformTestBase +import org.fbme.ide.platform.testing.PlatformTestRunner +import org.fbme.lib.common.Declaration +import org.fbme.lib.iec61499.declarations.CompositeFBTypeDeclaration +import org.fbme.lib.iec61499.declarations.FBTypeDeclaration +import org.fbme.lib.iec61499.stringify.RootDeclarationPrinter +import org.jdom.Document +import org.jdom.output.Format +import org.jdom.output.XMLOutputter +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.mps.openapi.model.SModel +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + + +//@LoadFrom("org.fbme.richediting.lib") +@RunWith(PlatformTestRunner::class) +class ExtendedAdapterUtilsTest : PlatformTestBase() { + private val extendedAdapterUtils = ExtendedAdapterUtils(factory, stFactory, repository, ::getPublishSubscribeBlock) + private val publishSubscribeMap: Map = mapOf( + "PUBLISH_5" to rootConverterByPath("/source/publishSubscribes/PUBLISH_5.fbt").convertFBType(), + "SUBSCRIBE_5" to rootConverterByPath("/source/publishSubscribes/SUBSCRIBE_5.fbt").convertFBType() + ) + + private fun getPublishSubscribeBlock(name: String): FBTypeDeclaration { + return checkNotNull(publishSubscribeMap[name]) + } + + @Test + fun testRevealDeclarations() { + val sourcePath = "/source/testRevealDeclarations" + val resultPath = "/results/testRevealDeclarations" + val extendedAdapterType = rootConverterByPath("$sourcePath/EA.eadp").convertExtendedAdapterType() + val leftBlock = createDocumentByPath("$resultPath/EA_network1.fbt") + val rightBlock = createDocumentByPath("$resultPath/EA_network2.fbt") + val leftAdapter = createDocumentByPath("$resultPath/Left_EA.adp") + val middleAdapter = createDocumentByPath("$resultPath/Middle_EA.adp") + val rightAdapter = createDocumentByPath("$resultPath/Right_EA.adp") + project.repository.modelAccess.runWriteAction { + val model = TemporaryModels.getInstance().create(false, false, "tmp", TempModuleOptions.forDefaultModule()) + model.addRootNode((extendedAdapterType as PlatformElement).node) + val revealDeclarations = extendedAdapterUtils.revealDeclarations(extendedAdapterType, null) + + assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) + assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) + assertEqualStrings(leftAdapter, revealDeclarations.leftAdapter.toDocument()) + assertEqualStrings(middleAdapter, revealDeclarations.middleAdapter.toDocument()) + assertEqualStrings(rightAdapter, revealDeclarations.rightAdapter.toDocument()) + } + } + + @Test + fun testRevealAdapter() { + val sourcePath = "/source/testRevealAdapter" + val resultPath = "/results/testRevealAdapter" + val extendedAdapterType = rootConverterByPath("$sourcePath/EA.eadp").convertExtendedAdapterType() + val example = rootConverterByPath("$sourcePath/CompositeBlock.fbt").convertFBType() + val baseBlock = rootConverterByPath("$sourcePath/BaseBlock.fbt").convertFBType() + val leftBlock = createDocumentByPath("$resultPath/EA_network1.fbt") + val rightBlock = createDocumentByPath("$resultPath/EA_network2.fbt") + val leftAdapter = createDocumentByPath("$resultPath/Left_EA.adp") + val middleAdapter = createDocumentByPath("$resultPath/Middle_EA.adp") + val rightAdapter = createDocumentByPath("$resultPath/Right_EA.adp") + val routerAdapter = createDocumentByPath("$resultPath/RouterAdapter_EA.adp") + + project.repository.modelAccess.runWriteAction { + val model = TemporaryModels.getInstance().create(false, false, "tmp", TempModuleOptions.forDefaultModule()) + model.addRootNode((baseBlock as PlatformElement).node) + model.addRootNode((extendedAdapterType as PlatformElement).node) + model.addRootNode((example as PlatformElement).node) + + val revealDeclarations = extendedAdapterUtils.revealAdapter(extendedAdapterType, model) + + assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) + assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) + assertEqualStrings(leftAdapter, revealDeclarations.leftAdapter.toDocument()) + assertEqualStrings(middleAdapter, revealDeclarations.middleAdapter.toDocument()) + assertEqualStrings(rightAdapter, revealDeclarations.rightAdapter.toDocument()) + assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) + checkNode(model, "$resultPath/CompositeBlock.fbt", "CompositeBlock") + checkNode(model, "$resultPath/EA_2_leftSwitch.fbt", "EA_2_leftSwitch") + checkNode(model, "$resultPath/EA_2_rightSwitch.fbt", "EA_2_rightSwitch") + checkNode(model, "$resultPath/EA_2_router.fbt", "EA_2_router") + } + } + + @Test + fun testRevealAdapterWithNetLeft() { + val sourcePath = "/source/testRevealAdapter" + val resultPath = "/results/testRevealAdapter" + val networkPath = "/results/resourceNetworkBlocks" + val extendedAdapterType = rootConverterByPath("$sourcePath/EA.eadp").convertExtendedAdapterType() + val composite = rootConverterByPath("$sourcePath/CompositeBlockWithoutConnections.fbt").convertFBType() + val baseBlock = rootConverterByPath("$sourcePath/BaseBlock.fbt").convertFBType() + val routerAdapter = createDocumentByPath("$resultPath/RouterAdapter_EA.adp") + + project.repository.modelAccess.runWriteAction { + val model = TemporaryModels.getInstance().create(false, false, "tmp", TempModuleOptions.forDefaultModule()) + model.addRootNode((baseBlock as PlatformElement).node) + model.addRootNode((extendedAdapterType as PlatformElement).node) + model.addRootNode((composite as PlatformElement).node) + model.addRootNode((publishSubscribeMap["PUBLISH_5"] as PlatformElement).node) + model.addRootNode((publishSubscribeMap["SUBSCRIBE_5"] as PlatformElement).node) + val revealDeclarations = extendedAdapterUtils.revealDeclarations(extendedAdapterType, model) + val functionBlockDeclaration = + composite.cast().network.functionBlocks.first { it.name == "BaseBlock1" } + extendedAdapterUtils.revealAdapterWithNet( + revealResult = revealDeclarations, + block = functionBlockDeclaration, + port = functionBlockDeclaration.type.plugPorts[0], + count = 2, + model = model, + ) + + assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) + checkNode(model, "$networkPath/LeftCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") + checkNode(model, "$networkPath/EA_LeftPublishSubscribeAdapter_0.fbt", "EA_LeftPublishSubscribeAdapter_0") + checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") + checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") + } + } + + @Test + fun testRevealAdapterWithNetRight() { + val sourcePath = "/source/testRevealAdapter" + val resultPath = "/results/testRevealAdapter" + val networkPath = "/results/resourceNetworkBlocks" + val extendedAdapterType = rootConverterByPath("$sourcePath/EA.eadp").convertExtendedAdapterType() + val composite = rootConverterByPath("$sourcePath/CompositeBlockWithoutConnections.fbt").convertFBType() + val baseBlock = rootConverterByPath("$sourcePath/BaseBlock.fbt").convertFBType() + val routerAdapter = createDocumentByPath("$resultPath/RouterAdapter_EA.adp") + + project.repository.modelAccess.runWriteAction { + val model = TemporaryModels.getInstance().create(false, false, "tmp", TempModuleOptions.forDefaultModule()) + model.addRootNode((baseBlock as PlatformElement).node) + model.addRootNode((extendedAdapterType as PlatformElement).node) + model.addRootNode((composite as PlatformElement).node) + model.addRootNode((publishSubscribeMap["PUBLISH_5"] as PlatformElement).node) + model.addRootNode((publishSubscribeMap["SUBSCRIBE_5"] as PlatformElement).node) + val revealDeclarations = extendedAdapterUtils.revealDeclarations(extendedAdapterType, model) + val functionBlockDeclaration = + composite.cast().network.functionBlocks.first { it.name == "BaseBlock1" } + extendedAdapterUtils.revealAdapterWithNet( + revealResult = revealDeclarations, + block = functionBlockDeclaration, + port = functionBlockDeclaration.type.socketPorts[0], + count = 2, + model = model, + ) + + assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) + checkNode(model, "$networkPath/RightCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") + checkNode(model, "$networkPath/EA_RightPublishSubscribeAdapter_1.fbt", "EA_RightPublishSubscribeAdapter_1") + checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") + checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") + } + } + + private fun checkNode(model: SModel, path: String, nodeName: String) { + val example = createDocumentByPath(path) + val nodes = model.rootNodes.toList() + assertEqualStrings( + example, + assertNotNull(repository.adapterOrNull( + nodes.first { it.name == nodeName } + )).toDocument() + ) + } +} + +private fun assertEqualStrings(expected: Document, actual: Document) = + assertEquals(toString(expected), toString(actual)) + +private fun toString(document: Document): String = XMLOutputter(Format.getPrettyFormat()).outputString(document) + +private fun Declaration.toDocument(): Document = RootDeclarationPrinter(this).print() diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt new file mode 100644 index 000000000..20ca70c22 --- /dev/null +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt new file mode 100644 index 000000000..cb80f7d8a --- /dev/null +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt new file mode 100644 index 000000000..35145fe72 --- /dev/null +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt new file mode 100644 index 000000000..0c9e90377 --- /dev/null +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt new file mode 100644 index 000000000..2f81cd242 --- /dev/null +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt new file mode 100644 index 000000000..edf07f29e --- /dev/null +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt b/code/richediting/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt new file mode 100644 index 000000000..57effde52 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt b/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt new file mode 100644 index 000000000..60d6a57a1 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt b/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt new file mode 100644 index 000000000..547fab693 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_router.fbt b/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_router.fbt new file mode 100644 index 000000000..887d26287 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_router.fbt @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_network1.fbt b/code/richediting/src/test/resources/results/testRevealAdapter/EA_network1.fbt new file mode 100644 index 000000000..2f1b2fd23 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/EA_network1.fbt @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_network2.fbt b/code/richediting/src/test/resources/results/testRevealAdapter/EA_network2.fbt new file mode 100644 index 000000000..0aec0bddb --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/EA_network2.fbt @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/Left_EA.adp b/code/richediting/src/test/resources/results/testRevealAdapter/Left_EA.adp new file mode 100644 index 000000000..58a51047e --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/Left_EA.adp @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/Middle_EA.adp b/code/richediting/src/test/resources/results/testRevealAdapter/Middle_EA.adp new file mode 100644 index 000000000..3dacb3e9b --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/Middle_EA.adp @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/Right_EA.adp b/code/richediting/src/test/resources/results/testRevealAdapter/Right_EA.adp new file mode 100644 index 000000000..23e9bc973 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/Right_EA.adp @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp b/code/richediting/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp new file mode 100644 index 000000000..cc5db7046 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network1.fbt b/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network1.fbt new file mode 100644 index 000000000..0a579578d --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network1.fbt @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network2.fbt b/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network2.fbt new file mode 100644 index 000000000..d0209eafe --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network2.fbt @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/Left_EA.adp b/code/richediting/src/test/resources/results/testRevealDeclarations/Left_EA.adp new file mode 100644 index 000000000..de082465d --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealDeclarations/Left_EA.adp @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/Middle_EA.adp b/code/richediting/src/test/resources/results/testRevealDeclarations/Middle_EA.adp new file mode 100644 index 000000000..848389ead --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealDeclarations/Middle_EA.adp @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/Right_EA.adp b/code/richediting/src/test/resources/results/testRevealDeclarations/Right_EA.adp new file mode 100644 index 000000000..e6acc4f19 --- /dev/null +++ b/code/richediting/src/test/resources/results/testRevealDeclarations/Right_EA.adp @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt b/code/richediting/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt new file mode 100644 index 000000000..6cc72c38b --- /dev/null +++ b/code/richediting/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt b/code/richediting/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt new file mode 100644 index 000000000..fb0c0dead --- /dev/null +++ b/code/richediting/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/BaseBlock.fbt b/code/richediting/src/test/resources/source/testRevealAdapter/BaseBlock.fbt new file mode 100644 index 000000000..686dada82 --- /dev/null +++ b/code/richediting/src/test/resources/source/testRevealAdapter/BaseBlock.fbt @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt b/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt new file mode 100644 index 000000000..1adf2e90f --- /dev/null +++ b/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt b/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt new file mode 100644 index 000000000..9d3dd18ff --- /dev/null +++ b/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/EA.eadp b/code/richediting/src/test/resources/source/testRevealAdapter/EA.eadp new file mode 100644 index 000000000..f86a265b3 --- /dev/null +++ b/code/richediting/src/test/resources/source/testRevealAdapter/EA.eadp @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/richediting/src/test/resources/source/testRevealDeclarations/EA.eadp b/code/richediting/src/test/resources/source/testRevealDeclarations/EA.eadp new file mode 100644 index 000000000..422fb0afc --- /dev/null +++ b/code/richediting/src/test/resources/source/testRevealDeclarations/EA.eadp @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps new file mode 100644 index 000000000..12569e350 --- /dev/null +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps @@ -0,0 +1,1809 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2636d715a7f9ac35958e7c2ac4ad5343ba0ea51f Mon Sep 17 00:00:00 2001 From: klinachev Date: Mon, 15 Apr 2024 02:53:04 +0300 Subject: [PATCH 08/14] Fix tests --- build-bootstrap.xml | 3 -- .../actions/GenerateAdapterRouterAction.kt | 4 +-- .../actions/RevealExtendedAdapterAction.kt | 20 +++++++++--- .../richediting/utils/ExtendedAdapterUtils.kt | 2 +- .../utils/ExtendedAdapterUtilsTest.kt | 8 ++--- ...er_4.fbt => EA_EventToNumberAdapter_4.fbt} | 2 +- .../EA_LeftPublishSubscribeAdapter_0.fbt | 31 +++++++++---------- ...er_4.fbt => EA_NumberToEventAdapter_4.fbt} | 2 +- .../EA_RightPublishSubscribeAdapter_1.fbt | 31 +++++++++---------- 9 files changed, 55 insertions(+), 48 deletions(-) rename code/richediting/src/test/resources/results/resourceNetworkBlocks/{EventToNumberAdapter_4.fbt => EA_EventToNumberAdapter_4.fbt} (98%) rename code/richediting/src/test/resources/results/resourceNetworkBlocks/{NumberToEventAdapter_4.fbt => EA_NumberToEventAdapter_4.fbt} (97%) diff --git a/build-bootstrap.xml b/build-bootstrap.xml index 2416f0bd9..4f552809e 100644 --- a/build-bootstrap.xml +++ b/build-bootstrap.xml @@ -148,9 +148,6 @@ - - - diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt index 2070ef058..81790e9ed 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.ui.DialogWrapper import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.* import jetbrains.mps.ide.actions.MPSCommonDataKeys -import org.fbme.ide.richediting.utils.SwitchGenerator +import org.fbme.ide.richediting.utils.AdapterSwitchGenerator import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.fbnetwork.* import org.fbme.lib.st.expressions.* @@ -50,7 +50,7 @@ class GenerateAdapterRouterAction : AnAction(), DumbAware { val adapterTypeDeclaration = repository.adapter(node) val adapterName = adapterTypeDeclaration.name val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) - val switchGenerator = SwitchGenerator(repository.iec61499Factory, repository.stFactory) + val switchGenerator = AdapterSwitchGenerator(repository.iec61499Factory, repository.stFactory) switchGenerator.generateRouter( adapterName, model, diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt index 18c1d43c1..2f7cab3e5 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt @@ -6,8 +6,10 @@ import com.intellij.openapi.project.DumbAware import jetbrains.mps.extapi.persistence.ModelFactoryService import jetbrains.mps.ide.actions.MPSCommonDataKeys import jetbrains.mps.ide.dialogs.project.creation.ModelCreateHelper +import jetbrains.mps.model.ModelDeleteHelper import jetbrains.mps.project.AbstractModule import jetbrains.mps.project.MPSProject +import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider import org.fbme.ide.richediting.utils.ExtendedAdapterUtils import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jetbrains.mps.openapi.model.SModelName @@ -32,17 +34,27 @@ class RevealExtendedAdapterAction : AnAction(), DumbAware { val extendedAdapter = node?.let { repository.adapterOrNull(node) } ?: return@executeWriteActionInEditor - val extendedAdapterUtils = ExtendedAdapterUtils(factory, repository.stFactory, project) + val extendedAdapterUtils = ExtendedAdapterUtils( + factory = factory, + stFactory = repository.stFactory, + owner = PlatformRepositoryProvider.getInstance(project), + ) + val newSModelName = SModelName("${model.name}_extensions_revealed") + val existedModule = model.module.models.firstOrNull { it.name == newSModelName } + if (existedModule != null) { + ModelDeleteHelper(existedModule).delete() + } val modelCopy = ModelCreateHelper( project, model.module as AbstractModule, - SModelName("${model.name}_clone"), + newSModelName, model.modelRoot, project.getComponent(ModelFactoryService::class.java).factoryTypes.first(), ) .setClone(model, false) .createModel() - extendedAdapterUtils.revealAdapter(extendedAdapter, modelCopy) + val nodeCopy = modelCopy.rootNodes.first { it.name == extendedAdapter.name } + val extendedAdapterCopy = repository.adapter(nodeCopy) + extendedAdapterUtils.revealAdapter(extendedAdapterCopy, modelCopy) } - } \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt index 4b6bddfc0..cc9f9bb5a 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt @@ -535,7 +535,7 @@ class ExtendedAdapterUtils( typeDescriptor.eventOutputPorts.size ) { number -> createNumberToEventConverter( - name = "NumberToEventAdapter_$number", + name = "${name}_NumberToEventAdapter_$number", inputCount = number, ).also { currentModel.addRootNodes(it, virtualPackage = VIRTUAL_PACKAGE_NAME) } } diff --git a/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt b/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt index 32beeada9..e52792ba2 100644 --- a/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt +++ b/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt @@ -122,8 +122,8 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) checkNode(model, "$networkPath/LeftCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") checkNode(model, "$networkPath/EA_LeftPublishSubscribeAdapter_0.fbt", "EA_LeftPublishSubscribeAdapter_0") - checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") - checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") + checkNode(model, "$networkPath/EA_EventToNumberAdapter_4.fbt", "EA_EventToNumberAdapter_4") + checkNode(model, "$networkPath/EA_NumberToEventAdapter_4.fbt", "EA_NumberToEventAdapter_4") } } @@ -158,8 +158,8 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) checkNode(model, "$networkPath/RightCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") checkNode(model, "$networkPath/EA_RightPublishSubscribeAdapter_1.fbt", "EA_RightPublishSubscribeAdapter_1") - checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") - checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") + checkNode(model, "$networkPath/EA_EventToNumberAdapter_4.fbt", "EA_EventToNumberAdapter_4") + checkNode(model, "$networkPath/EA_NumberToEventAdapter_4.fbt", "EA_NumberToEventAdapter_4") } } diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt similarity index 98% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt rename to code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt index 35145fe72..9f2b8b746 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt @@ -1,7 +1,7 @@ - + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt index 20ca70c22..a68011b8c 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt @@ -28,11 +28,11 @@ - - + + - - + + @@ -43,17 +43,16 @@ - - - - - - - - - - + + + + + + + + + + - - + \ No newline at end of file diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt similarity index 97% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt rename to code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt index 2f81cd242..fae00ce7f 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt @@ -1,7 +1,7 @@ - + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt index cb80f7d8a..f19398ebc 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt +++ b/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt @@ -28,11 +28,11 @@ - - + + - - + + @@ -43,17 +43,16 @@ - - - - - - - - - - + + + + + + + + + + - - + \ No newline at end of file From acf4a589adebb978f3b86cf894279ec8e6bd0f74 Mon Sep 17 00:00:00 2001 From: klinachev Date: Thu, 2 May 2024 20:05:40 +0300 Subject: [PATCH 09/14] Move extension reveal algorithms into separate module --- .../adapter}/AdapterSwitchGenerator.kt | 233 +- .../adapter}/ExtendedAdapterUtils.kt | 83 +- .../fbme/extensions/adapter}/SystemUtils.kt | 3 +- .../utils/FBInterfaceDeclarationUtils.kt | 2 +- .../extensions}/utils/IEC61499FactoryUtils.kt | 29 +- .../fbme/extensions}/utils/STFactoryUtils.kt | 7 +- .../utils/ExtendedAdapterUtilsTest.kt | 16 +- .../EA_LeftPublishSubscribeAdapter.fbt} | 30 +- .../EA_RightPublishSubscribeAdapter.fbt} | 30 +- .../EventToNumberAdapter_4.fbt} | 2 +- .../LeftCompositeBlockWithoutConnections.fbt | 30 +- .../NumberToEventAdapter_4.fbt} | 2 +- .../RightCompositeBlockWithoutConnections.fbt | 2 +- .../testRevealAdapter/CompositeBlock.fbt | 0 .../testRevealAdapter/EA_2_leftSwitch.fbt | 0 .../testRevealAdapter/EA_2_rightSwitch.fbt | 0 .../results/testRevealAdapter/EA_2_router.fbt | 0 .../results/testRevealAdapter/EA_network1.fbt | 0 .../results/testRevealAdapter/EA_network2.fbt | 0 .../results/testRevealAdapter/Left_EA.adp | 0 .../results/testRevealAdapter/Middle_EA.adp | 0 .../results/testRevealAdapter/Right_EA.adp | 0 .../testRevealAdapter/RouterAdapter_EA.adp | 0 .../testRevealDeclarations/EA_network1.fbt | 0 .../testRevealDeclarations/EA_network2.fbt | 0 .../testRevealDeclarations/Left_EA.adp | 0 .../testRevealDeclarations/Middle_EA.adp | 0 .../testRevealDeclarations/Right_EA.adp | 0 .../source/publishSubscribes/PUBLISH_5.fbt | 0 .../source/publishSubscribes/SUBSCRIBE_5.fbt | 0 .../source/testRevealAdapter/BaseBlock.fbt | 0 .../testRevealAdapter/CompositeBlock.fbt | 0 .../CompositeBlockWithoutConnections.fbt | 0 .../source/testRevealAdapter/EA.eadp | 0 .../source/testRevealDeclarations/EA.eadp | 0 code/richediting/build.gradle.kts | 14 +- .../actions/GenerateAdapterRouterAction.kt | 2 +- .../actions/RevealExtendedAdapterAction.kt | 2 +- .../actions/SyncSystemResources.kt | 2 +- .../fbnetwork/FBConnectionController.kt | 2 +- ....fbme.ide.iec61499.lang.sandbox.blinky.mps | 4651 ++--------------- settings.gradle.kts | 1 + 42 files changed, 483 insertions(+), 4660 deletions(-) rename code/{richediting/src/main/kotlin/org/fbme/ide/richediting/utils => extensions/src/main/kotlin/org/fbme/extensions/adapter}/AdapterSwitchGenerator.kt (55%) rename code/{richediting/src/main/kotlin/org/fbme/ide/richediting/utils => extensions/src/main/kotlin/org/fbme/extensions/adapter}/ExtendedAdapterUtils.kt (94%) rename code/{richediting/src/main/kotlin/org/fbme/ide/richediting/utils => extensions/src/main/kotlin/org/fbme/extensions/adapter}/SystemUtils.kt (98%) rename code/{richediting/src/main/kotlin/org/fbme/ide/richediting => extensions/src/main/kotlin/org/fbme/extensions}/utils/FBInterfaceDeclarationUtils.kt (99%) rename code/{richediting/src/main/kotlin/org/fbme/ide/richediting => extensions/src/main/kotlin/org/fbme/extensions}/utils/IEC61499FactoryUtils.kt (80%) rename code/{richediting/src/main/kotlin/org/fbme/ide/richediting => extensions/src/main/kotlin/org/fbme/extensions}/utils/STFactoryUtils.kt (89%) rename code/{richediting/src/test/kotlin/org/fbme/ide/richediting => extensions/src/test/kotlin/org/fbme/ide/extensions}/utils/ExtendedAdapterUtilsTest.kt (94%) rename code/{richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt => extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt} (55%) rename code/{richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt => extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt} (55%) rename code/{richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt => extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt} (98%) rename code/{richediting => extensions}/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt (79%) rename code/{richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt => extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt} (97%) rename code/{richediting => extensions}/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt (98%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/EA_2_router.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/EA_network1.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/EA_network2.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/Left_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/Middle_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/Right_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealDeclarations/EA_network1.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealDeclarations/EA_network2.fbt (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealDeclarations/Left_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealDeclarations/Middle_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/results/testRevealDeclarations/Right_EA.adp (100%) rename code/{richediting => extensions}/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt (100%) rename code/{richediting => extensions}/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt (100%) rename code/{richediting => extensions}/src/test/resources/source/testRevealAdapter/BaseBlock.fbt (100%) rename code/{richediting => extensions}/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt (100%) rename code/{richediting => extensions}/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt (100%) rename code/{richediting => extensions}/src/test/resources/source/testRevealAdapter/EA.eadp (100%) rename code/{richediting => extensions}/src/test/resources/source/testRevealDeclarations/EA.eadp (100%) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterSwitchGenerator.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt similarity index 55% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterSwitchGenerator.kt rename to code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt index 54a8bf1e6..554292028 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/AdapterSwitchGenerator.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt @@ -1,12 +1,12 @@ -package org.fbme.ide.richediting.utils +package org.fbme.extensions.adapter -import org.fbme.lib.common.Identifier +import org.fbme.extensions.utils.IEC61499FactoryUtils +import org.fbme.extensions.utils.STFactoryUtils import org.fbme.lib.common.StringIdentifier import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.ecc.StateAction import org.fbme.lib.iec61499.ecc.StateDeclaration -import org.fbme.lib.iec61499.fbnetwork.EntryKind import org.fbme.lib.iec61499.fbnetwork.FBNetwork import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase @@ -20,24 +20,6 @@ class AdapterSwitchGenerator( ) { private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) private val stFactoryUtils: STFactoryUtils = STFactoryUtils(stFactory) - private val createdEvents = mutableListOf() - private val createdParams = mutableListOf() - - private data class EventCopyAndConnectObject( - val sourceEvent: EventDeclaration, - val createdEvent: EventDeclaration, - val input: Boolean, - val source: FunctionBlockDeclarationBase?, - val target: FunctionBlockDeclarationBase?, - ) - - private data class ParameterCopyAndConnectObject( - val sourceParam: ParameterDeclaration, - val createdParam: ParameterDeclaration, - val input: Boolean, - val source: FunctionBlockDeclarationBase?, - val target: FunctionBlockDeclarationBase?, - ) fun generateRouter( name: String, @@ -133,9 +115,16 @@ class AdapterSwitchGenerator( outputToInput = false, ).forEach { (sourceEvent, createdEvent) -> createdEvent.name += "_$i" + for (association in createdEvent.associations) { + val oldName = association.parameterReference.getTarget()?.name + if (oldName != null) { + association.parameterReference.setTargetName(oldName + "_$i") + } + } addState( switchDeclaration = switchDeclaration, start = startState, + name = createdEvent.name, inputEventDeclaration = checkNotNull(inputEvents[sourceEvent.name]?.second), outputEventDeclaration = createdEvent, assignableToVariableParameters = inputParameters.zip(parametersAndCopies.map { it.second }), @@ -197,9 +186,16 @@ class AdapterSwitchGenerator( network = network, ).forEach { (sourceEvent, createdEvent) -> createdEvent.name += "_$i" + for (association in createdEvent.associations) { + val oldName = association.parameterReference.getTarget()?.name + if (oldName != null) { + association.parameterReference.setTargetName(oldName + "_$i") + } + } addState( switchDeclaration = switchDeclaration, start = startState, + name = createdEvent.name, inputEventDeclaration = createdEvent, outputEventDeclaration = checkNotNull(outputEvents[sourceEvent.name]?.second), assignableToVariableParameters = parametersAndCopies.map { it.second }.zip(outputParameters), @@ -212,140 +208,10 @@ class AdapterSwitchGenerator( return switchBlock } - private fun addSocketPlugsSwitch( - adapterName: String, - model: SModel, - root: CompositeFBTypeDeclaration, - socket: SocketDeclaration, - plugs: List, - socketToPlug: Boolean, - routerName: String, - virtualPackage: String? = null, - ): FunctionBlockDeclaration { - createdEvents.clear() - createdParams.clear() - - val switchFBIdentifier = StringIdentifier(adapterName) - val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) - val switchBlock = factoryUtils.addFunctionalBlock(switchDeclaration, root.network) - - val socketAdapterType = checkNotNull(socket.typeReference.getTarget()) - val eventsToCopy = if (socketToPlug) socketAdapterType.outputEvents else socketAdapterType.inputEvents - val paramsToCopy = - (if (socketToPlug) socketAdapterType.outputParameters else socketAdapterType.inputParameters) -// .filter { it.name != routerName } - createConnections( - sourceEvents = eventsToCopy, - sourceParameters = paramsToCopy, - switchType = switchDeclaration, - sourceBlockDeclaration = socket, - targetBlockDeclaration = switchBlock, - input = socketToPlug, - ) - for (i in plugs.indices) { - val plugDeclaration = plugs[i] - createConnections( - sourceEvents = eventsToCopy, - sourceParameters = paramsToCopy, - switchType = switchDeclaration, - sourceBlockDeclaration = plugDeclaration, - targetBlockDeclaration = switchBlock, - input = !socketToPlug, - nameSuffix = "_$i", - ) - } - configureEcc( - switchDeclaration = switchDeclaration, - eventDeclarations = createdEvents.groupBy { it.sourceEvent.name } - .flatMap { (_, value) -> - val (inputs, outputs) = value.partition { it.input } - inputs.map { input -> input.createdEvent to outputs.map { it.createdEvent } } - }, - parameterDeclarations = createdParams.map { it.sourceParam to it.createdParam }, - createdParams.firstOrNull { it.sourceParam.name == routerName }?.sourceParam, - ) - - model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) - createdEvents.asSequence() - .map { it.toNetworkConnection() } - .toCollection(root.network.eventConnections) - createdParams.asSequence() - .map { it.toNetworkConnection() } - .toCollection(root.network.dataConnections) - - return switchBlock - } - - private fun createConnections( - sourceEvents: List, - sourceParameters: List, - switchType: BasicFBTypeDeclaration, - sourceBlockDeclaration: FunctionBlockDeclarationBase?, - targetBlockDeclaration: FunctionBlockDeclarationBase?, - input: Boolean, - nameSuffix: String? = null, - ) { - val nameToParameterDeclaration = sourceParameters.associate { sourceDeclaration -> - val newDeclaration = sourceDeclaration.copy(nameSuffix = nameSuffix) - sourceDeclaration.identifier to ParameterCopyAndConnectObject( - sourceParam = sourceDeclaration, - createdParam = newDeclaration, - input = input, - source = sourceBlockDeclaration, - target = targetBlockDeclaration, - ) - } - val nameToCreatedParameterDeclaration = nameToParameterDeclaration.mapValues { it.value.createdParam } - val newEventDeclarations = sourceEvents.map { eventDeclaration -> - val newDeclaration = eventDeclaration.copy( - nameSuffix = nameSuffix, - nameToParameterDeclaration = nameToCreatedParameterDeclaration - ) - EventCopyAndConnectObject( - sourceEvent = eventDeclaration, - createdEvent = newDeclaration, - input = input, - source = sourceBlockDeclaration, - target = targetBlockDeclaration, - ) - } - createdEvents += newEventDeclarations - createdParams += nameToParameterDeclaration.values - val eventDeclarations = if (input) switchType.inputEvents else switchType.outputEvents - eventDeclarations += newEventDeclarations.map { it.createdEvent } - val paramsDeclarations = if (input) switchType.inputParameters else switchType.outputParameters - paramsDeclarations += nameToCreatedParameterDeclaration.values - } - - private fun configureEcc( - switchDeclaration: BasicFBTypeDeclaration, - eventDeclarations: List>>, - parameterDeclarations: List>, - routerParameterDeclaration: ParameterDeclaration?, - ) { - val startState = factory.createStateDeclaration(StringIdentifier("Start")) - switchDeclaration.ecc.states += startState - for (i in eventDeclarations.indices) { - val (inputEvent, outputEvents) = eventDeclarations[i] - for (j in outputEvents.indices) { - val outputEvent = outputEvents[j] - addState( - switchDeclaration = switchDeclaration, - start = startState, - inputEventDeclaration = inputEvent, - outputEventDeclaration = outputEvent, - assignableToVariableParameters = parameterDeclarations, - number = j, - outputRouteVariable = routerParameterDeclaration, - inputRouteVariable = null, - ) - } - } - } - private fun addState( switchDeclaration: BasicFBTypeDeclaration, start: StateDeclaration, + name: String, inputEventDeclaration: EventDeclaration, outputEventDeclaration: EventDeclaration, assignableToVariableParameters: List>, @@ -354,7 +220,7 @@ class AdapterSwitchGenerator( inputRouteVariable: VariableDeclaration?, ): StateAction { val state = factory.createStateDeclaration( - StringIdentifier(outputEventDeclaration.name + "_state") + StringIdentifier(name + "_state") ) switchDeclaration.ecc.states += state val backTransition = factory.createStateTransition() @@ -375,7 +241,7 @@ class AdapterSwitchGenerator( val stateAction = factory.createStateAction() val algorithmDeclaration = factory.createAlgorithmDeclaration( - StringIdentifier(outputEventDeclaration.name + "_algorithm") + StringIdentifier(name + "_algorithm") ) val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST) for ((assignable, variable) in assignableToVariableParameters) { @@ -395,61 +261,4 @@ class AdapterSwitchGenerator( switchDeclaration.algorithms += algorithmDeclaration return stateAction } - - private fun EventCopyAndConnectObject.toNetworkConnection() = if (input) factoryUtils.createNetworkConnection( - kind = EntryKind.EVENT, - source = source, - sourcePortTarget = sourceEvent, - target = target, - targetPortTarget = createdEvent - ) else factoryUtils.createNetworkConnection( - kind = EntryKind.EVENT, - source = target, - sourcePortTarget = createdEvent, - target = source, - targetPortTarget = sourceEvent - ) - - private fun ParameterCopyAndConnectObject.toNetworkConnection() = if (input) factoryUtils.createNetworkConnection( - kind = EntryKind.DATA, - source = source, - sourcePortTarget = sourceParam, - target = target, - targetPortTarget = createdParam - ) else factoryUtils.createNetworkConnection( - kind = EntryKind.DATA, - source = target, - sourcePortTarget = createdParam, - target = source, - targetPortTarget = sourceParam - ) - - private fun EventDeclaration.copy( - nameSuffix: String?, - nameToParameterDeclaration: Map - ): EventDeclaration { - val declaration = factory.createEventDeclaration(identifier) - declaration.name = name.withSuffix(nameSuffix) - declaration.associations.addAll(associations.map { eventAssociation -> - val association = factory.createEventAssociation() - eventAssociation.parameterReference.getTarget() - ?.let { nameToParameterDeclaration[it.identifier] } - ?.run { association.parameterReference.setTarget(this) } - association - }) - return declaration - } - - private fun ParameterDeclaration.copy(nameSuffix: String?): ParameterDeclaration { - val declaration = factory.createParameterDeclaration(identifier) - declaration.name = name.withSuffix(nameSuffix) - declaration.type = type - return declaration - } - - private fun String.withSuffix(nameSuffix: String?) = if (nameSuffix == null) { - this - } else { - this + nameSuffix - } -} \ No newline at end of file +} diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt similarity index 94% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt rename to code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt index cc9f9bb5a..dd65efbda 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt @@ -1,7 +1,10 @@ -package org.fbme.ide.richediting.utils +package org.fbme.extensions.adapter import jetbrains.mps.smodel.ModelImports import jetbrains.mps.smodel.SNodeUtil +import org.fbme.extensions.utils.FBInterfaceDeclarationUtils +import org.fbme.extensions.utils.IEC61499FactoryUtils +import org.fbme.extensions.utils.STFactoryUtils import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.ide.iec61499.repository.PlatformRepository import org.fbme.lib.common.Declaration @@ -14,8 +17,6 @@ import org.fbme.lib.iec61499.descriptors.FBPortDescriptor import org.fbme.lib.iec61499.fbnetwork.* import org.fbme.lib.st.STFactory import org.fbme.lib.st.expressions.BinaryOperation -import org.fbme.lib.st.expressions.Literal -import org.fbme.lib.st.expressions.LiteralKind import org.fbme.lib.st.types.ElementaryType import org.jetbrains.mps.openapi.model.SModel @@ -30,9 +31,9 @@ class ExtendedAdapterUtils( private val switchGenerator = AdapterSwitchGenerator(factory, stFactory) private val numberToEventFbTypes: MutableMap = mutableMapOf() private val eventToNumberFbTypes: MutableMap = mutableMapOf() + private fun getPackageName(name: String) = "generated/$name" companion object { - private const val VIRTUAL_PACKAGE_NAME = "generated" fun isExtendedAdapterConnection(connection: FBNetworkConnection) = getSourceExtendedAdapterType(connection) != null && @@ -88,6 +89,12 @@ class ExtendedAdapterUtils( modelToCheck = currentModel, identifiersToRevealResult = identifiersToRevealResult, ) + for (revealResult in declarationsResults) { + val node = (revealResult.extendedAdapter as? PlatformElement)?.node + if (node != null) { + currentModel.removeRootNode(node) + } + } return revealDeclarations } @@ -261,7 +268,7 @@ class ExtendedAdapterUtils( rightAdapter, leftBlock, rightBlock, - virtualPackage = VIRTUAL_PACKAGE_NAME, + virtualPackage = getPackageName(extendedAdapter.name), ) return RevealDeclarationsResult( extendedAdapter = extendedAdapter, @@ -371,7 +378,7 @@ class ExtendedAdapterUtils( outputsCount = connectionsCount, outputRouterName = outputRouter.name, inputRouterName = adapterType.inputRouter?.name, - virtualPackage = VIRTUAL_PACKAGE_NAME, + virtualPackage = getPackageName(adapterType.name), ) } } @@ -406,23 +413,24 @@ class ExtendedAdapterUtils( val adapterType = revealResult.extendedAdapter val leftPublishSubscribeAdapter = revealResult.leftPublishSubscribeAdapter ?: run { val leftCompositeFBType = factory.createCompositeFBTypeDeclaration( - StringIdentifier("${adapterType.name}_LeftPublishSubscribeAdapter_$number") + StringIdentifier("${adapterType.name}_LeftPublishSubscribeAdapter") ) val socket = factory.createSocketDeclaration(StringIdentifier("socket")) socket.typeReference.setTarget(revealResult.middleAdapter) leftCompositeFBType.sockets += socket - model.addRootNodes(leftCompositeFBType, virtualPackage = VIRTUAL_PACKAGE_NAME) + model.addRootNodes(leftCompositeFBType, virtualPackage = getPackageName(adapterType.name)) createPublishSubscribeAdapter( compositeFBType = leftCompositeFBType, declaration = socket, currentModel = model, - name = adapterType.name, + packageName = adapterType.name, ) } revealResult.leftPublishSubscribeAdapter = leftPublishSubscribeAdapter val leftPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( blockType = leftPublishSubscribeAdapter, network = network, + name = "${leftPublishSubscribeAdapter.name}_$number", ) addPublishSubscribeBlocks( source = leftPublishSubscribeAdapterBlock, @@ -463,23 +471,24 @@ class ExtendedAdapterUtils( val adapterType = revealResult.extendedAdapter val rightPublishSubscribeAdapter = revealResult.rightPublishSubscribeAdapter ?: run { val rightCompositeFBType = factory.createCompositeFBTypeDeclaration( - StringIdentifier("${adapterType.name}_RightPublishSubscribeAdapter_$number") + StringIdentifier("${adapterType.name}_RightPublishSubscribeAdapter") ) val plug = factory.createPlugDeclaration(StringIdentifier("plug")) plug.typeReference.setTarget(revealResult.middleAdapter) rightCompositeFBType.plugs += plug - model.addRootNodes(rightCompositeFBType, virtualPackage = VIRTUAL_PACKAGE_NAME) + model.addRootNodes(rightCompositeFBType, virtualPackage = getPackageName(adapterType.name)) createPublishSubscribeAdapter( compositeFBType = rightCompositeFBType, declaration = plug, currentModel = model, - name = adapterType.name, + packageName = adapterType.name ) } revealResult.rightPublishSubscribeAdapter = rightPublishSubscribeAdapter val rightPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( blockType = rightPublishSubscribeAdapter, network = network, + name = "${rightPublishSubscribeAdapter.name}_$number", ) addPublishSubscribeBlocks( source = rightPublishSubscribeAdapterBlock, @@ -516,7 +525,7 @@ class ExtendedAdapterUtils( compositeFBType: CompositeFBTypeDeclaration, declaration: FunctionBlockDeclarationBase, currentModel: SModel, - name: String, + packageName: String, ): CompositeFBTypeDeclaration { val typeDescriptor = declaration.type @@ -524,9 +533,9 @@ class ExtendedAdapterUtils( typeDescriptor.eventOutputPorts.size ) { number -> createEventToNumberConverter( - name = "${name}_EventToNumberAdapter_$number", + name = "EventToNumberAdapter_$number", inputCount = number, - ).also { currentModel.addRootNodes(it, virtualPackage = VIRTUAL_PACKAGE_NAME) } + ).also { currentModel.addRootNodes(it, virtualPackage = getPackageName(packageName)) } } val eventToNumberBlock = factoryUtils.addFunctionalBlock(eventToNumberFbType, compositeFBType.network) @@ -535,9 +544,9 @@ class ExtendedAdapterUtils( typeDescriptor.eventOutputPorts.size ) { number -> createNumberToEventConverter( - name = "${name}_NumberToEventAdapter_$number", + name = "NumberToEventAdapter_$number", inputCount = number, - ).also { currentModel.addRootNodes(it, virtualPackage = VIRTUAL_PACKAGE_NAME) } + ).also { currentModel.addRootNodes(it, virtualPackage = getPackageName(packageName)) } } val numberToEventBlock = factoryUtils.addFunctionalBlock(numberToEventFbType, compositeFBType.network) @@ -624,21 +633,17 @@ class ExtendedAdapterUtils( val parameterOutput = factory.createParameterDeclaration(StringIdentifier("I_E_number")) parameterOutput.type = ElementaryType.INT basicFbType.outputParameters += parameterOutput + val outputAssociation = factory.createEventAssociation() + outputAssociation.parameterReference.setTarget(parameterOutput) + outputEvent.associations += outputAssociation val start = factory.createStateDeclaration(StringIdentifier("Start")) basicFbType.ecc.states += start for (i in basicFbType.inputEvents.indices) { val event = basicFbType.inputEvents[i] val state = factory.createStateDeclaration(StringIdentifier(event.name)) basicFbType.ecc.states += state - val startToState = factory.createStateTransition() - basicFbType.ecc.transitions += startToState - startToState.sourceReference.setTarget(start) - startToState.targetReference.setTarget(state) - startToState.condition.eventReference.setFQName(event.name) - val stateToStart = factory.createStateTransition() - basicFbType.ecc.transitions += stateToStart - stateToStart.sourceReference.setTarget(state) - stateToStart.targetReference.setTarget(start) + basicFbType.ecc.transitions += factoryUtils.createStateTransition(start, state, event) + basicFbType.ecc.transitions += factoryUtils.createStateTransition(state, start) val stateAction = factory.createStateAction() state.actions += stateAction @@ -648,15 +653,11 @@ class ExtendedAdapterUtils( ) val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST) algorithmDeclaration.body = algorithmBody - val assignment = stFactory.createAssignmentStatement() - val variableReference = stFactory.createVariableReference() - variableReference.reference.setTarget(parameterOutput) - assignment.variable = variableReference - val literal = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal - literal.value = i - assignment.expression = literal - - algorithmBody.statements += assignment + + algorithmBody.statements += stFactoryUtils.createAssign( + variable = parameterOutput, + assignable = stFactoryUtils.createIntLiteral(i), + ) stateAction.algorithm.setTarget(algorithmDeclaration) basicFbType.algorithms += algorithmDeclaration } @@ -675,6 +676,9 @@ class ExtendedAdapterUtils( val parameterInput = factory.createParameterDeclaration(StringIdentifier("O_E_number")) parameterInput.type = ElementaryType.INT basicFbType.inputParameters += parameterInput + val inputAssociation = factory.createEventAssociation() + inputAssociation.parameterReference.setTarget(parameterInput) + inputEvent.associations += inputAssociation val start = factory.createStateDeclaration(StringIdentifier("Start")) basicFbType.ecc.states += start for (i in basicFbType.outputEvents.indices) { @@ -688,12 +692,8 @@ class ExtendedAdapterUtils( startToState.targetReference.setTarget(state) val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) - val numberLiteral = stFactory.createLiteral(LiteralKind.DEC_INT) as Literal - numberLiteral.value = i - equality.rightExpression = numberLiteral - val variableReference = stFactory.createVariableReference() - variableReference.reference.setTarget(parameterInput) - equality.leftExpression = variableReference + equality.rightExpression = stFactoryUtils.createIntLiteral(i) + equality.leftExpression = stFactoryUtils.createVariable(parameterInput) val stateToStart = factory.createStateTransition() basicFbType.ecc.transitions += stateToStart @@ -795,7 +795,6 @@ class ExtendedAdapterUtils( socket.name = "Socket_Connection" composite.sockets += socket - // add port connections composite.network.copyElements(network) return composite } diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt similarity index 98% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt rename to code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt index 52af1518d..8d7ba2f24 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/SystemUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt @@ -1,5 +1,6 @@ -package org.fbme.ide.richediting.utils +package org.fbme.extensions.adapter +import org.fbme.extensions.utils.IEC61499FactoryUtils import org.fbme.ide.iec61499.repository.PlatformRepository import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.SystemDeclaration diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt similarity index 99% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt rename to code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt index 70754c0d8..39230f7c7 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/FBInterfaceDeclarationUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt @@ -1,4 +1,4 @@ -package org.fbme.ide.richediting.utils +package org.fbme.extensions.utils import org.fbme.lib.common.Identifier import org.fbme.lib.common.StringIdentifier diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt similarity index 80% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt rename to code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt index 43f0918b9..e946f5d99 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/IEC61499FactoryUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt @@ -1,15 +1,31 @@ -package org.fbme.ide.richediting.utils +package org.fbme.extensions.utils import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.EventDeclaration import org.fbme.lib.iec61499.declarations.FBTypeDeclaration import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.ecc.StateDeclaration +import org.fbme.lib.iec61499.ecc.StateTransition import org.fbme.lib.iec61499.fbnetwork.* class IEC61499FactoryUtils( private val factory: IEC61499Factory, ) { + fun createStateTransition( + source: StateDeclaration, + target: StateDeclaration, + eventCondition: EventDeclaration? = null, + ): StateTransition { + val transition = factory.createStateTransition() + transition.sourceReference.setTarget(source) + transition.targetReference.setTarget(target) + if (eventCondition != null) { + transition.condition.eventReference.setFQName(eventCondition.name) + } + return transition + } + fun createNetworkConnection( kind: EntryKind, source: FunctionBlockDeclarationBase?, @@ -39,12 +55,21 @@ class IEC61499FactoryUtils( name: String? = null, ): FunctionBlockDeclaration { val block = factory.createFunctionBlockDeclaration(blockType.identifier) - block.name = name ?: blockType.name + block.name = getUnusedName( + name ?: blockType.name, + network.functionBlocks.asSequence().map { it.name }.toSet() + ) block.typeReference.setTarget(blockType) network.functionBlocks += block return block } + private fun getUnusedName(newName: String, names: Set): String = if (newName in names) { + getUnusedName("${newName}_", names) + } else { + newName + } + fun copyEventsAndConnect( destination: MutableList, destinationBlock: FunctionBlockDeclarationBase?, diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/STFactoryUtils.kt similarity index 89% rename from code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt rename to code/extensions/src/main/kotlin/org/fbme/extensions/utils/STFactoryUtils.kt index a85cec39c..6b1a1a220 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/utils/STFactoryUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/STFactoryUtils.kt @@ -1,4 +1,4 @@ -package org.fbme.ide.richediting.utils +package org.fbme.extensions.utils import org.fbme.lib.st.STFactory import org.fbme.lib.st.expressions.* @@ -9,11 +9,8 @@ class STFactoryUtils( ) { fun intEquality(variable: VariableDeclaration, number: Int): BinaryExpression { val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) - - val numberLiteral = createIntLiteral(number) - equality.rightExpression = numberLiteral - equality.leftExpression = createVariable(variable) + equality.rightExpression = createIntLiteral(number) return equality } diff --git a/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/ExtendedAdapterUtilsTest.kt similarity index 94% rename from code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt rename to code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/ExtendedAdapterUtilsTest.kt index e52792ba2..e6b476687 100644 --- a/code/richediting/src/test/kotlin/org/fbme/ide/richediting/utils/ExtendedAdapterUtilsTest.kt +++ b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/ExtendedAdapterUtilsTest.kt @@ -1,7 +1,8 @@ -package org.fbme.ide.richediting.utils +package org.fbme.ide.extensions.utils import jetbrains.mps.smodel.tempmodel.TempModuleOptions import jetbrains.mps.smodel.tempmodel.TemporaryModels +import org.fbme.extensions.adapter.ExtendedAdapterUtils import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.ide.platform.testing.PlatformTestBase import org.fbme.ide.platform.testing.PlatformTestRunner @@ -20,7 +21,6 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull -//@LoadFrom("org.fbme.richediting.lib") @RunWith(PlatformTestRunner::class) class ExtendedAdapterUtilsTest : PlatformTestBase() { private val extendedAdapterUtils = ExtendedAdapterUtils(factory, stFactory, repository, ::getPublishSubscribeBlock) @@ -121,9 +121,9 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) checkNode(model, "$networkPath/LeftCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") - checkNode(model, "$networkPath/EA_LeftPublishSubscribeAdapter_0.fbt", "EA_LeftPublishSubscribeAdapter_0") - checkNode(model, "$networkPath/EA_EventToNumberAdapter_4.fbt", "EA_EventToNumberAdapter_4") - checkNode(model, "$networkPath/EA_NumberToEventAdapter_4.fbt", "EA_NumberToEventAdapter_4") + checkNode(model, "$networkPath/EA_LeftPublishSubscribeAdapter.fbt", "EA_LeftPublishSubscribeAdapter") + checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") + checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") } } @@ -157,9 +157,9 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) checkNode(model, "$networkPath/RightCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") - checkNode(model, "$networkPath/EA_RightPublishSubscribeAdapter_1.fbt", "EA_RightPublishSubscribeAdapter_1") - checkNode(model, "$networkPath/EA_EventToNumberAdapter_4.fbt", "EA_EventToNumberAdapter_4") - checkNode(model, "$networkPath/EA_NumberToEventAdapter_4.fbt", "EA_NumberToEventAdapter_4") + checkNode(model, "$networkPath/EA_RightPublishSubscribeAdapter.fbt", "EA_RightPublishSubscribeAdapter") + checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") + checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") } } diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt similarity index 55% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt index a68011b8c..431d241e4 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter_0.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt @@ -1,7 +1,7 @@ - + @@ -28,11 +28,11 @@ - - + + - - + + @@ -43,16 +43,16 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt similarity index 55% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt index f19398ebc..7ab4de909 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter_1.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt @@ -1,7 +1,7 @@ - + @@ -28,11 +28,11 @@ - - + + - - + + @@ -43,16 +43,16 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt similarity index 98% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt index 9f2b8b746..35145fe72 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_EventToNumberAdapter_4.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt @@ -1,7 +1,7 @@ - + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt similarity index 79% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt index 0c9e90377..b73280035 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt @@ -8,10 +8,10 @@ - + - + @@ -25,29 +25,29 @@ - - - - - - - - - - + + + + + + + + + + - - + + - + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt similarity index 97% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt index fae00ce7f..2f81cd242 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/EA_NumberToEventAdapter_4.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt @@ -1,7 +1,7 @@ - + diff --git a/code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt similarity index 98% rename from code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt index edf07f29e..80fa5a89d 100644 --- a/code/richediting/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt @@ -5,7 +5,7 @@ - + diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt rename to code/extensions/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt rename to code/extensions/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt rename to code/extensions/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_2_router.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_router.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/EA_2_router.fbt rename to code/extensions/src/test/resources/results/testRevealAdapter/EA_2_router.fbt diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_network1.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_network1.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/EA_network1.fbt rename to code/extensions/src/test/resources/results/testRevealAdapter/EA_network1.fbt diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/EA_network2.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_network2.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/EA_network2.fbt rename to code/extensions/src/test/resources/results/testRevealAdapter/EA_network2.fbt diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/Left_EA.adp b/code/extensions/src/test/resources/results/testRevealAdapter/Left_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/Left_EA.adp rename to code/extensions/src/test/resources/results/testRevealAdapter/Left_EA.adp diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/Middle_EA.adp b/code/extensions/src/test/resources/results/testRevealAdapter/Middle_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/Middle_EA.adp rename to code/extensions/src/test/resources/results/testRevealAdapter/Middle_EA.adp diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/Right_EA.adp b/code/extensions/src/test/resources/results/testRevealAdapter/Right_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/Right_EA.adp rename to code/extensions/src/test/resources/results/testRevealAdapter/Right_EA.adp diff --git a/code/richediting/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp b/code/extensions/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp rename to code/extensions/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network1.fbt b/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network1.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealDeclarations/EA_network1.fbt rename to code/extensions/src/test/resources/results/testRevealDeclarations/EA_network1.fbt diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/EA_network2.fbt b/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network2.fbt similarity index 100% rename from code/richediting/src/test/resources/results/testRevealDeclarations/EA_network2.fbt rename to code/extensions/src/test/resources/results/testRevealDeclarations/EA_network2.fbt diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/Left_EA.adp b/code/extensions/src/test/resources/results/testRevealDeclarations/Left_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealDeclarations/Left_EA.adp rename to code/extensions/src/test/resources/results/testRevealDeclarations/Left_EA.adp diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/Middle_EA.adp b/code/extensions/src/test/resources/results/testRevealDeclarations/Middle_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealDeclarations/Middle_EA.adp rename to code/extensions/src/test/resources/results/testRevealDeclarations/Middle_EA.adp diff --git a/code/richediting/src/test/resources/results/testRevealDeclarations/Right_EA.adp b/code/extensions/src/test/resources/results/testRevealDeclarations/Right_EA.adp similarity index 100% rename from code/richediting/src/test/resources/results/testRevealDeclarations/Right_EA.adp rename to code/extensions/src/test/resources/results/testRevealDeclarations/Right_EA.adp diff --git a/code/richediting/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt b/code/extensions/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt similarity index 100% rename from code/richediting/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt rename to code/extensions/src/test/resources/source/publishSubscribes/PUBLISH_5.fbt diff --git a/code/richediting/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt b/code/extensions/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt similarity index 100% rename from code/richediting/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt rename to code/extensions/src/test/resources/source/publishSubscribes/SUBSCRIBE_5.fbt diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/BaseBlock.fbt b/code/extensions/src/test/resources/source/testRevealAdapter/BaseBlock.fbt similarity index 100% rename from code/richediting/src/test/resources/source/testRevealAdapter/BaseBlock.fbt rename to code/extensions/src/test/resources/source/testRevealAdapter/BaseBlock.fbt diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt b/code/extensions/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt similarity index 100% rename from code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt rename to code/extensions/src/test/resources/source/testRevealAdapter/CompositeBlock.fbt diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt similarity index 100% rename from code/richediting/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt rename to code/extensions/src/test/resources/source/testRevealAdapter/CompositeBlockWithoutConnections.fbt diff --git a/code/richediting/src/test/resources/source/testRevealAdapter/EA.eadp b/code/extensions/src/test/resources/source/testRevealAdapter/EA.eadp similarity index 100% rename from code/richediting/src/test/resources/source/testRevealAdapter/EA.eadp rename to code/extensions/src/test/resources/source/testRevealAdapter/EA.eadp diff --git a/code/richediting/src/test/resources/source/testRevealDeclarations/EA.eadp b/code/extensions/src/test/resources/source/testRevealDeclarations/EA.eadp similarity index 100% rename from code/richediting/src/test/resources/source/testRevealDeclarations/EA.eadp rename to code/extensions/src/test/resources/source/testRevealDeclarations/EA.eadp diff --git a/code/richediting/build.gradle.kts b/code/richediting/build.gradle.kts index f12384587..c81387e1d 100644 --- a/code/richediting/build.gradle.kts +++ b/code/richediting/build.gradle.kts @@ -13,16 +13,13 @@ dependencies { compileOnly(project(":code:library")) compileOnly(project(":code:language")) compileOnly(project(":code:platform")) + compileOnly(project(":code:extensions")) + implementation(project(":code:extensions")) implementation("org.eclipse.elk:org.eclipse.elk.alg.layered:0.7.1") implementation("org.eclipse.elk:org.eclipse.elk.core:0.7.1") implementation("org.eclipse.elk:org.eclipse.elk.graph:0.7.1") - testImplementation(mpsDistribution()) - testImplementation(project(":code:library")) - testImplementation(project(":code:language")) - testImplementation(project(":code:platform")) - mpsImplementation(project(":code:language", "mps")) mpsImplementation(project(":code:platform", "mps")) mpsImplementation(project(":code:scenes", "mps")) @@ -37,13 +34,6 @@ mps { moduleDependency(project(":code:scenes")) } -val test by tasks.getting(Test::class) { - dependsOn( - ":code:library:buildDistPlugin", - "buildDistPlugin" - ) -} - val compileKotlin by tasks.getting(KotlinCompile::class) { kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all") } \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt index 81790e9ed..c46661d83 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.ui.DialogWrapper import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.* import jetbrains.mps.ide.actions.MPSCommonDataKeys -import org.fbme.ide.richediting.utils.AdapterSwitchGenerator +import org.fbme.extensions.adapter.AdapterSwitchGenerator import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.fbnetwork.* import org.fbme.lib.st.expressions.* diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt index 2f7cab3e5..f66a05530 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt @@ -10,7 +10,7 @@ import jetbrains.mps.model.ModelDeleteHelper import jetbrains.mps.project.AbstractModule import jetbrains.mps.project.MPSProject import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider -import org.fbme.ide.richediting.utils.ExtendedAdapterUtils +import org.fbme.extensions.adapter.ExtendedAdapterUtils import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jetbrains.mps.openapi.model.SModelName diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt index 983b2202f..1ac0f65e0 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt @@ -6,7 +6,7 @@ import com.intellij.openapi.project.DumbAware import jetbrains.mps.ide.actions.MPSCommonDataKeys import jetbrains.mps.project.MPSProject import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider -import org.fbme.ide.richediting.utils.SystemUtils +import org.fbme.extensions.adapter.SystemUtils import org.fbme.lib.iec61499.declarations.SystemDeclaration class SyncSystemResources : AnAction(), DumbAware { diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt index 8a34da0a3..0b1aea85c 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/adapters/fbnetwork/FBConnectionController.kt @@ -8,7 +8,7 @@ import jetbrains.mps.nodeEditor.cells.EditorCell_Collection import jetbrains.mps.openapi.editor.EditorContext import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory import org.fbme.ide.richediting.adapters.fbnetwork.fb.DiagramColors -import org.fbme.ide.richediting.utils.ExtendedAdapterUtils +import org.fbme.extensions.adapter.ExtendedAdapterUtils import org.fbme.ide.richediting.viewmodel.NetworkConnectionView import org.fbme.lib.iec61499.fbnetwork.ConnectionPath import org.fbme.lib.iec61499.fbnetwork.EntryKind diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps index 2ab27ee01..fd4f81542 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps @@ -11,30 +11,10 @@ - - - - - - - - - - - - - - - - - - - - @@ -53,55 +33,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - @@ -118,13 +53,6 @@ - - - - - - - @@ -136,15 +64,6 @@ - - - - - - - - - @@ -156,9 +75,6 @@ - - - @@ -215,19 +131,13 @@ - - - - - - @@ -303,4109 +213,408 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - + + + - - - + + - - - - - + + + + - - - - - - - - - - - - - - - - + + + - - - - - + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + + + - - - - - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - + + + + + + + + + + + + - - + + + + + + + + + + - - - - + + + + + + + + + + + + + - - - + + + + + + + + + + - - + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + - - - + + + + + + + - - - - + + + + + + + - - - - - + + + + + + + + + + + + - - - + + + + + + + + + + + + - - - - + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + - - - + + + + + + + + + + + + - - + + + + + + + + + + + + - - - - - - - - - - - + + + + - - - - + + + + + + + - - - + + + - - - - + + + + - - - - - - - - + + + + - - - - + + + + - - - - - - + + + + - - - - - + + + - - - + + + + + + - - - - + + + + - - - - - - - - + + + + + + - - - - + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + + + + - - - - + + + + - - - - - - + + + + - - - - + + + + - - - + + + - - + + - - - - + + + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4857,213 +1066,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts index 14f191ebb..145855487 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ rootProject.name = "FBME" include( "code:4diac-integration", "code:enas", + "code:extensions", "code:language", "code:library", "code:nxt-integration", From d2129a61c70473aedad6b3798426ab454955a4f3 Mon Sep 17 00:00:00 2001 From: klinachev Date: Sun, 17 Mar 2024 23:48:54 +0300 Subject: [PATCH 10/14] refactoring --- code/extensions/build.gradle.kts | 36 + .../extensions/adapter/AdapterRevealApi.kt | 20 + .../adapter/AdapterRevealService.kt | 212 ++++ .../adapter/AdapterSwitchGenerator.kt | 10 +- .../adapter/ExtendedAdapterUtils.kt | 117 +-- .../adapter/RevealDeclarationsResult.kt | 23 + .../fbme/extensions/adapter/SystemUtils.kt | 103 -- ...ilsTest.kt => AdapterRevealServiceTest.kt} | 22 +- ...bme.ide.iec61499.adapter.interfacepart.mps | 151 +-- .../declarations/AdapterTypeDeclaration.kt | 3 +- .../extention/AdapterNetworkDeclaration.kt | 7 +- .../ExtendedAdapterTypeDeclaration.kt | 3 +- .../actions/RevealExtendedAdapterAction.kt | 6 +- .../actions/SyncSystemResources.kt | 10 +- ....fbme.ide.iec61499.lang.sandbox.blinky.mps | 29 +- ...e.ide.iec61499.lang.sandbox.ea_example.mps | 991 +++++++++--------- 16 files changed, 862 insertions(+), 881 deletions(-) create mode 100644 code/extensions/build.gradle.kts create mode 100644 code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt create mode 100644 code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt create mode 100644 code/extensions/src/main/kotlin/org/fbme/extensions/adapter/RevealDeclarationsResult.kt delete mode 100644 code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt rename code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/{ExtendedAdapterUtilsTest.kt => AdapterRevealServiceTest.kt} (93%) diff --git a/code/extensions/build.gradle.kts b/code/extensions/build.gradle.kts new file mode 100644 index 000000000..e56541f75 --- /dev/null +++ b/code/extensions/build.gradle.kts @@ -0,0 +1,36 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile + +plugins { + mps + kotlin +} + +dependencies { + compileOnly(mpsDistribution()) + compileOnly(project(":code:library")) + compileOnly(project(":code:language")) + + mpsImplementation(project(":code:library", "mps")) + mpsImplementation(project(":code:language", "mps")) + + testImplementation(mpsDistribution()) + testImplementation(project(":code:library")) + testImplementation(project(":code:language")) + testImplementation(project(":code:platform")) + +} + +mps { + moduleName.set("org.fbme.extensions.lib") +} + +val test by tasks.getting(Test::class) { + dependsOn( + ":code:library:buildDistPlugin", + "buildDistPlugin" + ) +} + +val compileKotlin by tasks.getting(KotlinCompile::class) { + kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all") +} \ No newline at end of file diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt new file mode 100644 index 000000000..e625c334c --- /dev/null +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt @@ -0,0 +1,20 @@ +package org.fbme.extensions.adapter + +import org.fbme.lib.iec61499.declarations.SystemDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.jetbrains.mps.openapi.model.SModel + +interface AdapterRevealApi { + fun revealAdapter( + extendedAdapter: ExtendedAdapterTypeDeclaration, + currentModel: SModel, + removeAdapter: Boolean = true, + ): RevealDeclarationsResult + + fun revealAllAdapters(currentModel: SModel) + + fun syncApplicationResources( + systemDeclaration: SystemDeclaration, + model: SModel, + ) +} \ No newline at end of file diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt new file mode 100644 index 000000000..77181a547 --- /dev/null +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt @@ -0,0 +1,212 @@ +package org.fbme.extensions.adapter + +import org.fbme.extensions.utils.IEC61499FactoryUtils +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.ide.iec61499.repository.PlatformRepository +import org.fbme.lib.common.Identifier +import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.DeclarationWithNetwork +import org.fbme.lib.iec61499.declarations.FBTypeDeclaration +import org.fbme.lib.iec61499.declarations.SystemDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.hierarchies.ResourceFunctionBlockHierarchy +import org.fbme.lib.iec61499.descriptors.FBPortDescriptor +import org.fbme.lib.iec61499.fbnetwork.EntryKind +import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration +import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase +import org.fbme.lib.st.STFactory +import org.jetbrains.mps.openapi.model.SModel + +class AdapterRevealService( + factory: IEC61499Factory, + stFactory: STFactory, + private val owner: PlatformRepository, + publishSubscribeProvider: ((name: String) -> FBTypeDeclaration)? = null, +): AdapterRevealApi { + private val extendedAdapterUtils: ExtendedAdapterUtils = + ExtendedAdapterUtils(factory, stFactory, owner, publishSubscribeProvider) + private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) + + override fun revealAdapter( + extendedAdapter: ExtendedAdapterTypeDeclaration, + currentModel: SModel, + removeAdapter: Boolean, + ): RevealDeclarationsResult { + val revealDeclarations = extendedAdapterUtils.createDeclarations(extendedAdapter, currentModel) + val declarationsResults = listOf(revealDeclarations) + val identifiersToRevealResult = + declarationsResults.associateBy { it.extendedAdapter.identifier } + + revealAdapters( + modelToGenerateNodesIn = currentModel, + modelToCheck = currentModel, + identifiersToRevealResult = identifiersToRevealResult, + ) + if (!removeAdapter) { + return revealDeclarations + } + for (revealResult in declarationsResults) { + val node = (revealResult.extendedAdapter as? PlatformElement)?.node + if (node != null) { + currentModel.removeRootNode(node) + } + } + return revealDeclarations + } + + fun revealAdapterWithNetBlocks( + revealResult: RevealDeclarationsResult, + block: FunctionBlockDeclaration, + port: FBPortDescriptor, + count: Int, + model: SModel, + ) = extendedAdapterUtils.revealAdapterWithNetBlocks(revealResult, block, port, count, model) + + override fun revealAllAdapters(currentModel: SModel) { + val identifiersToRevealResult = hashMapOf() + for (module in currentModel.repository.modules) { + for (model in module.models) { + for (node in model.rootNodes) { + if (node is ExtendedAdapterTypeDeclaration) { + identifiersToRevealResult[node.identifier] = extendedAdapterUtils.createDeclarations(node, currentModel) + } + } + } + } + for (module in currentModel.repository.modules) { + for (model in module.models) { + revealAdapters( + modelToGenerateNodesIn = currentModel, + modelToCheck = model, + identifiersToRevealResult = identifiersToRevealResult + ) + } + } + } + + private fun revealAdapters( + modelToGenerateNodesIn: SModel, + modelToCheck: SModel, + identifiersToRevealResult: Map + ) { + val rootNodes = modelToCheck.rootNodes.toList() + for (node in rootNodes) { + val fbTypeDeclaration = owner.adapterOrNull(node) + if (fbTypeDeclaration != null) { + fbTypeDeclaration.sockets.forEach { + val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] + if (revealDeclarationsResult != null) { + it.typeReference.setTarget(revealDeclarationsResult.getFarRightAdapter()) + } + } + fbTypeDeclaration.plugs.forEach { + val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] + if (revealDeclarationsResult != null) { + it.typeReference.setTarget(revealDeclarationsResult.getFarLeftAdapter()) + } + } + } + } + for (node in rootNodes) { + val declarationWithNetwork = owner.adapterOrNull(node) + if (declarationWithNetwork != null) { + val sourceIdentifiersToRevealResult = + identifiersToRevealResult.mapKeys { it.value.getFarLeftAdapter().identifier } + extendedAdapterUtils.revealExtendedAdaptersInNetwork( + network = declarationWithNetwork.network, + identifiersToRevealResult = sourceIdentifiersToRevealResult, + model = modelToGenerateNodesIn, + withPublishSubscribe = false, + ) + } + } + } + + override fun syncApplicationResources( + systemDeclaration: SystemDeclaration, + model: SModel, + ) { + val applicationBlockToMapping: Map = + systemDeclaration.mappings.asSequence() + .mapNotNull { mapping -> + val functionBlock = mapping.applicationFBReference.getTarget()?.functionBlock + val resource = mapping.resourceFBReference.getTarget() + if (functionBlock != null && resource != null) { + functionBlock to resource + } else { + null + } + } + .associate { it } + val revealDeclarationsResults = mutableMapOf() + for (application in systemDeclaration.applications) { + val adapterConnections = application.network.adapterConnections.asSequence() + .filter { ExtendedAdapterUtils.isExtendedAdapterConnection(it) } + .mapNotNull { connection -> (connection.sourceReference.getTarget())?.let { it to connection } } + .groupBy({ it.first }, { it.second }) + for ((plug, connections) in adapterConnections) { + val adapterType = ExtendedAdapterUtils.getPlugExtendedAdapterType(plug.portTarget) ?: continue + val sourceResource = applicationBlockToMapping[plug.functionBlock] ?: continue + val sourceBlockInResource = sourceResource.functionBlock ?: continue + val plugPort = sourceBlockInResource.type.plugPorts.first { it.name == plug.portTarget.name } + val sourceResourceNetwork = sourceResource.resourceHierarchy.resource.network + val needReveal = sourceResourceNetwork.adapterConnections.none { + it.sourceReference.getTarget()?.functionBlock == sourceBlockInResource && + it.sourceReference.getTarget()?.portTarget == plugPort.declaration + } && connections.any { connection -> + val socket = connection.targetReference.getTarget() + sourceResource != applicationBlockToMapping[socket?.functionBlock] + } + if (needReveal) { + val revealDeclarationsResult = revealDeclarationsResults.computeIfAbsent(adapterType.name) { + extendedAdapterUtils.createDeclarations(adapterType, model) + } + extendedAdapterUtils.revealAdapterWithNetBlocks( + revealResult = revealDeclarationsResult, + block = sourceBlockInResource, + port = plugPort, + count = connections.size, + model = model, + ) + } + for (connection in connections) { + val socket = connection.targetReference.getTarget() ?: continue + val targetResource = applicationBlockToMapping[socket.functionBlock] ?: continue + val targetBlockInResource = targetResource.functionBlock ?: continue + val socketPort = targetBlockInResource.type.socketPorts.first { it.name == socket.portTarget.name } + val targetResourceNetwork = targetResource.resourceHierarchy.resource.network + val connectionExists = targetResourceNetwork.adapterConnections.any { + it.targetReference.getTarget()?.functionBlock == targetBlockInResource && + it.targetReference.getTarget()?.portTarget == socketPort.declaration + } + if (!connectionExists) { + if (sourceResource == targetResource) { + targetResourceNetwork.adapterConnections += factoryUtils.createConnection( + source = sourceBlockInResource.getPort(plugPort), + target = targetBlockInResource.getPort(socketPort), + entryKind = EntryKind.ADAPTER, + ) + continue + } + val revealDeclarationsResult = revealDeclarationsResults.computeIfAbsent(adapterType.name) { + extendedAdapterUtils.createDeclarations(adapterType, model) + } + extendedAdapterUtils.revealAdapterWithNetBlocks( + revealResult = revealDeclarationsResult, + block = targetBlockInResource, + port = socketPort, + count = connections.size, + model = model, + ) + } + } + + } + } + } + + fun revealDeclarations( + extendedAdapter: ExtendedAdapterTypeDeclaration, + model: SModel?, + ): RevealDeclarationsResult = extendedAdapterUtils.createDeclarations(extendedAdapter, model) +} diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt index 554292028..122f61ee5 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt @@ -24,7 +24,7 @@ class AdapterSwitchGenerator( fun generateRouter( name: String, model: SModel, - socketAdapterTypeDeclaration: AdapterTypeDeclaration, + adapter: AdapterTypeDeclaration, outputsCount: Int, outputRouterName: String, plugAdapterTypeDeclaration: AdapterTypeDeclaration? = null, @@ -37,12 +37,12 @@ class AdapterSwitchGenerator( model.addRootNodes(routerDeclaration, virtualPackage = virtualPackage) val socket = factory.createSocketDeclaration(StringIdentifier("socket")) - socket.typeReference.setTarget(socketAdapterTypeDeclaration) + socket.typeReference.setTarget(adapter) routerDeclaration.sockets += socket for (i in 0 until outputsCount) { val plug = factory.createPlugDeclaration(StringIdentifier("plug_$i")) - plug.typeReference.setTarget(plugAdapterTypeDeclaration ?: socketAdapterTypeDeclaration) + plug.typeReference.setTarget(plugAdapterTypeDeclaration ?: adapter) routerDeclaration.plugs += plug } addSocketToPlugsSwitch( @@ -166,7 +166,9 @@ class AdapterSwitchGenerator( network = network, outputToInput = false, ).associateBy { it.second.name } - val routerParameterDeclaration = outputParameters.first { it.name == routerName } + val routerParameterDeclaration = routerName?.let { router -> + outputParameters.first { it.name == router } + } val startState = factory.createStateDeclaration(StringIdentifier("Start")) switchDeclaration.ecc.states += startState for ((i, plug) in sources.withIndex()) { diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt index dd65efbda..1e7a73098 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt @@ -58,47 +58,7 @@ class ExtendedAdapterUtils( (declaration as? PlugDeclaration)?.typeReference?.getTarget() } - data class RevealDeclarationsResult( - val extendedAdapter: ExtendedAdapterTypeDeclaration, - val routerAdapter: AdapterTypeDeclaration?, - val leftAdapter: AdapterTypeDeclaration, - val middleAdapter: AdapterTypeDeclaration, - val rightAdapter: AdapterTypeDeclaration, - val leftBlockDeclaration: CompositeFBTypeDeclaration?, - val rightBlockDeclaration: CompositeFBTypeDeclaration?, - var leftPublishSubscribeAdapter: CompositeFBTypeDeclaration? = null, - var rightPublishSubscribeAdapter: CompositeFBTypeDeclaration? = null, - val routers: MutableMap = mutableMapOf(), - ) { - fun getFarLeftAdapter(): AdapterTypeDeclaration = routerAdapter ?: leftAdapter - - fun getFarRightAdapter(): AdapterTypeDeclaration = rightAdapter - } - - fun revealAdapter( - extendedAdapter: ExtendedAdapterTypeDeclaration, - currentModel: SModel, - ): RevealDeclarationsResult { - val revealDeclarations = revealDeclarations(extendedAdapter, currentModel) - val declarationsResults = listOf(revealDeclarations) - val identifiersToRevealResult = - declarationsResults.associateBy { it.extendedAdapter.identifier } - - revealAdapterInNetworks( - currentModel = currentModel, - modelToCheck = currentModel, - identifiersToRevealResult = identifiersToRevealResult, - ) - for (revealResult in declarationsResults) { - val node = (revealResult.extendedAdapter as? PlatformElement)?.node - if (node != null) { - currentModel.removeRootNode(node) - } - } - return revealDeclarations - } - - fun revealAdapterWithNet( + fun revealAdapterWithNetBlocks( revealResult: RevealDeclarationsResult, block: FunctionBlockDeclaration, port: FBPortDescriptor, @@ -145,70 +105,12 @@ class ExtendedAdapterUtils( } } - private fun revealAdapterInAllNetworks( - currentModel: SModel, - identifiersToRevealResult: Map - ) { - for (module in currentModel.repository.modules) { - for (model in module.models) { - revealAdapterInNetworks( - currentModel = currentModel, - modelToCheck = model, - identifiersToRevealResult = identifiersToRevealResult - ) - } - } - } - - private fun revealAdapterInNetworks( - currentModel: SModel, - modelToCheck: SModel, - identifiersToRevealResult: Map - ) { - val rootNodes = modelToCheck.rootNodes.toList() - for (node in rootNodes) { - val fbTypeDeclaration = owner.adapterOrNull(node) - if (fbTypeDeclaration != null) { - fbTypeDeclaration.sockets.forEach { - val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] - if (revealDeclarationsResult != null) { - it.typeReference.setTarget(revealDeclarationsResult.getFarRightAdapter()) - } - } - fbTypeDeclaration.plugs.forEach { - val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] - if (revealDeclarationsResult != null) { - it.typeReference.setTarget(revealDeclarationsResult.getFarLeftAdapter()) - } - } - } - } - for (node in rootNodes) { - val declarationWithNetwork = owner.adapterOrNull(node) - if (declarationWithNetwork != null) { - val sourceIdentifiersToRevealResult = - identifiersToRevealResult.mapKeys { it.value.getFarLeftAdapter().identifier } - revealExtendedAdaptersInNetwork( - network = declarationWithNetwork.network, - identifiersToRevealResult = sourceIdentifiersToRevealResult, - model = currentModel, - withPublishSubscribe = false, - ) - } - } - } - - fun revealDeclarations( + fun createDeclarations( extendedAdapter: ExtendedAdapterTypeDeclaration, model: SModel?, ): RevealDeclarationsResult { val name = extendedAdapter.name val FBInterfaceDeclarationUtils = FBInterfaceDeclarationUtils(factory) - val leftAdapter = FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( - name = "Left_$name", - fbTypeDescriptor = extendedAdapter.getCustomNetworkComponents()[1].block.type, - reversed = false, - ) val routerAdapter = if (extendedAdapter.outputRouter != null) { FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( name = "RouterAdapter_$name", @@ -219,6 +121,15 @@ class ExtendedAdapterUtils( null } val leftNetwork = extendedAdapter.leftNetwork + val leftAdapter = FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = "Left_$name", + fbTypeDescriptor = if (leftNetwork != null) { + leftNetwork.getCustomNetworkComponents()[1].block.type + } else { + extendedAdapter.plugTypeDescriptor + }, + reversed = false, + ) val middleAdapter = if (leftNetwork == null || (extendedAdapter.internalFbSocketInterface?.isEmpty() != false && extendedAdapter.internalNetworksInterface?.isEmpty() != false) @@ -282,7 +193,7 @@ class ExtendedAdapterUtils( ) } - private fun revealExtendedAdaptersInNetwork( + fun revealExtendedAdaptersInNetwork( network: FBNetwork, identifiersToRevealResult: Map, model: SModel, @@ -354,7 +265,7 @@ class ExtendedAdapterUtils( network.adapterConnections.removeIf { it in connectionsToRemove } } - private fun revealLeftPart( + fun revealLeftPart( revealResult: RevealDeclarationsResult, sourcePort: PortPath<*>, network: FBNetwork, @@ -373,7 +284,7 @@ class ExtendedAdapterUtils( switchGenerator.generateRouter( name = "${adapterType.name}_$connectionsCount", model = model, - socketAdapterTypeDeclaration = checkNotNull(revealResult.routerAdapter), + adapter = checkNotNull(revealResult.routerAdapter), plugAdapterTypeDeclaration = revealResult.leftAdapter, outputsCount = connectionsCount, outputRouterName = outputRouter.name, diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/RevealDeclarationsResult.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/RevealDeclarationsResult.kt new file mode 100644 index 000000000..7fc29d85b --- /dev/null +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/RevealDeclarationsResult.kt @@ -0,0 +1,23 @@ +package org.fbme.extensions.adapter + +import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration +import org.fbme.lib.iec61499.declarations.CompositeFBTypeDeclaration +import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration + + +data class RevealDeclarationsResult( + val extendedAdapter: ExtendedAdapterTypeDeclaration, + val routerAdapter: AdapterTypeDeclaration?, + val leftAdapter: AdapterTypeDeclaration, + val middleAdapter: AdapterTypeDeclaration, + val rightAdapter: AdapterTypeDeclaration, + val leftBlockDeclaration: CompositeFBTypeDeclaration?, + val rightBlockDeclaration: CompositeFBTypeDeclaration?, + var leftPublishSubscribeAdapter: CompositeFBTypeDeclaration? = null, + var rightPublishSubscribeAdapter: CompositeFBTypeDeclaration? = null, + val routers: MutableMap = mutableMapOf(), +) { + fun getFarLeftAdapter(): AdapterTypeDeclaration = routerAdapter ?: leftAdapter + + fun getFarRightAdapter(): AdapterTypeDeclaration = rightAdapter +} \ No newline at end of file diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt deleted file mode 100644 index 8d7ba2f24..000000000 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/SystemUtils.kt +++ /dev/null @@ -1,103 +0,0 @@ -package org.fbme.extensions.adapter - -import org.fbme.extensions.utils.IEC61499FactoryUtils -import org.fbme.ide.iec61499.repository.PlatformRepository -import org.fbme.lib.iec61499.IEC61499Factory -import org.fbme.lib.iec61499.declarations.SystemDeclaration -import org.fbme.lib.iec61499.declarations.hierarchies.ResourceFunctionBlockHierarchy -import org.fbme.lib.iec61499.fbnetwork.EntryKind -import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase -import org.fbme.lib.st.STFactory -import org.jetbrains.mps.openapi.model.SModel - -class SystemUtils( - factory: IEC61499Factory, - stFactory: STFactory, - owner: PlatformRepository, -) { - private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) - private val extendedAdapterUtils = ExtendedAdapterUtils(factory, stFactory, owner) - - fun syncApplicationResources( - systemDeclaration: SystemDeclaration, - model: SModel, - ) { - val applicationBlockToMapping: Map = - systemDeclaration.mappings.asSequence() - .mapNotNull { mapping -> - val functionBlock = mapping.applicationFBReference.getTarget()?.functionBlock - val resource = mapping.resourceFBReference.getTarget() - if (functionBlock != null && resource != null) { - functionBlock to resource - } else { - null - } - } - .associate { it } - val revealDeclarationsResults = mutableMapOf() - for (application in systemDeclaration.applications) { - val adapterConnections = application.network.adapterConnections.asSequence() - .filter { ExtendedAdapterUtils.isExtendedAdapterConnection(it) } - .mapNotNull { connection -> (connection.sourceReference.getTarget())?.let { it to connection } } - .groupBy({ it.first }, { it.second }) - for ((plug, connections) in adapterConnections) { - val adapterType = ExtendedAdapterUtils.getPlugExtendedAdapterType(plug.portTarget) ?: continue - val sourceResource = applicationBlockToMapping[plug.functionBlock] ?: continue - val sourceBlockInResource = sourceResource.functionBlock ?: continue - val plugPort = sourceBlockInResource.type.plugPorts.first { it.name == plug.portTarget.name } - val sourceResourceNetwork = sourceResource.resourceHierarchy.resource.network - val needReveal = sourceResourceNetwork.adapterConnections.none { - it.sourceReference.getTarget()?.functionBlock == sourceBlockInResource && - it.sourceReference.getTarget()?.portTarget == plugPort.declaration - } && connections.any { connection -> - val socket = connection.targetReference.getTarget() - sourceResource != applicationBlockToMapping[socket?.functionBlock] - } - if (needReveal) { - val revealDeclarationsResult = revealDeclarationsResults.computeIfAbsent(adapterType.name) { - extendedAdapterUtils.revealDeclarations(adapterType, model) - } - extendedAdapterUtils.revealAdapterWithNet( - revealResult = revealDeclarationsResult, - block = sourceBlockInResource, - port = plugPort, - count = connections.size, - model = model, - ) - } - for (connection in connections) { - val socket = connection.targetReference.getTarget() ?: continue - val targetResource = applicationBlockToMapping[socket.functionBlock] ?: continue - val targetBlockInResource = targetResource.functionBlock ?: continue - val socketPort = targetBlockInResource.type.socketPorts.first { it.name == socket.portTarget.name } - val targetResourceNetwork = targetResource.resourceHierarchy.resource.network - val connectionExists = targetResourceNetwork.adapterConnections.any { - it.targetReference.getTarget()?.functionBlock == targetBlockInResource && - it.targetReference.getTarget()?.portTarget == socketPort.declaration - } - if (!connectionExists) { - if (sourceResource == targetResource) { - targetResourceNetwork.adapterConnections += factoryUtils.createConnection( - source = sourceBlockInResource.getPort(plugPort), - target = targetBlockInResource.getPort(socketPort), - entryKind = EntryKind.ADAPTER, - ) - continue - } - val revealDeclarationsResult = revealDeclarationsResults.computeIfAbsent(adapterType.name) { - extendedAdapterUtils.revealDeclarations(adapterType, model) - } - extendedAdapterUtils.revealAdapterWithNet( - revealResult = revealDeclarationsResult, - block = targetBlockInResource, - port = socketPort, - count = connections.size, - model = model, - ) - } - } - - } - } - } -} \ No newline at end of file diff --git a/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/ExtendedAdapterUtilsTest.kt b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt similarity index 93% rename from code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/ExtendedAdapterUtilsTest.kt rename to code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt index e6b476687..9932d082e 100644 --- a/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/ExtendedAdapterUtilsTest.kt +++ b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt @@ -2,10 +2,9 @@ package org.fbme.ide.extensions.utils import jetbrains.mps.smodel.tempmodel.TempModuleOptions import jetbrains.mps.smodel.tempmodel.TemporaryModels -import org.fbme.extensions.adapter.ExtendedAdapterUtils +import org.fbme.extensions.adapter.AdapterRevealService import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.ide.platform.testing.PlatformTestBase -import org.fbme.ide.platform.testing.PlatformTestRunner import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.CompositeFBTypeDeclaration import org.fbme.lib.iec61499.declarations.FBTypeDeclaration @@ -16,14 +15,11 @@ import org.jdom.output.XMLOutputter import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.mps.openapi.model.SModel import org.junit.Test -import org.junit.runner.RunWith import kotlin.test.assertEquals import kotlin.test.assertNotNull - -@RunWith(PlatformTestRunner::class) -class ExtendedAdapterUtilsTest : PlatformTestBase() { - private val extendedAdapterUtils = ExtendedAdapterUtils(factory, stFactory, repository, ::getPublishSubscribeBlock) +class AdapterRevealServiceTest : PlatformTestBase() { + private val adapterRevealService = AdapterRevealService(factory, stFactory, repository, ::getPublishSubscribeBlock) private val publishSubscribeMap: Map = mapOf( "PUBLISH_5" to rootConverterByPath("/source/publishSubscribes/PUBLISH_5.fbt").convertFBType(), "SUBSCRIBE_5" to rootConverterByPath("/source/publishSubscribes/SUBSCRIBE_5.fbt").convertFBType() @@ -46,7 +42,7 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { project.repository.modelAccess.runWriteAction { val model = TemporaryModels.getInstance().create(false, false, "tmp", TempModuleOptions.forDefaultModule()) model.addRootNode((extendedAdapterType as PlatformElement).node) - val revealDeclarations = extendedAdapterUtils.revealDeclarations(extendedAdapterType, null) + val revealDeclarations = adapterRevealService.revealDeclarations(extendedAdapterType, null) assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) @@ -76,7 +72,7 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { model.addRootNode((extendedAdapterType as PlatformElement).node) model.addRootNode((example as PlatformElement).node) - val revealDeclarations = extendedAdapterUtils.revealAdapter(extendedAdapterType, model) + val revealDeclarations = adapterRevealService.revealAdapter(extendedAdapterType, model) assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) @@ -108,10 +104,10 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { model.addRootNode((composite as PlatformElement).node) model.addRootNode((publishSubscribeMap["PUBLISH_5"] as PlatformElement).node) model.addRootNode((publishSubscribeMap["SUBSCRIBE_5"] as PlatformElement).node) - val revealDeclarations = extendedAdapterUtils.revealDeclarations(extendedAdapterType, model) + val revealDeclarations = adapterRevealService.revealDeclarations(extendedAdapterType, model) val functionBlockDeclaration = composite.cast().network.functionBlocks.first { it.name == "BaseBlock1" } - extendedAdapterUtils.revealAdapterWithNet( + adapterRevealService.revealAdapterWithNetBlocks( revealResult = revealDeclarations, block = functionBlockDeclaration, port = functionBlockDeclaration.type.plugPorts[0], @@ -144,10 +140,10 @@ class ExtendedAdapterUtilsTest : PlatformTestBase() { model.addRootNode((composite as PlatformElement).node) model.addRootNode((publishSubscribeMap["PUBLISH_5"] as PlatformElement).node) model.addRootNode((publishSubscribeMap["SUBSCRIBE_5"] as PlatformElement).node) - val revealDeclarations = extendedAdapterUtils.revealDeclarations(extendedAdapterType, model) + val revealDeclarations = adapterRevealService.revealDeclarations(extendedAdapterType, model) val functionBlockDeclaration = composite.cast().network.functionBlocks.first { it.name == "BaseBlock1" } - extendedAdapterUtils.revealAdapterWithNet( + adapterRevealService.revealAdapterWithNetBlocks( revealResult = revealDeclarations, block = functionBlockDeclaration, port = functionBlockDeclaration.type.socketPorts[0], diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps index 678968d96..1bf375d8c 100644 --- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps +++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps @@ -63,7 +63,6 @@ - @@ -7861,7 +7860,7 @@ - + @@ -8271,43 +8270,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -8762,117 +8724,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt index adc62ed90..4b3f6dab6 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/AdapterTypeDeclaration.kt @@ -5,8 +5,7 @@ import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor import org.fbme.lib.iec61499.descriptors.PlugType import org.fbme.lib.iec61499.descriptors.SocketType -interface AdapterTypeDeclaration : FBInterfaceDeclaration, - RootElement { +interface AdapterTypeDeclaration : FBInterfaceDeclaration, RootElement { val plugTypeDescriptor: FBTypeDescriptor get() = PlugType(this) val socketTypeDescriptor: FBTypeDescriptor diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt index 233f67d19..0f26f5732 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/AdapterNetworkDeclaration.kt @@ -1,11 +1,12 @@ package org.fbme.lib.iec61499.declarations.extention -import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.declarations.DeclarationWithNetwork -import org.fbme.lib.iec61499.descriptors.* +import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor +import org.fbme.lib.iec61499.descriptors.InternalPlugType +import org.fbme.lib.iec61499.descriptors.InternalSocketType import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider -interface AdapterNetworkDeclaration : Declaration, CustomNetworkComponentProvider, DeclarationWithNetwork { +interface AdapterNetworkDeclaration : CustomNetworkComponentProvider, DeclarationWithNetwork { override val container: ExtendedAdapterTypeDeclaration val internalFbPlugDescriptor: FBTypeDescriptor diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt index 9d894303b..a5856f7c9 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/extention/ExtendedAdapterTypeDeclaration.kt @@ -6,9 +6,8 @@ import org.fbme.lib.iec61499.declarations.ParameterDeclaration import org.fbme.lib.iec61499.descriptors.ExtendedPlugType import org.fbme.lib.iec61499.descriptors.ExtendedSocketType import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor -import org.fbme.lib.iec61499.fbnetwork.CustomNetworkComponentProvider -interface ExtendedAdapterTypeDeclaration : CustomNetworkComponentProvider, AdapterTypeDeclaration { +interface ExtendedAdapterTypeDeclaration : AdapterTypeDeclaration { var leftNetwork: AdapterNetworkDeclaration? var rightNetwork: AdapterNetworkDeclaration? diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt index f66a05530..3e747ea2b 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt @@ -9,8 +9,8 @@ import jetbrains.mps.ide.dialogs.project.creation.ModelCreateHelper import jetbrains.mps.model.ModelDeleteHelper import jetbrains.mps.project.AbstractModule import jetbrains.mps.project.MPSProject +import org.fbme.extensions.adapter.AdapterRevealService import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider -import org.fbme.extensions.adapter.ExtendedAdapterUtils import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration import org.jetbrains.mps.openapi.model.SModelName @@ -34,7 +34,7 @@ class RevealExtendedAdapterAction : AnAction(), DumbAware { val extendedAdapter = node?.let { repository.adapterOrNull(node) } ?: return@executeWriteActionInEditor - val extendedAdapterUtils = ExtendedAdapterUtils( + val adapterRevealService = AdapterRevealService( factory = factory, stFactory = repository.stFactory, owner = PlatformRepositoryProvider.getInstance(project), @@ -55,6 +55,6 @@ class RevealExtendedAdapterAction : AnAction(), DumbAware { .createModel() val nodeCopy = modelCopy.rootNodes.first { it.name == extendedAdapter.name } val extendedAdapterCopy = repository.adapter(nodeCopy) - extendedAdapterUtils.revealAdapter(extendedAdapterCopy, modelCopy) + adapterRevealService.revealAdapter(extendedAdapterCopy, modelCopy) } } \ No newline at end of file diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt index 1ac0f65e0..16d101694 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt @@ -5,8 +5,8 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAware import jetbrains.mps.ide.actions.MPSCommonDataKeys import jetbrains.mps.project.MPSProject +import org.fbme.extensions.adapter.AdapterRevealService import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider -import org.fbme.extensions.adapter.SystemUtils import org.fbme.lib.iec61499.declarations.SystemDeclaration class SyncSystemResources : AnAction(), DumbAware { @@ -29,7 +29,11 @@ class SyncSystemResources : AnAction(), DumbAware { val applicationDeclaration = node?.let { repository.adapterOrNull(node) } ?: return@executeWriteActionInEditor - val systemUtils = SystemUtils(factory, repository.stFactory, PlatformRepositoryProvider.getInstance(project)) - systemUtils.syncApplicationResources(applicationDeclaration, model) + val adapterRevealService = AdapterRevealService( + factory = factory, + stFactory = repository.stFactory, + owner = PlatformRepositoryProvider.getInstance(project) + ) + adapterRevealService.syncApplicationResources(applicationDeclaration, model) } } \ No newline at end of file diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps index fd4f81542..4518eb706 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps @@ -53,6 +53,7 @@ + @@ -637,7 +638,7 @@ - + @@ -645,7 +646,7 @@ - + @@ -653,7 +654,7 @@ - + @@ -665,7 +666,7 @@ - + @@ -673,7 +674,7 @@ - + @@ -681,7 +682,7 @@ - + @@ -693,7 +694,7 @@ - + @@ -979,7 +980,7 @@ - + @@ -987,7 +988,7 @@ - + @@ -995,7 +996,7 @@ - + @@ -1035,7 +1036,7 @@ - + @@ -1043,7 +1044,7 @@ - + @@ -1066,5 +1067,9 @@ + + + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps index 12569e350..6a76bf56d 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps @@ -11,25 +11,12 @@ - - - - - - - - - - - - - @@ -63,23 +50,18 @@ - - - - - - - - + + - + + + - @@ -112,9 +94,6 @@ - - - @@ -312,10 +291,8 @@ - - - - + + @@ -327,7 +304,9 @@ - + + + @@ -338,7 +317,9 @@ - + + + @@ -349,18 +330,18 @@ - - - - + + + + - - + + @@ -373,16 +354,16 @@ - - + + - - + + @@ -394,8 +375,8 @@ - - + + @@ -407,32 +388,32 @@ - - + + - - + + - - - + + + - - - + + + @@ -549,24 +530,24 @@ - - + + - + - - - + + + - + - - - + + + @@ -709,75 +690,220 @@ - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -785,7 +911,7 @@ - + @@ -793,7 +919,7 @@ - + @@ -813,20 +939,6 @@ - - - - - - - - - - - - - - @@ -834,36 +946,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -872,7 +957,7 @@ - + @@ -880,7 +965,7 @@ - + @@ -889,206 +974,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + + + - - - - - - - + + - - - - - - + + + + + + - - - - - - + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -1132,7 +1071,7 @@ - + @@ -1159,7 +1098,7 @@ - + @@ -1177,8 +1116,8 @@ - - + + @@ -1433,7 +1372,7 @@ - + @@ -1489,7 +1428,7 @@ - + @@ -1536,208 +1475,313 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + + + - - + + + + + + + + + + + - + - + - + - + - + - + - + - + - - + + - + - + - - - - + + + + - - - + + + - + - + - + - + - + - - - - + + + + - - + + - + - - - - + + + + - - + + - + - - - + + + - - - + + + - + - - - - + + + + - - - - - - + + + + + + - - - - + + + + - - + + - + - - - - + + + + - - + + - - + + + + + + + + + + + + + + + + + - - + + - + - - - - + + + + - - - + + + - + - - - - + + + + - - - + + + - + - + - + - + - + - - - - + + + + - - - + + + - + @@ -1745,64 +1789,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + - - - - - - - - - - - - - - - + + + + + + + + + - - - + + + + + + + From 5da351133a08e58ba21439c8a7aa59a2ae54a5b0 Mon Sep 17 00:00:00 2001 From: klinachev Date: Thu, 30 May 2024 01:56:28 +0300 Subject: [PATCH 11/14] Reveal extensions in module action added, few algorithm fixes, docs added --- code/extensions/build.gradle.kts | 10 +- .../extensions/adapter/AdapterRevealApi.kt | 22 +- .../adapter/AdapterRevealService.kt | 92 +++-- .../adapter/AdapterSwitchGenerator.kt | 38 ++- .../adapter/ExtendedAdapterUtils.kt | 319 ++++++++++++------ .../utils/FBInterfaceDeclarationUtils.kt | 68 ++-- .../extensions/utils/IEC61499FactoryUtils.kt | 40 ++- .../utils/AdapterRevealServiceTest.kt | 18 +- .../EventToNumberAdapter_4.fbt | 12 +- .../LeftCompositeBlockWithoutConnections.fbt | 53 --- .../NumberToEventAdapter_4.fbt | 12 +- .../RightCompositeBlockWithoutConnections.fbt | 33 -- .../left/CompositeBlockWithoutConnections.fbt | 53 +++ .../EA_LeftPublishSubscribeAdapter.fbt | 16 +- .../CompositeBlockWithoutConnections.fbt | 33 ++ .../EA_RightPublishSubscribeAdapter.fbt | 16 +- .../testRevealAdapter/CompositeBlock.fbt | 20 +- .../testRevealAdapter/EA_2_leftSwitch.fbt | 32 +- .../testRevealAdapter/EA_2_rightSwitch.fbt | 112 +++--- .../results/testRevealAdapter/EA_network1.fbt | 36 +- .../results/testRevealAdapter/EA_network2.fbt | 36 +- .../testRevealAdapter/RouterAdapter_EA.adp | 32 +- .../testRevealDeclarations/EA_network1.fbt | 10 +- .../testRevealDeclarations/EA_network2.fbt | 8 +- .../iec61499/descriptors/ExtendedPlugType.kt | 14 + .../descriptors/FBTypeDescriptorUtils.kt | 4 +- .../fbme/lib/iec61499/fbnetwork/FBNetwork.kt | 8 - code/richediting/build.gradle.kts | 1 - .../actions/GenerateAdapterRouterAction.kt | 91 ----- .../RevealAllExtendedAdaptersAction.kt | 59 ++++ .../actions/RevealExtendedAdapterAction.kt | 20 +- .../actions/SyncSystemResources.kt | 19 +- .../src/main/resources/META-INF/plugin.xml | 10 +- docs/images/extended-adapter/ea-usage.png | Bin 0 -> 75616 bytes .../internal-networks-interfaces.png | Bin 0 -> 219370 bytes docs/images/extended-adapter/plug-example.png | Bin 0 -> 43648 bytes .../images/extended-adapter/router-fields.png | Bin 0 -> 19055 bytes docs/pages/extended-adapter.md | 47 +++ .../FBME_E_SR.fbt | 4 +- .../header.iec61499 | 1 + 40 files changed, 836 insertions(+), 563 deletions(-) delete mode 100644 code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt delete mode 100644 code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt create mode 100644 code/extensions/src/test/resources/results/resourceNetworkBlocks/left/CompositeBlockWithoutConnections.fbt rename code/extensions/src/test/resources/results/resourceNetworkBlocks/{ => left}/EA_LeftPublishSubscribeAdapter.fbt (87%) create mode 100644 code/extensions/src/test/resources/results/resourceNetworkBlocks/right/CompositeBlockWithoutConnections.fbt rename code/extensions/src/test/resources/results/resourceNetworkBlocks/{ => right}/EA_RightPublishSubscribeAdapter.fbt (87%) delete mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt create mode 100644 code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt create mode 100644 docs/images/extended-adapter/ea-usage.png create mode 100644 docs/images/extended-adapter/internal-networks-interfaces.png create mode 100644 docs/images/extended-adapter/plug-example.png create mode 100644 docs/images/extended-adapter/router-fields.png create mode 100644 docs/pages/extended-adapter.md diff --git a/code/extensions/build.gradle.kts b/code/extensions/build.gradle.kts index e56541f75..590dd2c7a 100644 --- a/code/extensions/build.gradle.kts +++ b/code/extensions/build.gradle.kts @@ -21,7 +21,11 @@ dependencies { } mps { - moduleName.set("org.fbme.extensions.lib") + moduleName.set("org.fbme.platform.lib") +} + +val compileKotlin by tasks.getting(KotlinCompile::class) { + kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all") } val test by tasks.getting(Test::class) { @@ -31,6 +35,6 @@ val test by tasks.getting(Test::class) { ) } -val compileKotlin by tasks.getting(KotlinCompile::class) { - kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all") +val copyLibs by tasks.getting(Copy::class) { + dependsOn(":code:language:jar") } \ No newline at end of file diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt index e625c334c..a5f1d7374 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealApi.kt @@ -5,14 +5,32 @@ import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclarati import org.jetbrains.mps.openapi.model.SModel interface AdapterRevealApi { + /** + * Reveals usages of the provided extended adapter in the provided model. + * Generates new nodes in the same model and deletes all the extended adapters from the model. + * + * @param removeAdapter If true removes provided extendedAdapter after reveal + */ fun revealAdapter( extendedAdapter: ExtendedAdapterTypeDeclaration, - currentModel: SModel, + model: SModel, removeAdapter: Boolean = true, ): RevealDeclarationsResult - fun revealAllAdapters(currentModel: SModel) + /** + * Reveals inplace all extended adapters that are declared in the model. + * Generates new nodes in the same model and removes all extended adapters from the model. + */ + fun revealModel(model: SModel) + /** + * Synchronize system resources by revealing all "broken connections" from each application halfway in each resource. + * At the end, a block of connections is added that adapts the adapter for communication using the PUBLISH/SUBSCRIBE. + * If a port already has any connections in the resource it's connections will no be revealed. + * + * @param systemDeclaration Declaration to sync + * @param model Model to generate new nodes into + */ fun syncApplicationResources( systemDeclaration: SystemDeclaration, model: SModel, diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt index 77181a547..a04174087 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt @@ -3,6 +3,7 @@ package org.fbme.extensions.adapter import org.fbme.extensions.utils.IEC61499FactoryUtils import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.ide.iec61499.repository.PlatformRepository +import org.fbme.lib.common.Declaration import org.fbme.lib.common.Identifier import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.DeclarationWithNetwork @@ -12,10 +13,12 @@ import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclarati import org.fbme.lib.iec61499.declarations.hierarchies.ResourceFunctionBlockHierarchy import org.fbme.lib.iec61499.descriptors.FBPortDescriptor import org.fbme.lib.iec61499.fbnetwork.EntryKind +import org.fbme.lib.iec61499.fbnetwork.FBNetwork import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclarationBase import org.fbme.lib.st.STFactory import org.jetbrains.mps.openapi.model.SModel +import org.jetbrains.mps.openapi.model.SNode class AdapterRevealService( factory: IEC61499Factory, @@ -29,29 +32,34 @@ class AdapterRevealService( override fun revealAdapter( extendedAdapter: ExtendedAdapterTypeDeclaration, - currentModel: SModel, + model: SModel, removeAdapter: Boolean, ): RevealDeclarationsResult { - val revealDeclarations = extendedAdapterUtils.createDeclarations(extendedAdapter, currentModel) + val revealDeclarations = extendedAdapterUtils.createDeclarations(extendedAdapter, model) val declarationsResults = listOf(revealDeclarations) val identifiersToRevealResult = declarationsResults.associateBy { it.extendedAdapter.identifier } revealAdapters( - modelToGenerateNodesIn = currentModel, - modelToCheck = currentModel, + model = model, identifiersToRevealResult = identifiersToRevealResult, ) - if (!removeAdapter) { - return revealDeclarations + if (removeAdapter) { + removeExtendedAdapters(declarationsResults, model) } + return revealDeclarations + } + + private fun removeExtendedAdapters( + declarationsResults: Collection, + currentModel: SModel + ) { for (revealResult in declarationsResults) { val node = (revealResult.extendedAdapter as? PlatformElement)?.node if (node != null) { currentModel.removeRootNode(node) } } - return revealDeclarations } fun revealAdapterWithNetBlocks( @@ -62,66 +70,56 @@ class AdapterRevealService( model: SModel, ) = extendedAdapterUtils.revealAdapterWithNetBlocks(revealResult, block, port, count, model) - override fun revealAllAdapters(currentModel: SModel) { + override fun revealModel(model: SModel) { val identifiersToRevealResult = hashMapOf() - for (module in currentModel.repository.modules) { - for (model in module.models) { - for (node in model.rootNodes) { - if (node is ExtendedAdapterTypeDeclaration) { - identifiersToRevealResult[node.identifier] = extendedAdapterUtils.createDeclarations(node, currentModel) - } - } - } - } - for (module in currentModel.repository.modules) { - for (model in module.models) { - revealAdapters( - modelToGenerateNodesIn = currentModel, - modelToCheck = model, - identifiersToRevealResult = identifiersToRevealResult - ) + val rootNodes = model.rootNodes.toList() + for (node in rootNodes) { + val adapter = owner.adapterOrNull(node) + if (adapter != null) { + identifiersToRevealResult[adapter.identifier] = extendedAdapterUtils.createDeclarations(adapter, model) } } + revealAdapters( + model = model, + identifiersToRevealResult = identifiersToRevealResult + ) + removeExtendedAdapters(identifiersToRevealResult.values, model) } private fun revealAdapters( - modelToGenerateNodesIn: SModel, - modelToCheck: SModel, + model: SModel, identifiersToRevealResult: Map ) { - val rootNodes = modelToCheck.rootNodes.toList() + val rootNodes = model.rootNodes.toList() for (node in rootNodes) { - val fbTypeDeclaration = owner.adapterOrNull(node) - if (fbTypeDeclaration != null) { - fbTypeDeclaration.sockets.forEach { - val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] - if (revealDeclarationsResult != null) { - it.typeReference.setTarget(revealDeclarationsResult.getFarRightAdapter()) - } - } - fbTypeDeclaration.plugs.forEach { - val revealDeclarationsResult = identifiersToRevealResult[it.typeReference.getTarget()?.identifier] - if (revealDeclarationsResult != null) { - it.typeReference.setTarget(revealDeclarationsResult.getFarLeftAdapter()) - } - } - } + val fbTypeDeclaration = owner.adapterOrNull(node) ?: continue + extendedAdapterUtils.changeBlockPorts(fbTypeDeclaration, identifiersToRevealResult) } for (node in rootNodes) { - val declarationWithNetwork = owner.adapterOrNull(node) - if (declarationWithNetwork != null) { + val networks = getNetworks(node) ?: continue + for (network in networks) { val sourceIdentifiersToRevealResult = identifiersToRevealResult.mapKeys { it.value.getFarLeftAdapter().identifier } extendedAdapterUtils.revealExtendedAdaptersInNetwork( - network = declarationWithNetwork.network, + network = network, identifiersToRevealResult = sourceIdentifiersToRevealResult, - model = modelToGenerateNodesIn, + model = model, withPublishSubscribe = false, ) } } } + private fun getNetworks(node: SNode): List? = + when (val declaration = owner.adapterOrNull(node)) { + is DeclarationWithNetwork -> listOf(declaration.network) + is ExtendedAdapterTypeDeclaration -> + listOfNotNull(declaration.leftNetwork?.network, declaration.rightNetwork?.network) + is SystemDeclaration -> declaration.applications.map { it.network } + + declaration.devices.flatMap { device -> device.resources.map { it.network } } + else -> null + } + override fun syncApplicationResources( systemDeclaration: SystemDeclaration, model: SModel, @@ -207,6 +205,6 @@ class AdapterRevealService( fun revealDeclarations( extendedAdapter: ExtendedAdapterTypeDeclaration, - model: SModel?, + model: SModel, ): RevealDeclarationsResult = extendedAdapterUtils.createDeclarations(extendedAdapter, model) } diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt index 122f61ee5..a63080675 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt @@ -24,10 +24,10 @@ class AdapterSwitchGenerator( fun generateRouter( name: String, model: SModel, - adapter: AdapterTypeDeclaration, + source: AdapterTypeDeclaration, outputsCount: Int, outputRouterName: String, - plugAdapterTypeDeclaration: AdapterTypeDeclaration? = null, + target: AdapterTypeDeclaration? = null, inputRouterName: String? = null, virtualPackage: String? = null, ): CompositeFBTypeDeclaration { @@ -37,12 +37,12 @@ class AdapterSwitchGenerator( model.addRootNodes(routerDeclaration, virtualPackage = virtualPackage) val socket = factory.createSocketDeclaration(StringIdentifier("socket")) - socket.typeReference.setTarget(adapter) + socket.typeReference.setTarget(source) routerDeclaration.sockets += socket for (i in 0 until outputsCount) { val plug = factory.createPlugDeclaration(StringIdentifier("plug_$i")) - plug.typeReference.setTarget(plugAdapterTypeDeclaration ?: adapter) + plug.typeReference.setTarget(target ?: source) routerDeclaration.plugs += plug } addSocketToPlugsSwitch( @@ -90,10 +90,18 @@ class AdapterSwitchGenerator( val inputEvents = factoryUtils.copyEventsAndConnect( destination = switchDeclaration.inputEvents, destinationBlock = switchBlock, - source = source.type.eventOutputPorts.map { it.declaration as EventDeclaration }, + sources = source.type.eventOutputPorts.map { it.declaration as EventDeclaration }, sourceBlock = source, network = network, - ).associateBy { it.second.name } + keepAssociations = true, + ).onEach { (_, event) -> + for (association in event.associations) { + val name = association.parameterReference.getTarget()?.name + if (name != null) { + association.parameterReference.setTargetName(name) + } + } + }.associateBy { it.second.name } val startState = factory.createStateDeclaration(StringIdentifier("Start")) switchDeclaration.ecc.states += startState for ((i, plug) in targets.withIndex()) { @@ -109,10 +117,11 @@ class AdapterSwitchGenerator( factoryUtils.copyEventsAndConnect( destination = switchDeclaration.outputEvents, destinationBlock = switchBlock, - source = plug.type.eventInputPorts.map { it.declaration as EventDeclaration }, + sources = plug.type.eventInputPorts.map { it.declaration as EventDeclaration }, sourceBlock = plug, network = network, outputToInput = false, + keepAssociations = true, ).forEach { (sourceEvent, createdEvent) -> createdEvent.name += "_$i" for (association in createdEvent.associations) { @@ -161,11 +170,19 @@ class AdapterSwitchGenerator( val outputEvents = factoryUtils.copyEventsAndConnect( destination = switchDeclaration.outputEvents, destinationBlock = switchBlock, - source = target.type.eventInputPorts.map { it.declaration as EventDeclaration }, + sources = target.type.eventInputPorts.map { it.declaration as EventDeclaration }, sourceBlock = target, network = network, outputToInput = false, - ).associateBy { it.second.name } + keepAssociations = true, + ).onEach { (_, event) -> + for (association in event.associations) { + val name = association.parameterReference.getTarget()?.name + if (name != null) { + association.parameterReference.setTargetName(name) + } + } + }.associateBy { it.second.name } val routerParameterDeclaration = routerName?.let { router -> outputParameters.first { it.name == router } } @@ -183,9 +200,10 @@ class AdapterSwitchGenerator( factoryUtils.copyEventsAndConnect( destination = switchDeclaration.inputEvents, destinationBlock = switchBlock, - source = plug.type.eventOutputPorts.map { it.declaration as EventDeclaration }, + sources = plug.type.eventOutputPorts.map { it.declaration as EventDeclaration }, sourceBlock = plug, network = network, + keepAssociations = true, ).forEach { (sourceEvent, createdEvent) -> createdEvent.name += "_$i" for (association in createdEvent.associations) { diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt index 1e7a73098..2d87ea5c2 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt @@ -16,7 +16,6 @@ import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclarati import org.fbme.lib.iec61499.descriptors.FBPortDescriptor import org.fbme.lib.iec61499.fbnetwork.* import org.fbme.lib.st.STFactory -import org.fbme.lib.st.expressions.BinaryOperation import org.fbme.lib.st.types.ElementaryType import org.jetbrains.mps.openapi.model.SModel @@ -58,65 +57,27 @@ class ExtendedAdapterUtils( (declaration as? PlugDeclaration)?.typeReference?.getTarget() } - fun revealAdapterWithNetBlocks( - revealResult: RevealDeclarationsResult, - block: FunctionBlockDeclaration, - port: FBPortDescriptor, - count: Int, - model: SModel, - ) = if (port.isInput) { - val network = checkNotNull(block.container) - revealRightPart( - revealResult = revealResult, - leftPort = createRightPublishSubscribeAdapter( - revealResult = revealResult, - network = network, - number = 1, - model = model, - ), - rightPort = block.getPort(port), - network = network, - number = 1, - ) - } else { - val network = checkNotNull(block.container) - val portPaths = revealLeftPart( - revealResult = revealResult, - sourcePort = block.getPort(port), - network = network, - model = model, - connectionsCount = count, - ) - for ((number, connectionSourcePort) in portPaths.withIndex()) { - val publishSubscribeAdapter = createLeftPublishSubscribeAdapter( - revealResult = revealResult, - network = network, - number = number, - model = model, - ) - - network.adapterConnections += factoryUtils.createConnection( - source = connectionSourcePort, - target = publishSubscribeAdapter.getPort( - publishSubscribeAdapter.type.socketPorts.first() - ), - entryKind = EntryKind.ADAPTER, - ) - } - } - fun createDeclarations( extendedAdapter: ExtendedAdapterTypeDeclaration, - model: SModel?, + model: SModel, ): RevealDeclarationsResult { val name = extendedAdapter.name val FBInterfaceDeclarationUtils = FBInterfaceDeclarationUtils(factory) val routerAdapter = if (extendedAdapter.outputRouter != null) { - FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( + val adapter = FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( name = "RouterAdapter_$name", fbTypeDescriptor = extendedAdapter.plugTypeDescriptor, reversed = true, ) + for (event in adapter.outputEvents) { + event.associations += factoryUtils.createAssociation(adapter.outputParameters.last()) + } + if (extendedAdapter.inputRouter != null) { + for (event in adapter.inputEvents) { + event.associations += factoryUtils.createAssociation(adapter.inputParameters.last()) + } + } + adapter } else { null } @@ -172,7 +133,7 @@ class ExtendedAdapterUtils( network = it.network, ) } - model?.addRootNodes( + model.addRootNodes( routerAdapter, leftAdapter, middleAdapter, @@ -181,6 +142,12 @@ class ExtendedAdapterUtils( rightBlock, virtualPackage = getPackageName(extendedAdapter.name), ) + if (leftBlock != null) { + updateInternalAdapterPorts(leftBlock) + } + if (rightBlock != null) { + updateInternalAdapterPorts(rightBlock) + } return RevealDeclarationsResult( extendedAdapter = extendedAdapter, routerAdapter = routerAdapter, @@ -193,6 +160,143 @@ class ExtendedAdapterUtils( ) } + private data class ConnectionInfo( + val connection: FBNetworkConnection, + val source: Boolean, + val portPath: PortPath<*>, + ) { + val portName: String get() = portPath.portTarget.name + } + + fun changeBlockPorts( + fbTypeDeclaration: FBTypeDeclaration, + identifiersToRevealResult: Map + ) { + val socketsToChange = fbTypeDeclaration.sockets.filter { socket -> + identifiersToRevealResult[socket.typeReference.getTarget()?.identifier] != null + } + val plugsToChange = fbTypeDeclaration.plugs.filter { plug -> + identifiersToRevealResult[plug.typeReference.getTarget()?.identifier] != null + } + when (fbTypeDeclaration) { + is CompositeFBTypeDeclaration -> { + val network = fbTypeDeclaration.network + val networkConnections = network.eventConnections.asSequence() + .plus(network.dataConnections) + .plus(network.adapterConnections) + .toList() + + val blockToConnections = networkConnections.asSequence() + .flatMap { connection -> + val targetPortPath = connection.targetReference.getTarget() + val target = targetPortPath?.functionBlock + ?.let { it.identifier to ConnectionInfo(connection, false, targetPortPath) } + val sourcePortPath = connection.sourceReference.getTarget() + val source = sourcePortPath?.functionBlock + ?.let { it.identifier to ConnectionInfo(connection, true, sourcePortPath) } + listOfNotNull(target, source) + } + .groupBy({ it.first }, { it.second }) + for (socket in socketsToChange) { + val connectionToTargetPort = blockToConnections[socket.identifier] ?: continue + val result = identifiersToRevealResult[socket.typeReference.getTarget()?.identifier] ?: continue + changePorts(connectionToTargetPort, socket) + socket.typeReference.setTarget(result.getFarRightAdapter()) + } + for (plug in plugsToChange) { + val connectionToTargetPort = blockToConnections[plug.identifier] ?: continue + val result = identifiersToRevealResult[plug.typeReference.getTarget()?.identifier] ?: continue + changePorts(connectionToTargetPort, plug) + plug.typeReference.setTarget(result.getFarLeftAdapter()) + } + } + + is BasicFBTypeDeclaration -> { + for (socket in socketsToChange) { + val result = identifiersToRevealResult[socket.typeReference.getTarget()?.identifier] ?: continue + socket.typeReference.setTarget(result.getFarRightAdapter()) + } + for (plug in plugsToChange) { + val result = identifiersToRevealResult[plug.typeReference.getTarget()?.identifier] ?: continue + plug.typeReference.setTarget(result.getFarLeftAdapter()) + } + } + + is ServiceInterfaceFBTypeDeclaration -> error("Extended adapters cannot be used in service interface block") + } + } + + private fun changePorts( + connectionToTargetPort: List, + block: FunctionBlockDeclarationBase, + ) { + for (connectionInfo in connectionToTargetPort) { + if (connectionInfo.source) { + val ports = when (connectionInfo.connection.kind) { + EntryKind.EVENT -> block.type.eventOutputPorts + EntryKind.DATA -> block.type.dataOutputPorts + EntryKind.ADAPTER -> block.type.plugPorts + } + connectionInfo.connection.sourceReference.setTarget( + block.getPort(ports.first { it.name == connectionInfo.portName }), + ) + } else { + val ports = when (connectionInfo.connection.kind) { + EntryKind.EVENT -> block.type.eventInputPorts + EntryKind.DATA -> block.type.dataInputPorts + EntryKind.ADAPTER -> block.type.socketPorts + } + connectionInfo.connection.targetReference.setTarget( + block.getPort(ports.first { it.name == connectionInfo.portName }), + ) + } + } + } + + fun revealAdapterWithNetBlocks( + revealResult: RevealDeclarationsResult, + block: FunctionBlockDeclaration, + port: FBPortDescriptor, + count: Int, + model: SModel, + ) = if (port.isInput) { + val network = checkNotNull(block.container) + revealRightPart( + revealResult = revealResult, + leftPort = createRightPublishSubscribeAdapter( + revealResult = revealResult, + network = network, + model = model, + ), + rightPort = block.getPort(port), + network = network, + ) + } else { + val network = checkNotNull(block.container) + val portPaths = revealLeftPart( + revealResult = revealResult, + sourcePort = block.getPort(port), + network = network, + model = model, + connectionsCount = count, + ) + for (connectionSourcePort in portPaths) { + val publishSubscribeAdapter = createLeftPublishSubscribeAdapter( + revealResult = revealResult, + network = network, + model = model, + ) + + network.adapterConnections += factoryUtils.createConnection( + source = connectionSourcePort, + target = publishSubscribeAdapter.getPort( + publishSubscribeAdapter.type.socketPorts.first() + ), + entryKind = EntryKind.ADAPTER, + ) + } + } + fun revealExtendedAdaptersInNetwork( network: FBNetwork, identifiersToRevealResult: Map, @@ -223,11 +327,10 @@ class ExtendedAdapterUtils( connectionsCount = connections.size, ) if (withPublishSubscribe) { - for ((number, connectionSourcePort) in portsBeforePublishBlocks.withIndex()) { + for (connectionSourcePort in portsBeforePublishBlocks) { val publishSubscribeAdapter = createLeftPublishSubscribeAdapter( revealResult = revealResult, network = network, - number = number, model = model, ) @@ -250,7 +353,6 @@ class ExtendedAdapterUtils( createRightPublishSubscribeAdapter( revealResult = revealResult, network = network, - number = i, model = model, ) } else { @@ -258,7 +360,6 @@ class ExtendedAdapterUtils( }, rightPort = checkNotNull(connection.targetReference.getTarget()), network = network, - number = i, ) } } @@ -284,8 +385,8 @@ class ExtendedAdapterUtils( switchGenerator.generateRouter( name = "${adapterType.name}_$connectionsCount", model = model, - adapter = checkNotNull(revealResult.routerAdapter), - plugAdapterTypeDeclaration = revealResult.leftAdapter, + source = checkNotNull(revealResult.routerAdapter), + target = revealResult.leftAdapter, outputsCount = connectionsCount, outputRouterName = outputRouter.name, inputRouterName = adapterType.inputRouter?.name, @@ -305,12 +406,12 @@ class ExtendedAdapterUtils( ) routerBlock.type.plugPorts.map { routerBlock.getPort(it) } } - return portsBeforePublishBlocks.mapIndexed { index, it -> + return portsBeforePublishBlocks.map { connectBlockReturnPlugPort( blockDeclaration = revealResult.leftBlockDeclaration, sourcePort = it, network = network, - name = revealResult.leftBlockDeclaration?.let { "${it.name}_$index" }, + name = revealResult.leftBlockDeclaration?.name, ) } } @@ -318,7 +419,6 @@ class ExtendedAdapterUtils( private fun createLeftPublishSubscribeAdapter( revealResult: RevealDeclarationsResult, network: FBNetwork, - number: Int, model: SModel, ): FunctionBlockDeclaration { val adapterType = revealResult.extendedAdapter @@ -341,13 +441,12 @@ class ExtendedAdapterUtils( val leftPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( blockType = leftPublishSubscribeAdapter, network = network, - name = "${leftPublishSubscribeAdapter.name}_$number", ) addPublishSubscribeBlocks( source = leftPublishSubscribeAdapterBlock, network = network, currentModel = model, - name = "Left_${adapterType.name}_$number", + name = "Left_${adapterType.name}", ) return leftPublishSubscribeAdapterBlock } @@ -357,13 +456,12 @@ class ExtendedAdapterUtils( leftPort: PortPath, rightPort: PortPath, network: FBNetwork, - number: Int, ) { val portBeforeSocketBlock = connectBlockReturnPlugPort( blockDeclaration = revealResult.rightBlockDeclaration, sourcePort = leftPort, network = network, - name = revealResult.rightBlockDeclaration?.let { "${it.name}_$number" } + name = revealResult.rightBlockDeclaration?.name, ) network.adapterConnections += factoryUtils.createConnection( @@ -376,7 +474,6 @@ class ExtendedAdapterUtils( private fun createRightPublishSubscribeAdapter( revealResult: RevealDeclarationsResult, network: FBNetwork, - number: Int, model: SModel, ): PortPath { val adapterType = revealResult.extendedAdapter @@ -399,13 +496,12 @@ class ExtendedAdapterUtils( val rightPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( blockType = rightPublishSubscribeAdapter, network = network, - name = "${rightPublishSubscribeAdapter.name}_$number", ) addPublishSubscribeBlocks( source = rightPublishSubscribeAdapterBlock, network = network, currentModel = model, - name = "Right_${adapterType.name}_$number", + name = "Right_${adapterType.name}", ) return PortPath.createPlugPortPath( functionBlock = rightPublishSubscribeAdapterBlock, @@ -452,7 +548,7 @@ class ExtendedAdapterUtils( val eventToNumberBlock = factoryUtils.addFunctionalBlock(eventToNumberFbType, compositeFBType.network) val numberToEventFbType = numberToEventFbTypes.computeIfAbsent( - typeDescriptor.eventOutputPorts.size + typeDescriptor.eventInputPorts.size ) { number -> createNumberToEventConverter( name = "NumberToEventAdapter_$number", @@ -462,14 +558,14 @@ class ExtendedAdapterUtils( val numberToEventBlock = factoryUtils.addFunctionalBlock(numberToEventFbType, compositeFBType.network) - // events: inputs -> numberToEventBlock -> socket -> eventToNumberBlock -> outputs - // data: inputs -> numberToEventBlock - // data: eventToNumberBlock -> outputs - // data: inputs -> socket -> outputs + // event way: inputs -> numberToEventBlock -> socket -> eventToNumberBlock -> outputs + // data way: inputs -> numberToEventBlock + // data way: eventToNumberBlock -> outputs + // data way: inputs -> socket -> outputs factoryUtils.copyEventsAndConnect( destination = compositeFBType.inputEvents, destinationBlock = null, - source = numberToEventFbType.inputEvents, + sources = numberToEventFbType.inputEvents, sourceBlock = numberToEventBlock, network = compositeFBType.network, outputToInput = false, @@ -503,7 +599,7 @@ class ExtendedAdapterUtils( factoryUtils.copyEventsAndConnect( destination = compositeFBType.outputEvents, destinationBlock = null, - source = eventToNumberFbType.outputEvents, + sources = eventToNumberFbType.outputEvents, sourceBlock = eventToNumberBlock, network = compositeFBType.network ) @@ -529,6 +625,12 @@ class ExtendedAdapterUtils( sourceBlock = declaration, network = compositeFBType.network ) + for (newInput in compositeFBType.inputEvents) { + newInput.associations += compositeFBType.inputParameters.map { factoryUtils.createAssociation(it) } + } + for (newOutput in compositeFBType.outputEvents) { + newOutput.associations += compositeFBType.outputParameters.map { factoryUtils.createAssociation(it) } + } return compositeFBType } @@ -544,9 +646,7 @@ class ExtendedAdapterUtils( val parameterOutput = factory.createParameterDeclaration(StringIdentifier("I_E_number")) parameterOutput.type = ElementaryType.INT basicFbType.outputParameters += parameterOutput - val outputAssociation = factory.createEventAssociation() - outputAssociation.parameterReference.setTarget(parameterOutput) - outputEvent.associations += outputAssociation + outputEvent.associations += factoryUtils.createAssociation(parameterOutput) val start = factory.createStateDeclaration(StringIdentifier("Start")) basicFbType.ecc.states += start for (i in basicFbType.inputEvents.indices) { @@ -596,21 +696,13 @@ class ExtendedAdapterUtils( val event = basicFbType.outputEvents[i] val state = factory.createStateDeclaration(StringIdentifier(event.name)) basicFbType.ecc.states += state - val startToState = factory.createStateTransition() - basicFbType.ecc.transitions += startToState - startToState.condition.setGuardCondition(stFactoryUtils.intEquality(parameterInput, i)) - startToState.sourceReference.setTarget(start) - startToState.targetReference.setTarget(state) - - val equality = stFactory.createBinaryExpression(BinaryOperation.EQ) - equality.rightExpression = stFactoryUtils.createIntLiteral(i) - equality.leftExpression = stFactoryUtils.createVariable(parameterInput) - - val stateToStart = factory.createStateTransition() - basicFbType.ecc.transitions += stateToStart - stateToStart.sourceReference.setTarget(state) - stateToStart.targetReference.setTarget(start) - + basicFbType.ecc.transitions += factoryUtils.createStateTransition( + source = start, + target = state, + eventCondition = inputEvent, + condition = stFactoryUtils.intEquality(parameterInput, i) + ) + basicFbType.ecc.transitions += factoryUtils.createStateTransition(state, start) val stateAction = factory.createStateAction() state.actions += stateAction stateAction.event.setFQName(event.name) @@ -698,17 +790,54 @@ class ExtendedAdapterUtils( val plug = factory.createPlugDeclaration(rightAdapter.identifier) plug.typeReference.setTarget(rightAdapter) - plug.name = "Plug_Connection" + plug.name = "plug" composite.plugs += plug val socket = factory.createSocketDeclaration(leftAdapter.identifier) socket.typeReference.setTarget(leftAdapter) - socket.name = "Socket_Connection" + socket.name = "socket" composite.sockets += socket - composite.network.copyElements(network) + composite.network.functionBlocks += network.functionBlocks.map { it.copy() as FunctionBlockDeclaration } + composite.network.adapterConnections += network.adapterConnections.map { it.copy() as FBNetworkConnection } + composite.network.eventConnections += network.eventConnections.map { it.copy() as FBNetworkConnection } + composite.network.dataConnections += network.dataConnections.map { it.copy() as FBNetworkConnection } + composite.network.endpointCoordinates += network.endpointCoordinates.map { it.copy() as EndpointCoordinate } return composite } + + private fun updateInternalAdapterPorts(block: CompositeFBTypeDeclaration) { + val plug = block.plugs.first() + val socket = block.sockets.first() + for (connection in block.network.eventConnections.asSequence() + .plus(block.network.dataConnections) + .plus(block.network.adapterConnections) + .toList()) { + val source = connection.sourceReference.getTarget() + val newSource = getPortForInternalAdapterBlock(source, plug, socket) + if (newSource != null) { + connection.sourceReference.setTarget(newSource) + } + val target = connection.targetReference.getTarget() + val newTarget = getPortForInternalAdapterBlock(target, plug, socket) + if (newTarget != null) { + connection.targetReference.setTarget(newTarget) + } + } + } + + private fun getPortForInternalAdapterBlock( + port: PortPath<*>?, + plug: PlugDeclaration, + socket: SocketDeclaration, + ): PortPath? { + if (port?.functionBlock?.name == "Plug_Connection") { + return plug.ports.first { it.portTarget.name == port.portTarget.name } + } else if (port?.functionBlock?.name == "Socket_Connection") { + return socket.ports.first { it.portTarget.name == port.portTarget.name } + } + return null + } } fun SModel.addRootNodes( diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt index 39230f7c7..1a1087915 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/FBInterfaceDeclarationUtils.kt @@ -3,10 +3,7 @@ package org.fbme.extensions.utils import org.fbme.lib.common.Identifier import org.fbme.lib.common.StringIdentifier import org.fbme.lib.iec61499.IEC61499Factory -import org.fbme.lib.iec61499.declarations.AdapterTypeDeclaration -import org.fbme.lib.iec61499.declarations.EventDeclaration -import org.fbme.lib.iec61499.declarations.FBInterfaceDeclaration -import org.fbme.lib.iec61499.declarations.ParameterDeclaration +import org.fbme.lib.iec61499.declarations.* import org.fbme.lib.iec61499.descriptors.FBTypeDescriptor class FBInterfaceDeclarationUtils( @@ -20,10 +17,10 @@ class FBInterfaceDeclarationUtils( ) = copy( reversed = reversed, declaration = target, - outputEvents = source.outputEvents.map { it.copy() as EventDeclaration }, - inputEvents = source.inputEvents.map { it.copy() as EventDeclaration }, - parameterOutputs = source.outputParameters.map { it.copy() as ParameterDeclaration }, - parametersInputs = source.inputParameters.map { it.copy() as ParameterDeclaration }, + outputEvents = source.outputEvents, + inputEvents = source.inputEvents, + parameterOutputs = source.outputParameters, + parametersInputs = source.inputParameters, ) fun copyPorts( @@ -33,14 +30,12 @@ class FBInterfaceDeclarationUtils( ) = copy( reversed = reversed, declaration = declaration, - outputEvents = fbTypeDescriptor.eventOutputPorts - .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, - inputEvents = fbTypeDescriptor.eventInputPorts - .map { checkNotNull(it.declaration?.copy() as? EventDeclaration) }, + outputEvents = fbTypeDescriptor.eventOutputPorts.map { checkNotNull(it.declaration as EventDeclaration) }, + inputEvents = fbTypeDescriptor.eventInputPorts.map { checkNotNull(it.declaration as EventDeclaration) }, parameterOutputs = fbTypeDescriptor.dataOutputPorts - .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) }, + .map { checkNotNull(it.declaration as ParameterDeclaration) }, parametersInputs = fbTypeDescriptor.dataInputPorts - .map { checkNotNull(it.declaration?.copy() as? ParameterDeclaration) } + .map { checkNotNull(it.declaration as ParameterDeclaration) }, ) fun copy( @@ -49,19 +44,45 @@ class FBInterfaceDeclarationUtils( outputEvents: List, inputEvents: List, parameterOutputs: List, - parametersInputs: List + parametersInputs: List, ) { if (reversed) { - declaration.inputEvents += outputEvents - declaration.outputEvents += inputEvents - declaration.inputParameters += parameterOutputs - declaration.outputParameters += parametersInputs + copy( + reversed = false, + declaration = declaration, + outputEvents = inputEvents, + inputEvents = outputEvents, + parameterOutputs = parametersInputs, + parametersInputs = parameterOutputs, + ) return } - declaration.inputEvents += inputEvents - declaration.outputEvents += outputEvents - declaration.inputParameters += parametersInputs - declaration.outputParameters += parameterOutputs + val newParametersInputs = parametersInputs.map { it.copy() as ParameterDeclaration } + declaration.inputParameters += newParametersInputs + val newParameterOutputs = parameterOutputs.map { it.copy() as ParameterDeclaration } + declaration.outputParameters += newParameterOutputs + + declaration.inputEvents += inputEvents.map { event -> + (event.copy() as EventDeclaration).also { newEvent -> + newEvent.associations.replaceAll { copyAssociation(it, newParametersInputs) } + } + } + declaration.outputEvents += outputEvents.map { event -> + (event.copy() as EventDeclaration).also { newEvent -> + newEvent.associations.replaceAll { copyAssociation(it, newParameterOutputs) } + } + } + } + + fun copyAssociation( + association: EventAssociation, + newParametersInputs: List + ): EventAssociation { + val newAssociation = association.copy() as EventAssociation + newAssociation.parameterReference.setTarget( + newParametersInputs.first { it.name == association.parameterReference.getTarget()?.name } + ) + return newAssociation } } @@ -96,5 +117,4 @@ class FBInterfaceDeclarationUtils( copyPorts(adapterTypeDeclaration, fbTypeDescriptor, reversed) return adapterTypeDeclaration } - } \ No newline at end of file diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt index e946f5d99..80d36faa9 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/IEC61499FactoryUtils.kt @@ -2,12 +2,14 @@ package org.fbme.extensions.utils import org.fbme.lib.common.Declaration import org.fbme.lib.iec61499.IEC61499Factory +import org.fbme.lib.iec61499.declarations.EventAssociation import org.fbme.lib.iec61499.declarations.EventDeclaration import org.fbme.lib.iec61499.declarations.FBTypeDeclaration import org.fbme.lib.iec61499.declarations.ParameterDeclaration import org.fbme.lib.iec61499.ecc.StateDeclaration import org.fbme.lib.iec61499.ecc.StateTransition import org.fbme.lib.iec61499.fbnetwork.* +import org.fbme.lib.st.expressions.Expression class IEC61499FactoryUtils( private val factory: IEC61499Factory, @@ -16,6 +18,7 @@ class IEC61499FactoryUtils( source: StateDeclaration, target: StateDeclaration, eventCondition: EventDeclaration? = null, + condition: Expression? = null, ): StateTransition { val transition = factory.createStateTransition() transition.sourceReference.setTarget(source) @@ -23,6 +26,9 @@ class IEC61499FactoryUtils( if (eventCondition != null) { transition.condition.eventReference.setFQName(eventCondition.name) } + if (condition != null) { + transition.condition.setGuardCondition(condition) + } return transition } @@ -49,6 +55,14 @@ class IEC61499FactoryUtils( return connection } + fun createAssociation( + declaration: ParameterDeclaration, + ): EventAssociation { + val eventAssociation = factory.createEventAssociation() + eventAssociation.parameterReference.setTarget(declaration) + return eventAssociation + } + fun addFunctionalBlock( blockType: FBTypeDeclaration, network: FBNetwork, @@ -64,22 +78,36 @@ class IEC61499FactoryUtils( return block } - private fun getUnusedName(newName: String, names: Set): String = if (newName in names) { - getUnusedName("${newName}_", names) + private fun getUnusedName(name: String, usedNames: Set): String = if (name in usedNames) { + getUnusedName(name, usedNames, 2) } else { - newName + name + } + + private fun getUnusedName(name: String, usedNames: Set, id: Int): String { + val nextName = "${name}_$id" + return if (nextName in usedNames) { + getUnusedName(name, usedNames, id + 1) + } else { + nextName + } } fun copyEventsAndConnect( destination: MutableList, destinationBlock: FunctionBlockDeclarationBase?, - source: List, + sources: List, sourceBlock: FunctionBlockDeclarationBase?, network: FBNetwork, outputToInput: Boolean = true, + keepAssociations: Boolean = false, ): List> { - val eventsAndCopies = source.map { - it to it.copy() as EventDeclaration + val eventsAndCopies = sources.map { source -> + val copy = source.copy() as EventDeclaration + if (!keepAssociations) { + copy.associations.clear() + } + source to copy } destination += eventsAndCopies.map { it.second } diff --git a/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt index 9932d082e..f5536eb68 100644 --- a/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt +++ b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt @@ -42,13 +42,13 @@ class AdapterRevealServiceTest : PlatformTestBase() { project.repository.modelAccess.runWriteAction { val model = TemporaryModels.getInstance().create(false, false, "tmp", TempModuleOptions.forDefaultModule()) model.addRootNode((extendedAdapterType as PlatformElement).node) - val revealDeclarations = adapterRevealService.revealDeclarations(extendedAdapterType, null) + val revealDeclarations = adapterRevealService.revealDeclarations(extendedAdapterType, model) - assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) - assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) assertEqualStrings(leftAdapter, revealDeclarations.leftAdapter.toDocument()) assertEqualStrings(middleAdapter, revealDeclarations.middleAdapter.toDocument()) assertEqualStrings(rightAdapter, revealDeclarations.rightAdapter.toDocument()) + assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) + assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) } } @@ -74,11 +74,11 @@ class AdapterRevealServiceTest : PlatformTestBase() { val revealDeclarations = adapterRevealService.revealAdapter(extendedAdapterType, model) - assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) - assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) assertEqualStrings(leftAdapter, revealDeclarations.leftAdapter.toDocument()) assertEqualStrings(middleAdapter, revealDeclarations.middleAdapter.toDocument()) assertEqualStrings(rightAdapter, revealDeclarations.rightAdapter.toDocument()) + assertEqualStrings(leftBlock, assertNotNull(revealDeclarations.leftBlockDeclaration).toDocument()) + assertEqualStrings(rightBlock, assertNotNull(revealDeclarations.rightBlockDeclaration).toDocument()) assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) checkNode(model, "$resultPath/CompositeBlock.fbt", "CompositeBlock") checkNode(model, "$resultPath/EA_2_leftSwitch.fbt", "EA_2_leftSwitch") @@ -116,8 +116,8 @@ class AdapterRevealServiceTest : PlatformTestBase() { ) assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) - checkNode(model, "$networkPath/LeftCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") - checkNode(model, "$networkPath/EA_LeftPublishSubscribeAdapter.fbt", "EA_LeftPublishSubscribeAdapter") + checkNode(model, "$networkPath/left/CompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") + checkNode(model, "$networkPath/left/EA_LeftPublishSubscribeAdapter.fbt", "EA_LeftPublishSubscribeAdapter") checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") } @@ -152,8 +152,8 @@ class AdapterRevealServiceTest : PlatformTestBase() { ) assertEqualStrings(routerAdapter, assertNotNull(revealDeclarations.routerAdapter?.toDocument())) - checkNode(model, "$networkPath/RightCompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") - checkNode(model, "$networkPath/EA_RightPublishSubscribeAdapter.fbt", "EA_RightPublishSubscribeAdapter") + checkNode(model, "$networkPath/right/CompositeBlockWithoutConnections.fbt", "CompositeBlockWithoutConnections") + checkNode(model, "$networkPath/right/EA_RightPublishSubscribeAdapter.fbt", "EA_RightPublishSubscribeAdapter") checkNode(model, "$networkPath/EventToNumberAdapter_4.fbt", "EventToNumberAdapter_4") checkNode(model, "$networkPath/NumberToEventAdapter_4.fbt", "NumberToEventAdapter_4") } diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt index 35145fe72..2200bea61 100644 --- a/code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/EventToNumberAdapter_4.fbt @@ -10,7 +10,9 @@ - + + + @@ -41,16 +43,16 @@ - + - + - + - + diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt deleted file mode 100644 index b73280035..000000000 --- a/code/extensions/src/test/resources/results/resourceNetworkBlocks/LeftCompositeBlockWithoutConnections.fbt +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt index 2f81cd242..190a74e30 100644 --- a/code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/NumberToEventAdapter_4.fbt @@ -4,7 +4,9 @@ - + + + @@ -31,13 +33,13 @@ - + - + - + - + diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt deleted file mode 100644 index 80fa5a89d..000000000 --- a/code/extensions/src/test/resources/results/resourceNetworkBlocks/RightCompositeBlockWithoutConnections.fbt +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/left/CompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/left/CompositeBlockWithoutConnections.fbt new file mode 100644 index 000000000..2fbe36ff8 --- /dev/null +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/left/CompositeBlockWithoutConnections.fbt @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/left/EA_LeftPublishSubscribeAdapter.fbt similarity index 87% rename from code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/left/EA_LeftPublishSubscribeAdapter.fbt index 431d241e4..2402a4330 100644 --- a/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_LeftPublishSubscribeAdapter.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/left/EA_LeftPublishSubscribeAdapter.fbt @@ -4,10 +4,22 @@ - + + + + + + + - + + + + + + + diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/right/CompositeBlockWithoutConnections.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/right/CompositeBlockWithoutConnections.fbt new file mode 100644 index 000000000..b21599bdc --- /dev/null +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/right/CompositeBlockWithoutConnections.fbt @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt b/code/extensions/src/test/resources/results/resourceNetworkBlocks/right/EA_RightPublishSubscribeAdapter.fbt similarity index 87% rename from code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt rename to code/extensions/src/test/resources/results/resourceNetworkBlocks/right/EA_RightPublishSubscribeAdapter.fbt index 7ab4de909..5297190c6 100644 --- a/code/extensions/src/test/resources/results/resourceNetworkBlocks/EA_RightPublishSubscribeAdapter.fbt +++ b/code/extensions/src/test/resources/results/resourceNetworkBlocks/right/EA_RightPublishSubscribeAdapter.fbt @@ -4,10 +4,22 @@ - + + + + + + + - + + + + + + + diff --git a/code/extensions/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt index 57effde52..9bcdbe878 100644 --- a/code/extensions/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt +++ b/code/extensions/src/test/resources/results/testRevealAdapter/CompositeBlock.fbt @@ -8,18 +8,18 @@ - - - - + + + + - - - - - - + + + + + + diff --git a/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt index 60d6a57a1..22c469198 100644 --- a/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt +++ b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_leftSwitch.fbt @@ -4,10 +4,18 @@ - - - - + + + + + + + + + + + + @@ -82,28 +90,28 @@ - + - + - + - + - + - + - + - + diff --git a/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt index 547fab693..201d6bce9 100644 --- a/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt +++ b/code/extensions/src/test/resources/results/testRevealAdapter/EA_2_rightSwitch.fbt @@ -14,10 +14,18 @@ - - - - + + + + + + + + + + + + @@ -40,70 +48,70 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/code/extensions/src/test/resources/results/testRevealAdapter/EA_network1.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_network1.fbt index 2f1b2fd23..89f5ca737 100644 --- a/code/extensions/src/test/resources/results/testRevealAdapter/EA_network1.fbt +++ b/code/extensions/src/test/resources/results/testRevealAdapter/EA_network1.fbt @@ -4,32 +4,32 @@ - + - + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + diff --git a/code/extensions/src/test/resources/results/testRevealAdapter/EA_network2.fbt b/code/extensions/src/test/resources/results/testRevealAdapter/EA_network2.fbt index 0aec0bddb..813bce2cd 100644 --- a/code/extensions/src/test/resources/results/testRevealAdapter/EA_network2.fbt +++ b/code/extensions/src/test/resources/results/testRevealAdapter/EA_network2.fbt @@ -4,32 +4,32 @@ - + - + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + diff --git a/code/extensions/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp b/code/extensions/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp index cc5db7046..f70f2f938 100644 --- a/code/extensions/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp +++ b/code/extensions/src/test/resources/results/testRevealAdapter/RouterAdapter_EA.adp @@ -4,16 +4,32 @@ - - - - + + + + + + + + + + + + - - - - + + + + + + + + + + + + diff --git a/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network1.fbt b/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network1.fbt index 0a579578d..ecd8f8460 100644 --- a/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network1.fbt +++ b/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network1.fbt @@ -4,19 +4,19 @@ - + - + - + - - + + diff --git a/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network2.fbt b/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network2.fbt index d0209eafe..b3fca0b97 100644 --- a/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network2.fbt +++ b/code/extensions/src/test/resources/results/testRevealDeclarations/EA_network2.fbt @@ -4,18 +4,18 @@ - + - + - + - + diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt index e79844af9..842014e42 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/ExtendedPlugType.kt @@ -25,4 +25,18 @@ class ExtendedPlugType(override val myDeclaration: ExtendedAdapterTypeDeclaratio .run { myDeclaration.inputRouter?.let { plus(it) } ?: this } .toParametersPortDescriptors(isInput = false) .toList() + + override fun getAssociatedVariablesForInputEvent(eventNumber: Int): List { + if (myDeclaration.inputRouter != null) { + return super.getAssociatedVariablesForInputEvent(eventNumber) + dataOutputPorts.size + } + return super.getAssociatedVariablesForInputEvent(eventNumber) + } + + override fun getAssociatedVariablesForOutputEvent(eventNumber: Int): List { + if (myDeclaration.outputRouter != null) { + return super.getAssociatedVariablesForInputEvent(eventNumber) + dataInputPorts.size + } + return super.getAssociatedVariablesForOutputEvent(eventNumber) + } } diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt index 597e54852..8b8a7df26 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/descriptors/FBTypeDescriptorUtils.kt @@ -96,7 +96,7 @@ internal object FBTypeDescriptorUtils { } } -fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> +internal fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> FBPortDescriptor( name = event.name, connectionKind = EntryKind.EVENT, @@ -107,7 +107,7 @@ fun Sequence.toEventPortDescriptors(isInput: Boolean) = mapInd ) } -fun Sequence.toParametersPortDescriptors(isInput: Boolean) = +internal fun Sequence.toParametersPortDescriptors(isInput: Boolean) = mapIndexed { index, event -> FBPortDescriptor( name = event.name, diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt index b5f228e29..6d9f4ab43 100644 --- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt +++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/fbnetwork/FBNetwork.kt @@ -49,14 +49,6 @@ interface FBNetwork : Element { return components } - fun copyElements(network: FBNetwork) { - functionBlocks += network.functionBlocks.map { it.copy() as FunctionBlockDeclaration } - adapterConnections += network.adapterConnections.map { it.copy() as FBNetworkConnection } - dataConnections += network.dataConnections.map { it.copy() as FBNetworkConnection } - eventConnections += network.eventConnections.map { it.copy() as FBNetworkConnection } - endpointCoordinates += network.endpointCoordinates.map { it.copy() as EndpointCoordinate } - } - companion object { @JvmStatic fun extractNetwork(declaration: Declaration?): FBNetwork? { diff --git a/code/richediting/build.gradle.kts b/code/richediting/build.gradle.kts index c81387e1d..4403a2565 100644 --- a/code/richediting/build.gradle.kts +++ b/code/richediting/build.gradle.kts @@ -13,7 +13,6 @@ dependencies { compileOnly(project(":code:library")) compileOnly(project(":code:language")) compileOnly(project(":code:platform")) - compileOnly(project(":code:extensions")) implementation(project(":code:extensions")) implementation("org.eclipse.elk:org.eclipse.elk.alg.layered:0.7.1") diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt deleted file mode 100644 index c46661d83..000000000 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/GenerateAdapterRouterAction.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.fbme.ide.richediting.actions - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.ComboBox -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.ui.components.JBTextField -import com.intellij.ui.dsl.builder.* -import jetbrains.mps.ide.actions.MPSCommonDataKeys -import org.fbme.extensions.adapter.AdapterSwitchGenerator -import org.fbme.lib.iec61499.declarations.* -import org.fbme.lib.iec61499.fbnetwork.* -import org.fbme.lib.st.expressions.* -import org.fbme.lib.st.types.ElementaryType -import javax.swing.JComponent - - -class GenerateAdapterRouterAction : AnAction(), DumbAware { - - override fun update(event: AnActionEvent) = event.executeReadAction { - val repository = event.repository - val node = event.getData(MPSCommonDataKeys.NODE) - val adapterTypeDeclaration = node?.let { repository.adapterOrNull(node) } - event.presentation.isEnabledAndVisible = adapterTypeDeclaration != null - } - - override fun actionPerformed(event: AnActionEvent) { - val (outputsCount, routerName) = event.executeReadAction?> { - val repository = event.repository - - val node = event.getRequiredData(MPSCommonDataKeys.NODE) - val adapterTypeDeclaration = repository.adapter(node) - - val parameterNames = adapterTypeDeclaration.outputParameters - .mapNotNull { parameter -> parameter.name.takeIf { parameter.type == ElementaryType.INT } } - .toTypedArray() - val dialog = GenerateAdapterRouterDialog(checkNotNull(event.project), parameterNames) - if (!dialog.showAndGet()) { - return@executeReadAction null - } - val outputsCount = dialog.outputsCount - val routerName = dialog.routeParameterName - outputsCount to routerName - } ?: return - event.executeWriteActionInEditor { - val repository = event.repository - val node = event.getRequiredData(MPSCommonDataKeys.NODE) - val adapterTypeDeclaration = repository.adapter(node) - val adapterName = adapterTypeDeclaration.name - val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) - val switchGenerator = AdapterSwitchGenerator(repository.iec61499Factory, repository.stFactory) - switchGenerator.generateRouter( - adapterName, - model, - adapterTypeDeclaration, - outputsCount, - routerName - ) - } - } - - @Suppress("UnstableApiUsage") - private class GenerateAdapterRouterDialog( - project: Project, - private val parametersNames: Array, - ) : DialogWrapper(project) { - private lateinit var outputsCountField: Cell - private lateinit var _routeParameterName: Cell> - - val outputsCount: Int get() = outputsCountField.component.text.toInt() - val routeParameterName: String get() = _routeParameterName.component.item - - init { - init() - title = "Generate Adapter Router" - } - - override fun createCenterPanel(): JComponent = panel { - row("Outputs count:") { - outputsCountField = intTextField(1..20) - } - row("Output selector field:") { - _routeParameterName = comboBox(parametersNames) - } - } - - } - -} diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt new file mode 100644 index 000000000..db8fc8c07 --- /dev/null +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt @@ -0,0 +1,59 @@ +package org.fbme.ide.richediting.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAware +import jetbrains.mps.extapi.persistence.ModelFactoryService +import jetbrains.mps.ide.actions.MPSCommonDataKeys +import jetbrains.mps.ide.dialogs.project.creation.ModelCreateHelper +import jetbrains.mps.model.ModelDeleteHelper +import jetbrains.mps.project.AbstractModule +import jetbrains.mps.project.MPSProject +import org.fbme.extensions.adapter.AdapterRevealService +import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider +import org.jetbrains.mps.openapi.model.EditableSModel +import org.jetbrains.mps.openapi.model.SModel +import org.jetbrains.mps.openapi.model.SModelName + +class RevealAllExtendedAdaptersAction : AnAction(), DumbAware { + override fun update(event: AnActionEvent) = event.executeReadAction { + val model = event.getData(MPSCommonDataKeys.CONTEXT_MODEL) + event.presentation.isEnabledAndVisible = model != null + } + + override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { + val repository = event.repository + val factory = repository.iec61499Factory + val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) + val project = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) + val adapterRevealService = AdapterRevealService( + factory = factory, + stFactory = repository.stFactory, + owner = PlatformRepositoryProvider.getInstance(project), + ) + val modelCopy = copyModel(model, project, "${model.name}_extensions_revealed") + adapterRevealService.revealModel(modelCopy) + } +} + +internal fun copyModel( + source: SModel, + project: MPSProject, + newName: String, +): EditableSModel { + val newSModelName = SModelName(newName) + val existedModule = source.module.models.firstOrNull { it.name == newSModelName } + if (existedModule != null) { + ModelDeleteHelper(existedModule).delete() + } + val modelCopy = ModelCreateHelper( + project, + source.module as AbstractModule, + newSModelName, + source.modelRoot, + project.getComponent(ModelFactoryService::class.java).factoryTypes.first(), + ) + .setClone(source, false) + .createModel() + return modelCopy +} diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt index 3e747ea2b..6277a854d 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt @@ -3,16 +3,11 @@ package org.fbme.ide.richediting.actions import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAware -import jetbrains.mps.extapi.persistence.ModelFactoryService import jetbrains.mps.ide.actions.MPSCommonDataKeys -import jetbrains.mps.ide.dialogs.project.creation.ModelCreateHelper -import jetbrains.mps.model.ModelDeleteHelper -import jetbrains.mps.project.AbstractModule import jetbrains.mps.project.MPSProject import org.fbme.extensions.adapter.AdapterRevealService import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration -import org.jetbrains.mps.openapi.model.SModelName class RevealExtendedAdapterAction : AnAction(), DumbAware { override fun update(event: AnActionEvent) = event.executeReadAction { @@ -39,20 +34,7 @@ class RevealExtendedAdapterAction : AnAction(), DumbAware { stFactory = repository.stFactory, owner = PlatformRepositoryProvider.getInstance(project), ) - val newSModelName = SModelName("${model.name}_extensions_revealed") - val existedModule = model.module.models.firstOrNull { it.name == newSModelName } - if (existedModule != null) { - ModelDeleteHelper(existedModule).delete() - } - val modelCopy = ModelCreateHelper( - project, - model.module as AbstractModule, - newSModelName, - model.modelRoot, - project.getComponent(ModelFactoryService::class.java).factoryTypes.first(), - ) - .setClone(model, false) - .createModel() + val modelCopy = copyModel(model, project, "${model.name}_extensions_revealed") val nodeCopy = modelCopy.rootNodes.first { it.name == extendedAdapter.name } val extendedAdapterCopy = repository.adapter(nodeCopy) adapterRevealService.revealAdapter(extendedAdapterCopy, modelCopy) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt index 16d101694..a91b28bab 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt @@ -5,35 +5,30 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAware import jetbrains.mps.ide.actions.MPSCommonDataKeys import jetbrains.mps.project.MPSProject +import org.fbme.extensions.adapter.AdapterRevealApi import org.fbme.extensions.adapter.AdapterRevealService import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider import org.fbme.lib.iec61499.declarations.SystemDeclaration class SyncSystemResources : AnAction(), DumbAware { override fun update(event: AnActionEvent) = event.executeReadAction { - val repository = event.repository - val node = event.getData(MPSCommonDataKeys.NODE) - val applicationDeclaration = node?.let { - repository.adapterOrNull(node) - } - event.presentation.isEnabledAndVisible = applicationDeclaration != null + val systemDeclaration = event.element() + event.presentation.isEnabledAndVisible = systemDeclaration != null } override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { val repository = event.repository val factory = repository.iec61499Factory - val node = event.getData(MPSCommonDataKeys.NODE) val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) val project: MPSProject = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) - val applicationDeclaration = node?.let { - repository.adapterOrNull(node) - } ?: return@executeWriteActionInEditor - val adapterRevealService = AdapterRevealService( + val systemDeclaration = event.element() + ?: return@executeWriteActionInEditor + val adapterRevealApi: AdapterRevealApi = AdapterRevealService( factory = factory, stFactory = repository.stFactory, owner = PlatformRepositoryProvider.getInstance(project) ) - adapterRevealService.syncApplicationResources(applicationDeclaration, model) + adapterRevealApi.syncApplicationResources(systemDeclaration, model) } } \ No newline at end of file diff --git a/code/richediting/src/main/resources/META-INF/plugin.xml b/code/richediting/src/main/resources/META-INF/plugin.xml index b37bb6d4e..3b8dfad91 100644 --- a/code/richediting/src/main/resources/META-INF/plugin.xml +++ b/code/richediting/src/main/resources/META-INF/plugin.xml @@ -59,15 +59,15 @@ - + text="Convert Extended Adapter to Standard Elements"/> + + text="Synchronize System Resources"/> diff --git a/docs/images/extended-adapter/ea-usage.png b/docs/images/extended-adapter/ea-usage.png new file mode 100644 index 0000000000000000000000000000000000000000..84403deebdf80902715d6096560672f631021c16 GIT binary patch literal 75616 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 z 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 ZV0F4BD1;JJTmb4yIy1Y?cy6jc`k)R1&Qh} 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�BL3URVZiSFE#^GM_O2H0W!JVFDCFne#<+G z=F6($1<)E+b5f7QGI0^b3!Q*M2!fqV4>|ngA~rKI`5a(d3h?%Jha;T*Xv43yZhW+s5TB8j^0u9h`7TeJQ2~*y1QLmPa$OENgl11JrQqH6vo>$JK9ey?C zXHudK`wFV4yzMYh22$EAk{v%R7lKp%j?4q?2}T5B1s~SgP+#wfOd(_g=_`sXbHAS8 zKP~m1VRlnX9{PhjYTr7Zjm%#thVlo?D}Z(wV5gOqkoOsLT6K1yS80~5zis{32x5{E zM%ZicuWb}$YvDJco*&O5QOdKVy>(9hsyP|`fo$L|VR#;20E%JvM2P5S-$SXM_Rxh0 zagBb8O#2AY^L#SG3ro3i2Fu6b|2nX&6rpJ$5|KKndWFSlq@`n7DUlGRSu6~Y!^x?d z9#(~$HQFI67PLvG2!jjN#4vizj5<;bnF|z{IsE*w)3Ii`d z{~*)i5;+}gE`(XyhV>!jpd;7RBdAOoi{D3D3~ag$8DAp8^PAkO&1<@P`T)IYGUoF! zv}qJR45D0QkmkoK31cR~`1lDUz#=)CA$XSf1FI@Ea(KTFfogGBMYJ`Z>4x_@&_!Od zke9#*0~(xm3+b~U7#+4rkPD&3sr6Y<_a4c&0Pq1qjH_>1f!OIN47ezL)~u8u{(=Kg zT>ZCiCIvomP|r2}m2Xy1$|eP#_1M^3*aKRG=q7!esLQn54vm~w=*)rsqKAUJeLgIC zQo{f~fdN1B)HoOw#)P~u_$DF+BaoS#&$95O+PhvqIWEFOdn*`NCDst!3ABgZ{rgyi zp|+W}$@6|VxPQ-850CaV#DTCTpnG1TN}~1Ag~~jZGGd=snt(HvfJQS z%8&GrN5WTEv?O%K>xR=FhS!e+3eFSGW~foStIXTco;~WesMtuU#gDBhaTV(!;;yd! z>q{tis9q?;O`VQk*Bu^$=5qeBk&u-(eGm))j1b)l5 z9^_OonxFU*TVTB37@$Fj2*CydAQ3 zMfn0=(#z$5-58Xy#lOb4zD0Hfb=hMMAtVWnkw?@PhGG=6L>v=_yAusTXef!E5I{;) z7KR*8=o~*NNu~T5w<{3rS zPg&oK^I2xV8liQ+spqmC;WvWvMeB*%=Vu+sxPXACGK&vKG4YHe7#pH!5+?_S*F9^( zsK?A3+-tIQOfA;QS0cTJp*iAA_MC4aA`Tfl((|L`#4-%iTH0YL-oz<2WdxJ|RA#Ksi@AKS8lfu}sIZWfrE$ zT6!vp|Hu5V0}~`Ch%||8IX$Tjc{_4_GWQ<>Dy)>r=nT^EWLfoL%0gu&nMJ(09ct?8 z>`HDL*@d)k;avC&>G#WgmZfaEuS(SEqlgfvs zQyMkO0{MPleFcMxndC`?No0&$hwP#warn|n<=`ej-`Ps_ZQk-+1`sb`~7ZGx^xvkeLA^vm3DbJUi$U+0@kWKEq+~Z|pf!%@S!Rf?QMt4hXV_1u((Tv56ahdV2X4=}y1|I{l zrklESOEqKH{&b6Tvn<2TnYQ&$O^(e{4Q2JMR*@@hORzQ^D{fIlQH}&8UsPBQnzJJC z=IE!YC#%P@?Fh{DEjBFxR+Y<319cU{*#xf>y~{*%hVA|Dj_JF?Ll`mE_>V&~L-D7Y z`yR%;u0C5&kh>W}-EQ@+J3O{dZM(A0*+(4%t%J`Mkrk0kMH@wn^W$v4npWS19r3l(+_UzePX5{WL+FF-!xl6h zv<@^0G!{$_Oa=TD>@_Ge=l~)MmK-AVQw?I*1iOLra?{xd@?xAPQB@vw-b`LnUTnWj z-+A>ynPf1%2^m23K9t&iMcp_mS`qASDDppL#d;ZbHSv;8cfnTLsC22F6Je{4Dj_um32jHpxY4Vpb#Z-n5mk@(c+;pO(bd=0mJSQWW z!Dt)Rx=L0Fra~v%gp>A1W8~a@vAMhezliN8^Pyf+VP&7$i$aD>Xn6#4!n7=%thFgq z$3%)||$hMt)EkMJkUh{LC({cVBVQp~%`aR+ZK(D%2k9_1XtKqJ!o}S z$QD%Qp8ZM>x5imuUP!Kdw_cBO#Tv^R<)|sCOsTxAgs!kyF=#fN|IUzPnZ!}?R{o%N zs=-j<*B#$-y+Xbzcb|d4o?`dWC|0{axsY3hz=p}TzZ$lB)6BPu-}3&(VAuWoVtQ4n zZTD1udva1&sa@Mz*V@|s^ioeNbV2IG>rr-nu4~cXUZqp_eZlw6x9GLk@7D)bH~G8Q zso>GKTsx3cT{vHkBflNyMCgSXXf_LS!D71bIUE9GcgG15lIVt|0{$Byc;8%9 z%NaBm-)(lvV}T0fL8Rd-2~REH4vl{ZD|dGoX?0pl&aFC;otPKO#xXA6THJfYK=elr zm;Z>j2~uIXynzXT;gi1Sbp;})A8(hiloZfcF?KRFwR5(xckv4;(*P#GI7n(agMeU?{c)gD%A{97{R@_=nl74hvfRe@ zw)BQ3_C}`k9<~mD^nmbsa07$3rY?qr9=0}i&fFe+#DA6G28RD6GY}L0Rm8=bk62Sq zkx<0m$&`?to{^rBm>-sqkdW8O#Ee^6RQ%uKz!)E~g^P;=Hv@yayF0x*3%$LQIRg_H z7Z(E~GXpa-9k2wQv!|Vlp$DCvGs(Y{{6~+dsk5<@rGty5y&d5ny@p2it}cAU#D5I^ z_xjg;ntE9NZOP90-_-(Ekl{}b0~0+X!+&)HMS1_Eaw}STnA&KGTG|5J3}}O&iIJ1{ zuk!!DoWCvpLsIi^Nj5g-e@gx%=l?6I=4|RDVs8sH>B9f_L-8slayThvjAX?=$0vU1+Wz20q3wmZA!(Kp)u2{<_$Kzkim#e|(_BODYRfWdZtz zQldht9-wEsP(J8lSpIi*M{3*&GnWM%RMe6hg%D-4FkyGZ zc$UP3o!^PSDGJM3yiWM49ea<86sRlNvw{hE{)y?E6>BpA1w`b;T!f1BlO{;sGdsyd04L4D_6KQd;Ey2=+kN z2KGJvS5FW0(-*MH+^01%!*BnU5(f%(LH!-=IYb89I;4kz8~EE+W$qQq zb^m|q(iGbE{#SMWiuxb|Yw2^5P%HR%bM(N%Zi4?E-Es!ja>qF(7}DtPwg5H_{{Ne% zVNzE_)a4PKYC5DR(j;|8kxn8_lIa*nX9IieznuFO_X82Tp?Ffd=CxBEW*M_u9UXmd_PF!;AcOvz3612c9vK=IwrFcBlpaANSCCseg+64*_u~!n{d(ASmQ_v6 zbj+*(0>)$Z|u8W5|2~FO#xj zUrvb1P2MkE%src_hC~5hzm(@))ZJeh6qa+(a>{$6FsMqe_2*PleWZ0>uyC>Tg|V3a z=*ORrl?w~fJ*|}iC+}r053}(%&tSc{PtSdx4e%F4wO;pD<@()SGFcrS*L`vwNYA_Q+zx)QiSgO-{Tpl&v z8-!Qpa%@v>w?C@#yL3iai(ZqFFN=R&ZszSxe6G`byZlW0prXb~3Gkt&Yj{to5QUz| z(lQwb@bFK8_@<7<&zYG(8nK?zEYmjtHWx=HVsW&PSAKX!w4KPRq^uSbJsuzjZZXh| zqU4rEZArbdYRP~xp~EVzXV2sTE?}QU6MM=9u~4lmbJ-2(lEJGF-1$;G#s3=R_jWCD zos=D8x7l@`nO24`)H4SweYufjF`mjT-K9b?G`&@O zM-}(B^<&LUKLxw9S}!~#2A%uLT4UXWxLS;T8aZ70bOajBp5?wwPVD48FV<IDy4<-6;9cKUCZ3I{ZIJRgc>(P0JYUK!0cC@_k-Z4n~t;2nlgKfS5ki?jF z_sjBZ@t7}C#=|j5``sTek@aR1{ZmO+L^2_6CfnWrf1prtaQYmJg=6-*` zyFEtxb>7p_XsnSBOxkC;Yt8*p0eXR~ZeKlh4*Ip!A^1shsKDi6GVIIp!SY zV%0Q@+t!K3w;x66Y+%Dd5l=6 zGDXE*sQ=ybY3m(FY&!CO_W+dey2qF}k+ z=Gthp%oi46MLCkst{3U^*#`uqBh9yk3<&R1LJ4kj`}NKD7_E_W|cO^}DGViPe{ z+?-MB*0n;OUCvUDs<$M`b^~1RoHvj1>P?n~7A1RX0B#$_j#Wpfyo>|o@|+5NYuug@1Yf`!+js5C1aVKJpD!PM6?FZM^UG(+ z6=Ygo*JD(~|+Ec1>Gr7q=5I4h~cy#JbV4AEK1K zZOUcyrg6Jp*px5Al_=yS$8>RWOM58gcard0&zFmRZL!19A5R)gaSpDxJp&(=>GF9t znXl>ou)0}%-ut0oF`3CF?pE?_x86o&Iy~|j2Zy>Ce57OkmA+N4(20tl%wXqfFX56F2fAHWo?gX`Rnkg~ z>Zsn+$U&{ZI+)j}T)pj#pKBWdXplf5=ODP&uQJpSoQ9TLORzApFV3j3&`lQ+Y zeX&_1um+rC`kIW>0GW3tY%>`bx8{4Dh|GWso91}%jlnbw%)Fx-Me^4zAC^z|^#;dO#`r7${d)Cf>0{dlw-)}_6bm^7IF3a9L1(0?q+-9U zZJ(OkL=IiOPD+h6gY-`@#sb(k!Kh@iq+?H)54)?0h9;lXvKJh1Tq#$Dk_|gkZ@h!G zurOBX?MO7y@9uJL07RO8QO8+tmx}KkHn1V_m_ObYdAf=Lwof@$Ryu4DGCgMuU3XC&oW-RxD|tOUplLDb=t|Z zRhDh0sAN4sz)5B6#&hh|6Ded<1#|pAS_^l{_zikMOkIOa!6Lwlb>ptsa)^S?(}KC( z(a{?@ic_C*D>dr^bHj)YM@tR+zn9C%rmX8;!xQh&y- z=(>H_npM!J8E}inAXxGqJ}(e~WsCXN+A{#yo+3y;+By9#YTr;99nOCk%SG;WsFo#n z*U&PVeq=NsmXDRDI3Ow_)Uk$+cVpZAb3I0)<_LL7=sF^$SP!pactJ+Hf(Z>#wzNrm zR{CQgG3I-{AD)9OGiNxa3&#lQRPZ{QEN?sgIrGeFjfH(aLAT?Y=p(xy+M}%M4Y1#A zcH1Rfw!n2$aWmGA`qMt8hR1jdRzfTJuKBsFiee%Dia4MoNhFqgQnmbPZ%&Y21v8dG z((Lh-!|To>b^FUlwLXV_#u9{3$5j%1pAmBF~J z_-zsS`E=92VUGXdvb)?vr3i=WBIi)M%|(B#Es;vXVtae4F^R^}U;OUyQs9@RKcTq! z8zs*zVbCgU#C6EQW0HPie$q!*qZjDj3wz4LS?hD`ZN+AnvP*}7F#h+{7h1NW#QcKy zxkClHJA#Mz+#?Hy;#}D~pUr(07nHMcQ4zz9ZO@392M8jsDidwCNZ{3-_S zvQiwh8?gs8KcRF7UiN6ZOoJv~R~J6hPCOs07h^m`j92C3Ag&a|iL(+hL@p3`axyVZKqdiu27qSQt3n#s=<+&y{~k!9Z`xGNF0X z6iWMD=LL$G7EkuAFiH?^Si8_~{&gm-MIv?S)$UBDpd~ih71{%nol`cAzit!)#U@0V z_&8=oVTDJEd*UQ(1J^;}Szi;+OQMS<;YIzlqG7ba(FY=>|0xx=o;*{g+^`I~!Yb}X z^;l$c;eBEBV1~IFQ<9&wHGLzj^0wHXy$IzpiVy(*L7tp}WIuY0*Xd-ooA$ffg{PA; zwks+}?L!zF?w1s&*EuC8}Xj9FwPaghUDfjq4Z4@qTG?9W+vj8{85Pqre=L zNFaVbMo0Q$l*5lF>HVmeC!Iu`twN`bj`65wxjl!z4Q(4udJ0{06m?nZTRGoa;IUId zI2eb1V%>1cpqu69e!jsr7+K@IeP%JNZ!ga`2yPwDq_z6BB(&$$W(9JNPdrj&>srwG zk=g!B_ugKDn@guKr&jt4Ea&IkA4cb4$2bLVwR(nY2XGk~Cqx+qjT9EK;R+oi=Sx1= z0u^uKv|sk| zD##7Xa^`T`(5Q=2$qvG8#0;-uqU-v-d-3paCu~{>g~Rx{1q@uny3-!`^l#1? zJ)o=@9oc9nMGh8O;{5+lP?x&r?-Dm)0lWzn)2HIuJWB8aCA_~pf#WSOO@9YI#? z#l7*)i7~0F$?&IZab8Y-I5dY>VLiC{RZtL`t2*mTw=Swa3*)&a&GMn%X`7oU`Hq|2X=rFLEjy&cEq$f5)U0kCA#RF>J}!D3MaIG^k+8bIfn zJ#htqt!m|3u_T8zv5oR7pydgxKr6chfP#sc|3(Ln*Ss&ih|)9qL#aSK96AMxQZAkK zcZa8QtIeDRlS5}qs^d#OtyP+?)`AeR4sH{tY&yj?WWyaBvFd+I}&Q~jIE2<-V6jy_?H-0X)a!E6Z z;|fRric{beHpkxG$C|sOpjRSV#A0Jef!1FACh5KRqZd9zMl>a<+uQI_w!XmOdQxm2 z|FHY$h?pn)NWBLJ_fczUuJ(xC)DMXmc@aI)YQ9_wGT%gMcy6SxFBd&ZtwMd7h-@cD zvh7Nxt{_iX)K%>q36ELeuoA(jK0@M*lOaG^{&`hn)T)Ne#up-Bu!GN3s z3{o0}N>!IDO^vu7=G>NH6dAkgguyYx`Ea%LF#Y_aa450`d_G?jRKt}IOW-FAp!Z^b zE`5Z7kPNLUT1%^ONl>AiNv3J;qW8u8OKP4WOvQ(D@fzppBDNfafr>GhjPZ(-Uu3=z z{^^bN72-N^ay?A?$LX~-62jO|u{U(v>zP3ub;D!U%I11<`(2Km78L0&2ynDibjfXy zDnqg)dI6t;O6k#|23!n0pHKk54WC%bSRa)}1$SrpiGq!QLCIC?8a7K?w4jcPqSxa^ z6*{y;MOLs%Wqv=7ksR@OSH7`SJ=-xXL?vtO5T}g3A62#~;0ZDOnA^NEqBxczf6c15 z_krJ@*rkKqsAVK4%%fuE7+YUiv6l3!tQ?05=KujDnKnP=JNrX{(yGKSuIkc{Q=KjrxiWh=t4|+yBy$zh zdof2xxW-4nJ_p0QT1phU;@qp)hwj#{5RW@We;cB1)sJI3sS3BCG`R3Ff6Y?m(l8aN z;<8Sfmn9QMUsK_e*Qzkq8G=n({N7zmy!KG~J7}m|K<^&wgEsR`{0SvSQXPQ%8}FxA zz^EKuXeHIQ&tRu*`PX$Xws{)WDOK}m92;UB*t(F-c!N5*UXxXJNKM}$5PxV|ik()) zkpg70!KF6iG7XHcf`Z880RFVLs^;gaU_|5!*@QuxBp5I-;`Az}2`lEv+7`5uB?-X} zbSa;VE0eje%M9|mh6(0%U~x9-f>Sx z7DbE?hz~Xm<#szORY%T4WbQE^N<|jJydg0pLwcy|MZsmeSZz_b5DB(ug?9U-RizZN zJ~%}g+3s0c+m{SBq4fUG57Nb><@udSzT%uB@>&oPmm%Cih8+)pMOAe7e9y! z-D!w^n@1xKi?BrKq)dX^by5~X#y%ryxSy*^r2IxFTq!RR_Lv4mqR$R{IHnbCJ~Lc2 z&Op%F=_guxr(^7tmcMYq`h~Bvf$0Hfl#{vur!StQ(ow^b62|>yohaCegB(Rs)^60I zlI5U|Ya=3=rZCXBa`6^8+-P>1G#cvSOGpHl5U5+Hu8VZemCKV>3f4<~PsFAytdYxe z{2klCeTvV!<%=CNJ89yTWKp3JNk?$fj+v*G+I4;b>0Wm;0OvNI%EZj;XHvga6oUXE zHjW-js|YLVz!b`=Dl8R5{0#v*i;R*By50Zz#x&Gsq0`5OTr}c+D+uX~-_C_QW+@C= zer{n8=B?6qsFByH5Snj#6VnUiJO8+7cZ-L`{o;B@ z`+K?FPJLit#@K#vu38%mUpdM6@lbGB_7M95q0tuItuOZAoQI#WvGe)Xd{D%9zk!RU zmDDU_R>1W~x!wTTj3_)t`X#EWquhfSmExTb1Q@dBYF}Y|Z8r`{urCi6)=CB*mBiAG~6E${urUk^(suSac-lkz@)2Qgu#&9;AjV`2hwX{ zEi}u|tlFFpV{Ay!F;w)A?03a)8`hReppR>amNJ)QhJPc@ks1l$|Glvnkn3T@KuRT0 zTTSNljkwWRSazHM@+CMEaT6qoT}S!$%SMn>v9&xny&7PSI=T zOnok8(l{KpjX48xcX@o!3xtw!rj<=ou9(sZjNvp!-cb^7&of2SGEV(65Ey&t9LUIs zg^h0K+9@G}&Y-wwWD419`|LBO=+fwht?X{ogUaVe$&KHO?l+&Yi{(`9- z$qzr(dQFao$cUWdKTMDxNb-Kh5bA@MDye#(d zF(9qGAVQ{t9PG?nv!*+ErJ`i*J8g9hUtn!H)_LUKTlfb^Yk(erfrON?;rZl7YLguL zyHFCXwd1$Bl4Z` ziu(`T`@b&rMgRf=01S(Xjsjup*dN$>4n(aOg9`$Q)5b??UJ`|m8>$dRP$*C6aXTUr z&@cFE5$G6+#l&RL(6RD@qTI76peS*~e7~nf{$i~+ZWk9DogA$kFpr<6uG*KW%+03W z+?V;h4>DP#SmVRc{^q5OV1B`ZsF;dxM^=n9e>V*wiUDYdJ;dKNzT$z<6ufSi>4X0V z-h}&Kytzr#0~WEVi)!{yyearUc=HVr6lm)rfl=gdTRmX^i#Oe&YXf%NQ-ZPofj2S# z7jJfp1VEy9x08_BmX zh}Y@%JGtTPxPC5Dh(Q_kIu?i`6$H0{lK_{qC8n3x%Wf)0#<&P{D&fIMtP&vKdqXRp za+VE-4h|7h^myvjbkr+V{4^Rz)RgagvquG_m8Q%WeHwYfh|&oI*W zMbh#N-I;3l0$z7G>#etnNya~ZCDFt=9l%E+l!cSagN?>lNR&Qz9JrjVFwNysZjo^? zc;L^Ef1N9%*TW&a>*U)#h z+J$8S%gf7a^nSFH&f;c6z~^0B8!3Vi%JsTOUa8yFsT|V#$(R}#BtyBv=S{CX7%``c zo!05yuH5RxrMiBzKU}KS@LaF+(?Oi5<5AUCNXYo;;ha(}M(&_t>SU(pTlN^39vr0r z-g|f;7KF-gWOTZ1s?kYI#^P<>kMw*#M^D^u z_lx>vZs#iz-&rQ1I{+WAckhpzku`VlF2Kz?bFk`gcZi*@g2$^Z)X{jS$U_>HoZ`Tl z3>>9cgb6Cn5WW?nA4Be!Hm5MX?7MoS&uKagd-;LbaI?uQUuEyBWSWa1?dcfj2hH;a zJ1`s1Mwq!wzC}SEndJPq=JE-Q-G}?g7`GKhW{g)RSCH}yycu~*^46dvW)?oU7nm;A zd3pjXLUbsFzGwI6d;t^iPvy8p=71pd_+9l?15vq3o9z$B*&m4)DeYv_?(||~v){@wIREjPPlP6mc!c_8|8a}yKUthMJcQD+maeuw!D z70k*9C6LMBwE46Wkjde6j{hiyOhoc~3FumXuL3hLE{Rs9(8p{2?)rRTO8ulJ)CM@} zvI*rIo!NMJK(q?rG`Wc#Q!uy7iYICGDI=r9#G+>+)4$4bx6y-*g#N+kO1l^@oE|#z zwp$Ww`Tpzv_hU+z%hQP= zOVzD3C3iN~=L^*;>+DfvmHr6t$DUkH%1XzYIFM+j5aS_~vq;YK`P{^PxA~kudYR-O z%Utq{R)0KCLiqUigo^6%p!A(y>tm_vgK~uY)Kj{D7Lt7|623q#YyWKNg?{G;#(mj> zL{hw#vk?MY=Dgy3m6th2r{0sR+5!s)!CGO~q1)(YbHnOU*+ZhVDGgZ&_8OLW0ZnJD zz&4caO!aNM#bT>&WPI`<@{LbA^IFH5&GR zqE@f`jK}3Pa18JNevg$zi{07SYBrJX(IVFB_s;EmJHc_&0i@Wu60!JF6BEErwuh>+ zKblmg+wL}Nuo%uIG6-avmd`i)ecugg7_E3Efq1@Twn%2qx~7vH9(O|9PJ|OwYj{@F+!jhe0nGx6cz}k!)JRC-~2~ zC-<^N(n+O;BEz?$HN#BI%n5$4=Nv2brjZ@^T+RoiIvLbz<%;2`i0v@O1be%10;fMW z5_5Pxh2gO13$+@|l(zdq%7D1;WaHY5*ZyT7G+?&VTg*{`J)y*%B6sQf@YnPCOMCgP zreIxHhe3hj^WbRlb;g9X_*g7$iYtSuZ#d#TZ{f=Jxynn%Y|+E(lD(?dO%_YYUK zP+7rxke9M|g*)xMMcMnVgUx{`EXdESSQ%T$(C5Lit`v(i6#|?KBplr>%g06GST4I< z(OJpCF3AR8@K)#(nTsb&HGUOzZ?(L>*;#S66A6vf%QW527Z=&4a9<;>ESh)ABa6qi zshx>mMWnkMWYgjT1UWZnT%k;*G6pqmM@7(Jq6@QUOQaPzKCL-WeK~fS+P&WB^u8f` zq)k>CO{SB1eY`9YkH(pq5&)b*n0t6|noecwE==VJ}~gns%#p->Fm$jLal2kVI0@VS*c_AOk*js-%~ZoKUKllcPRaxa|Wz-niV zOM8j^A>=CGIywN!VRo+lvWo^G?A}Rs7`W3xZ8$86i-JOwjBhoaCld1*RObtXb@b78 zyWj%5tvd6NhKFK0w1QuCYyFGIk1Itsa%d1_h`8LDC;L-C`GHs{rqU^7mD;mScf;r> z;TrfaD3b038%cwDYTh%!;%FxLdLm@xZOP^LA4A5sI}`hn;x(`PPYk~;ns)`hPWEKF z0QZ18mtANRTEld*ncLQ0(*4nb^w8Duze?GEJQbgK=1;*`@)T+a(x};5v6Z0+;^8Nt zfRj)%n&!$26TJ$TI$M~x>eW!&GP|v?`S;jgtSWe zr3Uj@bXo(XAP9t5`tcFiYp8T6pQ_W;Bx*HjEBxH&0KN$zDT^CD??PXQU5`SbauOFm`|QQi4yfQs2}O3&FnFC~0^swdb^t?_H&535&= z-|nvZXmYQ!=Vg}H#WximlOJ5l^EGIhFGuM6Q*rtX(y>JPH#MKHnQxyLrj7-y0R>*T zo!PlCsmC-oEGb@ZPfkQ;6sg)4k7^EIA9q%`*s@(>@+Kw)eLLf7%(|+z( zx>7;B2{knWMh_;yP4H{z^Yz4Hl|8mDhgUGjDI{_WjXM|juq}ZeLx=BVlP)CV+Pq7L zf7QR}_&Mpyp#f`e;VN z#cPgGSHxx_QeyPZAC?TIpyr`=r#_6E!@FK1E{Y$}78O!gd?&iYu^C{`v6?h)Ww<%Z z7VMOGp3!8uaYlPY0gyg8xgqPYELo;~F)-zLE~`^^98Kni}tSC|B70HJ+IX)BIM0vHRHUTuzm?`685Nl)dPC5X^1`^crOpEr|N zDt#MR^SBy|y!B|r;89CqZ2bE?*m{d~i)FDr<@1E<_(C;zlz3qfBIx9Fs6!Tnq6JJw zkXQ4{)vKs2gWp3{+OL=sZ2Nv{pt(`XpZCc~MR(h-vX{7R_mZxL5bpl|4ok#Y{$Uh8 zoGf00E+dto{uBFDjR{}iX$yQ=VkvEDHo}y^)o-tgs`^Q5I8sInS~VTHxDgSL+u~<( zUnw_zzvn!M<*XrCP2LTM!`7!3hs6pM0XgjXe6eMksQwnKcv$SLb+Dn9kMtwSvQaRpw-7X*mLr0n5w>`!3p;ltuS3&pAO5hb`4C8 z3P9*sqp~xTe*n53TEb11w@r)PDnJl-;7M30akfoOPNpDP!1}s`d7%gSEN&aTnbN$y z^=~LZ#VpWlD2Q1}VX+F3;dj!ZWuh${iK-_@|wK2C#QHJJ-vPM`SMgnRLb@dGy6r zfg|2KHn2lGd<(@wJhR)93nEGObU|X39r1Wq!f*0KN6-;K)=G2mCr*xHVoO~P*s_a- z0C(CN_=W%Nvg>uS2!%44+Q1Sd}E1iqLudKp%{msTJ zjg~`p%)k|aFYwr$m!;Eep%jDyk@3wvYV4!{uWQt&pWLy~$tB(>%h&cgJJ+8C-g&3S zW>(4YKM(MGv8KA6b1SG-hzpdgG*VGrR5Z$l%p6e z6=AWmKa+6(=bf`uG`q-<%1Y+yN@W1NGMmaAPL~S8mcRL$nwmUWv|B6E%~@@y z!(hqr;a#|67Vznk}Oz@a))$B8EL3yX{#~JPFC}jNncQx+oUXe zxzWt0UxYE$J?eXBJ)~cof1DreNv#*pWt8ek^3CtZUid4feK8%u!S>K@u>9THv3!D` z<@dA;b@H>Z;Dq2K$6+*m;mg)&7y*3ZqMK&Lk+1K2^jp7RyLnP}1V=kOG09iU2nAaC zKVbCxe4eeyd7D!f6m-w6$=%Tmao;|EtCs%hLRHafv6AstuR|TN96S!XJK78za=30# z;AbkvE7ugk0&mr0m%5qUqeeh*0R;|xK}KD*5UBY3OBDtvth@A2ug7XxG;&evOVsF3 zkV8I040795hH8(#R{S)D6ILJn!dgYSbxZkdni3UwAjm~u6sGi#01{k*143dSSi%l z@^~3_e~5pa!#CPe*p>!&Cd|Zr-gT7Dgg(MSM?oKV1H+>4iDT5x_%SVBzhMDl@CEK8 z*t?ky&^MuZ&0v(rITJ>PzpR%h3|Ydi`bEG`=UxGtL@NuR*XgyKEl@1I00g2pqCtk{ zL4h+5XO5qz4o2DJbpUxchcXBe65{nkP`trGw4Dg+e)px;J3KAI>NPIUKY7T+>buk#J= z6a$G$n6&EZVC{38^!zJAW4rbJxACj189HVvVZd6#=xO|0NbJ96G_i0@m45tG!2F`K zCAw<~8CC0;BgP+b*GMxr3<`?Er`8o9wx_(RG;q;z0@>+$s{R$YY#6$S{92(-_lp36 zuAP(>J_3M2qs|#0_LHPucd3lC3CKd1)!rY^Sp-U|@H(z{P(Tm%o{U;7P95r~hcUV} zvj)J4QD8JzTb)bXfWQaIYmmUw2b$6&^k9TZT_?q&S5$x;2h?;)e0!7>fBdCpSb?t) zw>&JGsM9`>A5PhJzl-iL_~Z9xNx56pwSOc)0ha42Aygc~uIgp!3ezzqcYWH@F|*6O z?Gu*byE(<0AdA5nc*@rflWQkUN0Sipotn~zJ&^=!BLN~bxPLF)Py*y8rcQ{vu78Wq ze~PNSSmeyQe|h|9Z6ObgvLyD4XujGKp4hAbjOa_0bCF|bin45w^nqvxQKb6$7viUD zH^5Sz%2%qK=dSu&c4KJ}wM8lntS+B5}65c=K_(N1=QJ9v>*fVhl#HMx9Ys zdj%e5tekM9AHIiNZL!xM%*kr`!5E=-KK)(u2XsTt%65W{TpI`|9YQ11#VkcmnE5rJ zQO>A!60gi^Zt4#YbvSM&N8#2(tNXgAFwkGeZtXZYW!b~2+C#>}$f#(zHR{TTSmJPk zgMw0~`_tj;dlcB{>t&7VeQvA&=2_W^bWq@ryUT99@qT{@^jI6k2c0Dmj1pD!N275| z)7PZmKP`;W@}9H{1NE_3=a(^RA3CaT*~^oMxv2Bx1-!;AaBA%W^jx@!VbAI|hGp-M zC5qi~@8=|WJG!77bj)k(B7FW#+mPU{6g>?G;HZV|FUvbEeG0Cv5=)1b&{>%!&+AN@ z2*C0xPGe8WE^OH_nLj+e)*UnGG4 z!r1#iP1}1s(`y_Da5R}5Y|NGzGxvjBk`7HSX$YfGBzI9{kz_6h)5RnSui{J-tCrhQ zqFEwLrsSI2%56gAmMP4Ra(@1(`SNgBG%^>jCDNbn_)aa$$(n9>4s^^fGw~w$NN!6Y}?@8_Uj<%CV?ZB8yH(P3w_P$n3+Ac6~W+B)PzYHOACt6+aANu?B zYD)fI5K+|GxRUSRuY)Fugp#Ex7bkU=TyUB)ma{T7M>{O3$s48rMJN>XQjgg!_12-=*1l#47phmUyzm z1D~TO(u?DuCy&S)dQ`VMx3d~p#DtcHEU^pANj`kn4q2Yx&jI7j#st|6_EOJLs`|j( z6KBw%G^>*}h2ao=dtW&1A4k%1%#fA3wB7;flCOP>_M(g44DCRSuI7sG&{)o*qk=OS z)mF-$#+FRCxOz4}+i{nGAFY#j4vAFKcy(9nDaO4r;E0~*v!GBZYgMX>kdLFoKF(1B z?p(lNa6jT3;-yTOwN7D^yD44VqbQTD{Kgp00+wo&cm6hZNs>*UU;CKR5EaD04oeeS-v;qBaRZ1C#ugddsX+wRf*fSRDHW6D% zsVAK-D`oPp9a}7%(U(wxy&Xx9diZYqdw;nZJE%Z?)8z45tb0K1&&z(|S3Gt#AaLFM zChtS2bIC~0l>W5M;!7r0$WeD_cLgM;VZl2f= zWl!NyR_DV}+WSH} zHymR`Zi&WMqMHhAzW}Sy?m0yrY=J5|69n`3;3+y$9y7seI8Zf|=P@y<1Dbc^8*&3i z+V^V2;X{B@pMR)Ow8MF#pRhXJ&Jd}3I|3lF>R?9q0D9ZsrBu5>J=kT^~ZctaHgYdB2VZf`YP9tTMOASz9>NY^j)T-jDEK^lf3{SjFgZ*ur1Pu z_zAZlioco4|1L^4>WSzUGOnd6Y>N5eFd%_wc0cEr*7|{l(ZH>|2#hR2 zlt2Uqyt1Tpz5V691|oRCMqBhS!*2x9D@$O2Z(-;3wl}H)Z&-YsIT3dXTCTa(uC+gc Kv#GH5OZgwTclM|N literal 0 HcmV?d00001 diff --git a/docs/pages/extended-adapter.md b/docs/pages/extended-adapter.md new file mode 100644 index 000000000..d21fb0c54 --- /dev/null +++ b/docs/pages/extended-adapter.md @@ -0,0 +1,47 @@ + +# Extended adapter + +Adapter extension allows to implement dynamic connections between components and +add additional internal logic(internal fb networks) to the adapter declaration. + +A new extended adapter type can be created like other elements, +and added to the socket and plug sections for other blocks, such as a common adapter. + +You can convert a model that uses extended adapters to standard elements by selecting the +"Convert Extended Adapters to Standard Elements" option from the context menu. +As a result a copy of the current model will be created with suffix name "_extensions_revealed". +Networks that use extended adapters will be modified, and new models on the connection path will be added. +The new generated models will be stored in a directory with the prefix "generated". + +Also, in the system module you can select the "Synchronize System Resources" action, which will reveal +all broken connections of the extended adapter type halfway in the resources and add publish/subscribe blocks. +If a broken connection has any connections in the resource it will not be modified. + +## Dynamic adapter + +Dynamic connection allows you to connect a plug with multiple sockets and choose at runtime which socket will receive message. +Sockets in dynamic connections are numbered. + +ea usage example + +To use dynamic adapter connections, data parameter "Input router" should be added with type INT in the adapter declaration. +Additionally, a parameter "Output router" should be defined to receive the number of socket that sent a message to plug. + +routers fields + +These parameters appear in the plug and can be used in a similar way to other parameters. + +plug example + +## Adapter with internal networks + +There are two fields that can be used to create an internal network in the adapter. +Left network executes on the plug side, right network on the socket side. +Both the left and right networks have editors with two specific blocks: +"Socket_Connection"(can be considered as a socket for an internal composite block, that will be added in every connection usage) +and "Plug_Connection". +These blocks are symmetrical at first, you can add additional parameters("Setting Interfaces") to the adapter declaration. +This allows you to pass certain parameters to the internal algorithm, but not to the other end of the connection. +The next image demonstrates which part of the connection each setting interface affects. + +Setting Interfaces \ No newline at end of file diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt index 4010e0b34..46c8a26a7 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt @@ -30,10 +30,10 @@ - + - + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 index e0a8de51e..002273287 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 @@ -1,5 +1,6 @@ + From 10e0d475bcbf19dc283c8558eead810e85c374e5 Mon Sep 17 00:00:00 2001 From: klinachev Date: Mon, 3 Jun 2024 19:53:27 +0300 Subject: [PATCH 12/14] Reuse nodes generated previously --- code/extensions/build.gradle.kts | 2 +- .../adapter/AdapterSwitchGenerator.kt | 10 +- .../adapter/ExtendedAdapterUtils.kt | 336 ++++++++++-------- .../org/fbme/extensions/utils/SModelUtils.kt | 67 ++++ 4 files changed, 270 insertions(+), 145 deletions(-) create mode 100644 code/extensions/src/main/kotlin/org/fbme/extensions/utils/SModelUtils.kt diff --git a/code/extensions/build.gradle.kts b/code/extensions/build.gradle.kts index 590dd2c7a..d96e6f512 100644 --- a/code/extensions/build.gradle.kts +++ b/code/extensions/build.gradle.kts @@ -17,7 +17,6 @@ dependencies { testImplementation(project(":code:library")) testImplementation(project(":code:language")) testImplementation(project(":code:platform")) - } mps { @@ -31,6 +30,7 @@ val compileKotlin by tasks.getting(KotlinCompile::class) { val test by tasks.getting(Test::class) { dependsOn( ":code:library:buildDistPlugin", + ":code:platform:buildDistPlugin", "buildDistPlugin" ) } diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt index a63080675..c477ae6c4 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterSwitchGenerator.kt @@ -1,7 +1,9 @@ package org.fbme.extensions.adapter import org.fbme.extensions.utils.IEC61499FactoryUtils +import org.fbme.extensions.utils.SModelUtils import org.fbme.extensions.utils.STFactoryUtils +import org.fbme.ide.iec61499.repository.PlatformRepository import org.fbme.lib.common.StringIdentifier import org.fbme.lib.iec61499.IEC61499Factory import org.fbme.lib.iec61499.declarations.* @@ -16,10 +18,12 @@ import org.jetbrains.mps.openapi.model.SModel class AdapterSwitchGenerator( private val factory: IEC61499Factory, + owner: PlatformRepository, stFactory: STFactory, ) { private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) private val stFactoryUtils: STFactoryUtils = STFactoryUtils(stFactory) + private val sModelUtils: SModelUtils = SModelUtils(owner) fun generateRouter( name: String, @@ -34,7 +38,7 @@ class AdapterSwitchGenerator( val routerDeclaration = factory.createCompositeFBTypeDeclaration( StringIdentifier("${name}_router") ) - model.addRootNodes(routerDeclaration, virtualPackage = virtualPackage) + sModelUtils.addDeclarationToModel(routerDeclaration, model, virtualPackage) val socket = factory.createSocketDeclaration(StringIdentifier("socket")) socket.typeReference.setTarget(source) @@ -77,7 +81,7 @@ class AdapterSwitchGenerator( ): FunctionBlockDeclaration { val switchFBIdentifier = StringIdentifier(adapterName) val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) - model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) + sModelUtils.addDeclarationToModel(switchDeclaration, model, virtualPackage) val switchBlock = factoryUtils.addFunctionalBlock(switchDeclaration, network) val inputParameters = factoryUtils.copyParametersAndConnect( destination = switchDeclaration.inputParameters, @@ -157,7 +161,7 @@ class AdapterSwitchGenerator( ): FunctionBlockDeclaration { val switchFBIdentifier = StringIdentifier(adapterName) val switchDeclaration = factory.createBasicFBTypeDeclaration(switchFBIdentifier) - model.addRootNodes(switchDeclaration, virtualPackage = virtualPackage) + sModelUtils.addDeclarationToModel(switchDeclaration, model, virtualPackage) val switchBlock = factoryUtils.addFunctionalBlock(switchDeclaration, network) val outputParameters = factoryUtils.copyParametersAndConnect( destination = switchDeclaration.outputParameters, diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt index 2d87ea5c2..c3049b7c1 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/ExtendedAdapterUtils.kt @@ -1,11 +1,9 @@ package org.fbme.extensions.adapter -import jetbrains.mps.smodel.ModelImports -import jetbrains.mps.smodel.SNodeUtil import org.fbme.extensions.utils.FBInterfaceDeclarationUtils import org.fbme.extensions.utils.IEC61499FactoryUtils +import org.fbme.extensions.utils.SModelUtils import org.fbme.extensions.utils.STFactoryUtils -import org.fbme.ide.iec61499.repository.PlatformElement import org.fbme.ide.iec61499.repository.PlatformRepository import org.fbme.lib.common.Declaration import org.fbme.lib.common.Identifier @@ -21,13 +19,15 @@ import org.jetbrains.mps.openapi.model.SModel class ExtendedAdapterUtils( private val factory: IEC61499Factory, - private val stFactory: STFactory, - private val owner: PlatformRepository, + stFactory: STFactory, + owner: PlatformRepository, private val publishSubscribeProvider: ((name: String) -> FBTypeDeclaration)? = null, ) { + private val sModelUtils: SModelUtils = SModelUtils(owner) + private val fbInterfaceDeclarationUtils = FBInterfaceDeclarationUtils(factory) private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) private val stFactoryUtils: STFactoryUtils = STFactoryUtils(stFactory) - private val switchGenerator = AdapterSwitchGenerator(factory, stFactory) + private val switchGenerator = AdapterSwitchGenerator(factory, owner, stFactory) private val numberToEventFbTypes: MutableMap = mutableMapOf() private val eventToNumberFbTypes: MutableMap = mutableMapOf() private fun getPackageName(name: String) = "generated/$name" @@ -62,47 +62,68 @@ class ExtendedAdapterUtils( model: SModel, ): RevealDeclarationsResult { val name = extendedAdapter.name - val FBInterfaceDeclarationUtils = FBInterfaceDeclarationUtils(factory) val routerAdapter = if (extendedAdapter.outputRouter != null) { - val adapter = FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( - name = "RouterAdapter_$name", - fbTypeDescriptor = extendedAdapter.plugTypeDescriptor, - reversed = true, - ) - for (event in adapter.outputEvents) { - event.associations += factoryUtils.createAssociation(adapter.outputParameters.last()) - } - if (extendedAdapter.inputRouter != null) { - for (event in adapter.inputEvents) { - event.associations += factoryUtils.createAssociation(adapter.inputParameters.last()) + val routerName = "RouterAdapter_$name" + findDeclarationOrCreate( + name = routerName, + model = model, + virtualPackage = getPackageName(extendedAdapter.name), + ) { + val adapter = fbInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = routerName, + fbTypeDescriptor = extendedAdapter.plugTypeDescriptor, + reversed = true, + ) + // create associations with router parameters + for (event in adapter.outputEvents) { + event.associations += factoryUtils.createAssociation(adapter.outputParameters.last()) } + if (extendedAdapter.inputRouter != null) { + for (event in adapter.inputEvents) { + event.associations += factoryUtils.createAssociation(adapter.inputParameters.last()) + } + } + adapter } - adapter } else { null } val leftNetwork = extendedAdapter.leftNetwork - val leftAdapter = FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( - name = "Left_$name", - fbTypeDescriptor = if (leftNetwork != null) { - leftNetwork.getCustomNetworkComponents()[1].block.type - } else { - extendedAdapter.plugTypeDescriptor - }, - reversed = false, - ) + val leftAdapterName = "Left_$name" + val leftAdapter = findDeclarationOrCreate( + name = leftAdapterName, + model = model, + virtualPackage = getPackageName(extendedAdapter.name), + ) { + fbInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = leftAdapterName, + fbTypeDescriptor = if (leftNetwork != null) { + leftNetwork.getCustomNetworkComponents()[1].block.type + } else { + extendedAdapter.plugTypeDescriptor + }, + reversed = false, + ) + } val middleAdapter = if (leftNetwork == null || (extendedAdapter.internalFbSocketInterface?.isEmpty() != false && extendedAdapter.internalNetworksInterface?.isEmpty() != false) ) { leftAdapter } else { - FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( - name = "Middle_$name", - identifier = extendedAdapter.identifier, - fbTypeDescriptor = leftNetwork.getCustomNetworkComponents()[0].block.type, - reversed = true, - ) + val middleAdapterName = "Middle_$name" + findDeclarationOrCreate( + name = middleAdapterName, + model = model, + virtualPackage = getPackageName(extendedAdapter.name), + ) { + fbInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = middleAdapterName, + identifier = extendedAdapter.identifier, + fbTypeDescriptor = leftNetwork.getCustomNetworkComponents()[0].block.type, + reversed = true, + ) + } } val rightAdapter = if ( extendedAdapter.rightNetwork == null || @@ -111,43 +132,54 @@ class ExtendedAdapterUtils( ) { middleAdapter } else { - FBInterfaceDeclarationUtils.generateAdapterFromDescriptor( - name = "Right_$name", - fbTypeDescriptor = extendedAdapter.socketTypeDescriptor, - ) + val rightAdapterName = "Right_$name" + findDeclarationOrCreate( + name = rightAdapterName, + model = model, + virtualPackage = getPackageName(extendedAdapter.name), + ) { + fbInterfaceDeclarationUtils.generateAdapterFromDescriptor( + name = rightAdapterName, + fbTypeDescriptor = extendedAdapter.socketTypeDescriptor, + ) + } } - val leftBlock = leftNetwork?.let { - createCompositeFB( - name = "${extendedAdapter.name}_${it.name}", - leftAdapter = leftAdapter, - rightAdapter = middleAdapter, - network = it.network, - ) - } - val rightBlock = extendedAdapter.rightNetwork?.let { - createCompositeFB( - name = "${extendedAdapter.name}_${it.name}", - leftAdapter = middleAdapter, - rightAdapter = rightAdapter, - network = it.network, - ) - } - model.addRootNodes( - routerAdapter, - leftAdapter, - middleAdapter, - rightAdapter, - leftBlock, - rightBlock, - virtualPackage = getPackageName(extendedAdapter.name), - ) - if (leftBlock != null) { - updateInternalAdapterPorts(leftBlock) + val leftBlock = leftNetwork?.let { adapterNetworkDeclaration -> + val leftBlockName = "${extendedAdapter.name}_${adapterNetworkDeclaration.name}" + val existedBlock: CompositeFBTypeDeclaration? = sModelUtils.findOneDeclarationOrNull(leftBlockName, model) + if (existedBlock != null) { + existedBlock + } else { + val compositeFB = createCompositeFB( + name = leftBlockName, + leftAdapter = leftAdapter, + rightAdapter = middleAdapter, + network = adapterNetworkDeclaration.network, + ) + sModelUtils.addDeclarationToModel(compositeFB, model, getPackageName(extendedAdapter.name)) + updateInternalAdapterPorts(compositeFB) + compositeFB + } } - if (rightBlock != null) { - updateInternalAdapterPorts(rightBlock) + val rightBlock = extendedAdapter.rightNetwork?.let { adapterNetworkDeclaration -> + val rightBlockName = "${extendedAdapter.name}_${adapterNetworkDeclaration.name}" + val existedBlock: CompositeFBTypeDeclaration? = sModelUtils.findOneDeclarationOrNull(rightBlockName, model) + if (existedBlock != null) { + existedBlock + } else { + val compositeFB = createCompositeFB( + name = rightBlockName, + leftAdapter = middleAdapter, + rightAdapter = rightAdapter, + network = adapterNetworkDeclaration.network, + ) + sModelUtils.addDeclarationToModel(compositeFB, model, getPackageName(extendedAdapter.name)) + updateInternalAdapterPorts(compositeFB) + compositeFB + } } + return RevealDeclarationsResult( extendedAdapter = extendedAdapter, routerAdapter = routerAdapter, @@ -198,15 +230,15 @@ class ExtendedAdapterUtils( } .groupBy({ it.first }, { it.second }) for (socket in socketsToChange) { - val connectionToTargetPort = blockToConnections[socket.identifier] ?: continue + val connectionsToSourcePort = blockToConnections[socket.identifier] ?: continue val result = identifiersToRevealResult[socket.typeReference.getTarget()?.identifier] ?: continue - changePorts(connectionToTargetPort, socket) + changePorts(connectionsToSourcePort, socket) socket.typeReference.setTarget(result.getFarRightAdapter()) } for (plug in plugsToChange) { - val connectionToTargetPort = blockToConnections[plug.identifier] ?: continue + val connectionsToTargetPort = blockToConnections[plug.identifier] ?: continue val result = identifiersToRevealResult[plug.typeReference.getTarget()?.identifier] ?: continue - changePorts(connectionToTargetPort, plug) + changePorts(connectionsToTargetPort, plug) plug.typeReference.setTarget(result.getFarLeftAdapter()) } } @@ -227,10 +259,10 @@ class ExtendedAdapterUtils( } private fun changePorts( - connectionToTargetPort: List, + connectionInfos: List, block: FunctionBlockDeclarationBase, ) { - for (connectionInfo in connectionToTargetPort) { + for (connectionInfo in connectionInfos) { if (connectionInfo.source) { val ports = when (connectionInfo.connection.kind) { EntryKind.EVENT -> block.type.eventOutputPorts @@ -382,16 +414,18 @@ class ExtendedAdapterUtils( null } else { revealResult.routers.computeIfAbsent(connectionsCount) { - switchGenerator.generateRouter( - name = "${adapterType.name}_$connectionsCount", - model = model, - source = checkNotNull(revealResult.routerAdapter), - target = revealResult.leftAdapter, - outputsCount = connectionsCount, - outputRouterName = outputRouter.name, - inputRouterName = adapterType.inputRouter?.name, - virtualPackage = getPackageName(adapterType.name), - ) + val name = "${adapterType.name}_$connectionsCount" + sModelUtils.findOneDeclarationOrNull("${name}_router", model) + ?: switchGenerator.generateRouter( + name = name, + model = model, + source = checkNotNull(revealResult.routerAdapter), + target = revealResult.leftAdapter, + outputsCount = connectionsCount, + outputRouterName = outputRouter.name, + inputRouterName = adapterType.inputRouter?.name, + virtualPackage = getPackageName(adapterType.name), + ) } } @@ -423,19 +457,23 @@ class ExtendedAdapterUtils( ): FunctionBlockDeclaration { val adapterType = revealResult.extendedAdapter val leftPublishSubscribeAdapter = revealResult.leftPublishSubscribeAdapter ?: run { - val leftCompositeFBType = factory.createCompositeFBTypeDeclaration( - StringIdentifier("${adapterType.name}_LeftPublishSubscribeAdapter") - ) - val socket = factory.createSocketDeclaration(StringIdentifier("socket")) - socket.typeReference.setTarget(revealResult.middleAdapter) - leftCompositeFBType.sockets += socket - model.addRootNodes(leftCompositeFBType, virtualPackage = getPackageName(adapterType.name)) - createPublishSubscribeAdapter( - compositeFBType = leftCompositeFBType, - declaration = socket, - currentModel = model, - packageName = adapterType.name, - ) + val name = "${adapterType.name}_LeftPublishSubscribeAdapter" + val existedDeclaration = sModelUtils.findOneDeclarationOrNull(name, model) + if (existedDeclaration != null) { + existedDeclaration + } else { + val leftCompositeFBType = factory.createCompositeFBTypeDeclaration(StringIdentifier(name)) + val socket = factory.createSocketDeclaration(StringIdentifier("socket")) + socket.typeReference.setTarget(revealResult.middleAdapter) + leftCompositeFBType.sockets += socket + sModelUtils.addDeclarationToModel(leftCompositeFBType, model, getPackageName(adapterType.name)) + createPublishSubscribeAdapter( + compositeFBType = leftCompositeFBType, + declaration = socket, + currentModel = model, + packageName = adapterType.name, + ) + } } revealResult.leftPublishSubscribeAdapter = leftPublishSubscribeAdapter val leftPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( @@ -478,19 +516,23 @@ class ExtendedAdapterUtils( ): PortPath { val adapterType = revealResult.extendedAdapter val rightPublishSubscribeAdapter = revealResult.rightPublishSubscribeAdapter ?: run { - val rightCompositeFBType = factory.createCompositeFBTypeDeclaration( - StringIdentifier("${adapterType.name}_RightPublishSubscribeAdapter") - ) - val plug = factory.createPlugDeclaration(StringIdentifier("plug")) - plug.typeReference.setTarget(revealResult.middleAdapter) - rightCompositeFBType.plugs += plug - model.addRootNodes(rightCompositeFBType, virtualPackage = getPackageName(adapterType.name)) - createPublishSubscribeAdapter( - compositeFBType = rightCompositeFBType, - declaration = plug, - currentModel = model, - packageName = adapterType.name - ) + val name = "${adapterType.name}_RightPublishSubscribeAdapter" + val existedDeclaration = sModelUtils.findOneDeclarationOrNull(name, model) + if (existedDeclaration != null) { + existedDeclaration + } else { + val rightCompositeFBType = factory.createCompositeFBTypeDeclaration(StringIdentifier(name)) + val plug = factory.createPlugDeclaration(StringIdentifier("plug")) + plug.typeReference.setTarget(revealResult.middleAdapter) + rightCompositeFBType.plugs += plug + sModelUtils.addDeclarationToModel(rightCompositeFBType, model, getPackageName(adapterType.name)) + createPublishSubscribeAdapter( + compositeFBType = rightCompositeFBType, + declaration = plug, + currentModel = model, + packageName = adapterType.name + ) + } } revealResult.rightPublishSubscribeAdapter = rightPublishSubscribeAdapter val rightPublishSubscribeAdapterBlock = factoryUtils.addFunctionalBlock( @@ -539,10 +581,17 @@ class ExtendedAdapterUtils( val eventToNumberFbType = eventToNumberFbTypes.computeIfAbsent( typeDescriptor.eventOutputPorts.size ) { number -> - createEventToNumberConverter( - name = "EventToNumberAdapter_$number", - inputCount = number, - ).also { currentModel.addRootNodes(it, virtualPackage = getPackageName(packageName)) } + val name = "EventToNumberAdapter_$number" + findDeclarationOrCreate( + name = name, + model = currentModel, + virtualPackage = getPackageName(packageName), + ) { + createEventToNumberConverter( + name = name, + inputCount = number, + ) + } } val eventToNumberBlock = factoryUtils.addFunctionalBlock(eventToNumberFbType, compositeFBType.network) @@ -550,10 +599,17 @@ class ExtendedAdapterUtils( val numberToEventFbType = numberToEventFbTypes.computeIfAbsent( typeDescriptor.eventInputPorts.size ) { number -> - createNumberToEventConverter( - name = "NumberToEventAdapter_$number", - inputCount = number, - ).also { currentModel.addRootNodes(it, virtualPackage = getPackageName(packageName)) } + val name = "NumberToEventAdapter_$number" + findDeclarationOrCreate( + name = name, + model = currentModel, + virtualPackage = getPackageName(packageName), + ) { + createNumberToEventConverter( + name = "NumberToEventAdapter_$number", + inputCount = number, + ) + } } val numberToEventBlock = factoryUtils.addFunctionalBlock(numberToEventFbType, compositeFBType.network) @@ -772,13 +828,8 @@ class ExtendedAdapterUtils( private fun getFBTypeFrom4diacModel( currentModel: SModel, nodeName: String, - ): FBTypeDeclaration = publishSubscribeProvider?.invoke(nodeName) ?: owner.adapter( - ModelImports(currentModel).importedModels - .first { it.modelName == "iec61499.4diac.stdlib" } - .resolve(owner.mpsRepository) - .rootNodes - .first { it.name == nodeName } - ) + ): FBTypeDeclaration = publishSubscribeProvider?.invoke(nodeName) + ?: sModelUtils.getFBTypeFrom4diacModel(currentModel, nodeName) private fun createCompositeFB( name: String, @@ -838,24 +889,27 @@ class ExtendedAdapterUtils( } return null } -} - -fun SModel.addRootNodes( - vararg declarations: Declaration?, - virtualPackage: String? = null, -) { - val distinctDeclarations = declarations.associateBy { it?.name } - rootNodes.filter { distinctDeclarations[it.name] != null } - .forEach { removeRootNode(it) } - for (declaration in distinctDeclarations.values) { - if (declaration == null) { - continue - } - val node = (declaration as PlatformElement).node - if (!virtualPackage.isNullOrEmpty()) { - node.setProperty(SNodeUtil.property_BaseConcept_virtualPackage, virtualPackage) + /** + * Tries to find exactly one declaration with provided name in provided model, + * returns it if type of declaration matched with expected. + * Otherwise, returns declaration from producer and adds it to the model + * and removes all other nodes with provided name. + * + * This method helps to prevent reference invalidation after second application of the reveal algorithm + */ + private inline fun findDeclarationOrCreate( + name: String, + model: SModel, + virtualPackage: String?, + declarationProvider: () -> T, + ): T { + val existedDeclaration = sModelUtils.findOneDeclarationOrNull(name, model) + if (existedDeclaration != null) { + return existedDeclaration } - addRootNode(node) + val newDeclaration = declarationProvider() + sModelUtils.addDeclarationToModel(newDeclaration, model, virtualPackage) + return newDeclaration } } diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/utils/SModelUtils.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/SModelUtils.kt new file mode 100644 index 000000000..218f934d4 --- /dev/null +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/utils/SModelUtils.kt @@ -0,0 +1,67 @@ +package org.fbme.extensions.utils + +import jetbrains.mps.smodel.ModelImports +import jetbrains.mps.smodel.SNodeUtil +import org.fbme.ide.iec61499.repository.PlatformElement +import org.fbme.ide.iec61499.repository.PlatformRepository +import org.fbme.lib.common.Declaration +import org.fbme.lib.iec61499.declarations.FBTypeDeclaration +import org.jetbrains.mps.openapi.model.SModel + +class SModelUtils( + private val owner: PlatformRepository, +) { + fun getFBTypeFrom4diacModel( + currentModel: SModel, + nodeName: String, + ): FBTypeDeclaration = owner.adapter( + ModelImports(currentModel).importedModels + .first { it.modelName == MODEL_4DIAC_NAME } + .resolve(owner.mpsRepository) + .rootNodes + .first { it.name == nodeName } + ) + + inline fun findOneDeclarationOrNull( + name: String, + model: SModel, + ): T? = findDeclarations(setOf(name), model).singleOrNull() as? T + + fun findDeclarations( + names: Collection, + model: SModel, + ): List = model.rootNodes.asSequence() + .mapNotNull { + if (it.name !in names) { + null + } else { + owner.adapter(it) + } + } + .toList() + + fun addDeclarationToModel(declaration: Declaration?, model: SModel, virtualPackage: String? = null) = + addDeclarationsToModel(declaration, model = model, virtualPackage = virtualPackage) + + fun addDeclarationsToModel( + vararg declarations: Declaration?, + model: SModel, + virtualPackage: String? = null, + ) { + val distinctDeclarations = declarations.filterNotNull().associateBy { it.name } + val existedNodes = model.rootNodes.filter { it.name in distinctDeclarations } + for (node in existedNodes) { + node.delete() + } + + for (declaration in distinctDeclarations.values) { + val node = (declaration as PlatformElement).node + if (!virtualPackage.isNullOrEmpty()) { + node.setProperty(SNodeUtil.property_BaseConcept_virtualPackage, virtualPackage) + } + model.addRootNode(node) + } + } +} + +private const val MODEL_4DIAC_NAME = "iec61499.4diac.stdlib" From 4bdeeb1320229e3ff1a57b69bdd83b2983cf24dd Mon Sep 17 00:00:00 2001 From: klinachev Date: Tue, 4 Jun 2024 02:31:06 +0300 Subject: [PATCH 13/14] Clean samples --- .../org.fbme.ide.eccSandbox/OneStateTest.fbt | 1 - ....fbme.ide.iec61499.lang.sandbox.blinky.mps | 766 +------ ...e.ide.iec61499.lang.sandbox.ea_example.mps | 1834 ----------------- .../ConfigurationManager.fbt | 22 +- .../ConsoleControl.fbt | 20 +- ...g.fbme.ide.iec61499.lang.sandbox.festo.mps | 44 +- ...e.iec61499.lang.sandbox.mpsPersistence.mps | 73 +- .../FBME_E_SR.fbt | 4 +- .../header.iec61499 | 1 - 9 files changed, 115 insertions(+), 2650 deletions(-) delete mode 100644 samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps diff --git a/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt b/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt index 094958154..f00608d4a 100644 --- a/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.eccSandbox/models/org.fbme.ide.eccSandbox/OneStateTest.fbt @@ -17,7 +17,6 @@ - diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps index 4518eb706..6beb1b7fa 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.blinky.mps @@ -11,14 +11,9 @@ - - - - - @@ -33,10 +28,6 @@ - - - - @@ -47,13 +38,6 @@ - - - - - - - @@ -65,17 +49,8 @@ - - - - - - - - - @@ -106,10 +81,6 @@ - - - - @@ -130,28 +101,15 @@ - - - - - - - - - - - - - @@ -217,105 +175,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -326,7 +187,9 @@ - + + + @@ -337,7 +200,7 @@ - + @@ -348,7 +211,7 @@ - + @@ -359,18 +222,18 @@ - - + + - + - - + + @@ -383,16 +246,16 @@ - - + + - - + + @@ -404,8 +267,8 @@ - - + + @@ -417,61 +280,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -493,10 +303,10 @@ - - - - + + + + @@ -508,7 +318,11 @@ - + + + + + @@ -519,7 +333,7 @@ - + @@ -530,14 +344,14 @@ - + - - + + @@ -550,526 +364,32 @@ - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps deleted file mode 100644 index 6a76bf56d..000000000 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.ea_example.mps +++ /dev/null @@ -1,1834 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt index 922518936..b2c24c4dd 100755 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConfigurationManager.fbt @@ -37,12 +37,12 @@ - + - + - + @@ -65,19 +65,19 @@ - - - - + + + + - + - + - + @@ -86,7 +86,7 @@ - + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt index de59a9d31..8093ffd19 100755 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.enas/ConsoleControl.fbt @@ -30,27 +30,27 @@ - + - + - + - + - - - - - - + + + + + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps index fb36eccae..1388ae2ec 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.festo.mps @@ -347,8 +347,8 @@ - - + + @@ -360,8 +360,8 @@ - - + + @@ -373,8 +373,8 @@ - - + + @@ -385,8 +385,8 @@ - - + + @@ -413,8 +413,8 @@ - - + + @@ -427,8 +427,8 @@ - - + + @@ -441,8 +441,8 @@ - - + + @@ -450,8 +450,8 @@ - - + + @@ -459,8 +459,8 @@ - - + + @@ -478,8 +478,8 @@ - - + + @@ -492,8 +492,8 @@ - - + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps index 485c44a68..67b9e3804 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps @@ -12,7 +12,7 @@ - + @@ -134,10 +134,6 @@ - - - - @@ -230,7 +226,6 @@ - @@ -290,8 +285,8 @@ - - + + @@ -311,8 +306,8 @@ - - + + @@ -392,8 +387,8 @@ - - + + @@ -401,8 +396,8 @@ - - + + @@ -797,8 +792,8 @@ - - + + @@ -810,8 +805,8 @@ - - + + @@ -1045,7 +1040,6 @@ - @@ -1123,9 +1117,7 @@ - - - + @@ -1136,38 +1128,27 @@ - - - - - + - - + + - - + + - - - - - - - @@ -1176,8 +1157,8 @@ - - + + @@ -1188,16 +1169,16 @@ - - + + - - + + @@ -1263,8 +1244,8 @@ - - + + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt index 46c8a26a7..4010e0b34 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/FBME_E_SR.fbt @@ -30,10 +30,10 @@ - + - + diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 index 002273287..e0a8de51e 100644 --- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 +++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.iec61499.lang.sandbox.standard/header.iec61499 @@ -1,6 +1,5 @@ - From 14982fc2c988e9d39ff6242a9f37046c7c2da927 Mon Sep 17 00:00:00 2001 From: klinachev Date: Thu, 6 Jun 2024 21:33:19 +0300 Subject: [PATCH 14/14] AdapterRevealService constructor simplified --- .../extensions/adapter/AdapterRevealService.kt | 15 ++++++++++----- .../extensions/utils/AdapterRevealServiceTest.kt | 2 +- .../actions/RevealAllExtendedAdaptersAction.kt | 9 +-------- .../actions/RevealExtendedAdapterAction.kt | 8 +------- .../richediting/actions/SyncSystemResources.kt | 11 +---------- 5 files changed, 14 insertions(+), 31 deletions(-) diff --git a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt index a04174087..6e6069e7c 100644 --- a/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt +++ b/code/extensions/src/main/kotlin/org/fbme/extensions/adapter/AdapterRevealService.kt @@ -21,14 +21,19 @@ import org.jetbrains.mps.openapi.model.SModel import org.jetbrains.mps.openapi.model.SNode class AdapterRevealService( - factory: IEC61499Factory, - stFactory: STFactory, private val owner: PlatformRepository, publishSubscribeProvider: ((name: String) -> FBTypeDeclaration)? = null, ): AdapterRevealApi { - private val extendedAdapterUtils: ExtendedAdapterUtils = - ExtendedAdapterUtils(factory, stFactory, owner, publishSubscribeProvider) - private val factoryUtils: IEC61499FactoryUtils = IEC61499FactoryUtils(factory) + private val extendedAdapterUtils: ExtendedAdapterUtils + private val factoryUtils: IEC61499FactoryUtils + + init { + val stFactory: STFactory = owner.stFactory + val factory: IEC61499Factory = owner.iec61499Factory + extendedAdapterUtils = ExtendedAdapterUtils(factory, stFactory, owner, publishSubscribeProvider) + factoryUtils = IEC61499FactoryUtils(factory) + } + override fun revealAdapter( extendedAdapter: ExtendedAdapterTypeDeclaration, diff --git a/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt index f5536eb68..d9674deeb 100644 --- a/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt +++ b/code/extensions/src/test/kotlin/org/fbme/ide/extensions/utils/AdapterRevealServiceTest.kt @@ -19,7 +19,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull class AdapterRevealServiceTest : PlatformTestBase() { - private val adapterRevealService = AdapterRevealService(factory, stFactory, repository, ::getPublishSubscribeBlock) + private val adapterRevealService = AdapterRevealService(repository, ::getPublishSubscribeBlock) private val publishSubscribeMap: Map = mapOf( "PUBLISH_5" to rootConverterByPath("/source/publishSubscribes/PUBLISH_5.fbt").convertFBType(), "SUBSCRIBE_5" to rootConverterByPath("/source/publishSubscribes/SUBSCRIBE_5.fbt").convertFBType() diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt index db8fc8c07..9fb2f1e29 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealAllExtendedAdaptersAction.kt @@ -10,7 +10,6 @@ import jetbrains.mps.model.ModelDeleteHelper import jetbrains.mps.project.AbstractModule import jetbrains.mps.project.MPSProject import org.fbme.extensions.adapter.AdapterRevealService -import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider import org.jetbrains.mps.openapi.model.EditableSModel import org.jetbrains.mps.openapi.model.SModel import org.jetbrains.mps.openapi.model.SModelName @@ -22,15 +21,9 @@ class RevealAllExtendedAdaptersAction : AnAction(), DumbAware { } override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { - val repository = event.repository - val factory = repository.iec61499Factory val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) val project = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) - val adapterRevealService = AdapterRevealService( - factory = factory, - stFactory = repository.stFactory, - owner = PlatformRepositoryProvider.getInstance(project), - ) + val adapterRevealService = AdapterRevealService(owner = event.repository) val modelCopy = copyModel(model, project, "${model.name}_extensions_revealed") adapterRevealService.revealModel(modelCopy) } diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt index 6277a854d..a7ddd817a 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/RevealExtendedAdapterAction.kt @@ -6,7 +6,6 @@ import com.intellij.openapi.project.DumbAware import jetbrains.mps.ide.actions.MPSCommonDataKeys import jetbrains.mps.project.MPSProject import org.fbme.extensions.adapter.AdapterRevealService -import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider import org.fbme.lib.iec61499.declarations.extention.ExtendedAdapterTypeDeclaration class RevealExtendedAdapterAction : AnAction(), DumbAware { @@ -21,7 +20,6 @@ class RevealExtendedAdapterAction : AnAction(), DumbAware { override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { val repository = event.repository - val factory = repository.iec61499Factory val node = event.getData(MPSCommonDataKeys.NODE) val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) val project: MPSProject = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) @@ -29,11 +27,7 @@ class RevealExtendedAdapterAction : AnAction(), DumbAware { val extendedAdapter = node?.let { repository.adapterOrNull(node) } ?: return@executeWriteActionInEditor - val adapterRevealService = AdapterRevealService( - factory = factory, - stFactory = repository.stFactory, - owner = PlatformRepositoryProvider.getInstance(project), - ) + val adapterRevealService = AdapterRevealService(owner = repository) val modelCopy = copyModel(model, project, "${model.name}_extensions_revealed") val nodeCopy = modelCopy.rootNodes.first { it.name == extendedAdapter.name } val extendedAdapterCopy = repository.adapter(nodeCopy) diff --git a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt index a91b28bab..2c3f49966 100644 --- a/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt +++ b/code/richediting/src/main/kotlin/org/fbme/ide/richediting/actions/SyncSystemResources.kt @@ -4,10 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAware import jetbrains.mps.ide.actions.MPSCommonDataKeys -import jetbrains.mps.project.MPSProject import org.fbme.extensions.adapter.AdapterRevealApi import org.fbme.extensions.adapter.AdapterRevealService -import org.fbme.ide.iec61499.repository.PlatformRepositoryProvider import org.fbme.lib.iec61499.declarations.SystemDeclaration class SyncSystemResources : AnAction(), DumbAware { @@ -17,18 +15,11 @@ class SyncSystemResources : AnAction(), DumbAware { } override fun actionPerformed(event: AnActionEvent) = event.executeWriteActionInEditor { - val repository = event.repository - val factory = repository.iec61499Factory val model = event.getRequiredData(MPSCommonDataKeys.CONTEXT_MODEL) - val project: MPSProject = event.getRequiredData(MPSCommonDataKeys.MPS_PROJECT) val systemDeclaration = event.element() ?: return@executeWriteActionInEditor - val adapterRevealApi: AdapterRevealApi = AdapterRevealService( - factory = factory, - stFactory = repository.stFactory, - owner = PlatformRepositoryProvider.getInstance(project) - ) + val adapterRevealApi: AdapterRevealApi = AdapterRevealService(owner = event.repository) adapterRevealApi.syncApplicationResources(systemDeclaration, model) } } \ No newline at end of file