From 43b9d55f5e59fbc227e7d5dc3d4bd1bd50951e9f Mon Sep 17 00:00:00 2001 From: valb3r Date: Wed, 1 Jan 2025 14:45:30 +0200 Subject: [PATCH] FBP-395. Update element order --- .../plugin/activiti/parser/Activiti7Parser.kt | 2 +- .../plugin/activiti/parser/ActivitiParser.kt | 2 +- .../plugin/camunda/parser/CamundaParser.kt | 2 +- .../plugin/flowable/parser/FlowableParser.kt | 2 +- .../plugin/bpmn/parser/core/BaseBpmnParser.kt | 114 +++++++++++------- 5 files changed, 76 insertions(+), 46 deletions(-) diff --git a/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/Activiti7Parser.kt b/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/Activiti7Parser.kt index 044e3e19..011c312b 100644 --- a/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/Activiti7Parser.kt +++ b/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/Activiti7Parser.kt @@ -8,7 +8,7 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.parser.core.XmlType enum class Activiti7PropertyTypeDetails(val details: PropertyTypeDetails) { ID(PropertyTypeDetails(PropertyType.ID, "id", XmlType.ATTRIBUTE)), NAME(PropertyTypeDetails(PropertyType.NAME,"name", XmlType.ATTRIBUTE)), - DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA, forceFirst = true)), + DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA)), ASYNC(PropertyTypeDetails(PropertyType.ASYNC, "activiti:async", XmlType.ATTRIBUTE)), ASSIGNEE(PropertyTypeDetails(PropertyType.ASSIGNEE, "activiti:assignee", XmlType.ATTRIBUTE)), CANDIDATE_USERS(PropertyTypeDetails(PropertyType.CANDIDATE_USERS, "activiti:candidateUsers", XmlType.ATTRIBUTE)), diff --git a/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/ActivitiParser.kt b/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/ActivitiParser.kt index 7aa3fb3e..ee280300 100644 --- a/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/ActivitiParser.kt +++ b/activiti-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/activiti/parser/ActivitiParser.kt @@ -19,7 +19,7 @@ const val CDATA_FIELD = "CDATA" enum class ActivitiPropertyTypeDetails(val details: PropertyTypeDetails) { ID(PropertyTypeDetails(PropertyType.ID, "id", XmlType.ATTRIBUTE)), NAME(PropertyTypeDetails(PropertyType.NAME,"name", XmlType.ATTRIBUTE)), - DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA, forceFirst = true)), + DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA)), IS_FOR_COMPENSATION(PropertyTypeDetails(PropertyType.IS_FOR_COMPENSATION, "isForCompensation", XmlType.ATTRIBUTE)), ASYNC(PropertyTypeDetails(PropertyType.ASYNC, "activiti:async", XmlType.ATTRIBUTE)), ASSIGNEE(PropertyTypeDetails(PropertyType.ASSIGNEE, "activiti:assignee", XmlType.ATTRIBUTE)), diff --git a/camunda-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/camunda/parser/CamundaParser.kt b/camunda-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/camunda/parser/CamundaParser.kt index 377ab08f..06b22afb 100644 --- a/camunda-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/camunda/parser/CamundaParser.kt +++ b/camunda-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/camunda/parser/CamundaParser.kt @@ -26,7 +26,7 @@ import org.dom4j.Element enum class CamundaPropertyTypeDetails(val details: PropertyTypeDetails) { ID(PropertyTypeDetails(PropertyType.ID, "id", XmlType.ATTRIBUTE)), NAME(PropertyTypeDetails(PropertyType.NAME,"name", XmlType.ATTRIBUTE)), - DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA, forceFirst = true)), + DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA)), IS_FOR_COMPENSATION(PropertyTypeDetails(PropertyType.IS_FOR_COMPENSATION, "isForCompensation", XmlType.ATTRIBUTE)), ASYNC_BEFORE(PropertyTypeDetails(PropertyType.ASYNC_BEFORE, "camunda:asyncBefore", XmlType.ATTRIBUTE)), ASYNC_AFTER(PropertyTypeDetails(PropertyType.ASYNC_AFTER, "camunda:asyncAfter", XmlType.ATTRIBUTE)), diff --git a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParser.kt b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParser.kt index f132ef08..8fd14aed 100644 --- a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParser.kt +++ b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParser.kt @@ -19,7 +19,7 @@ import org.dom4j.Element enum class FlowablePropertyTypeDetails(val details: PropertyTypeDetails) { ID(PropertyTypeDetails(PropertyType.ID, "id", XmlType.ATTRIBUTE)), NAME(PropertyTypeDetails(PropertyType.NAME,"name", XmlType.ATTRIBUTE)), - DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA, forceFirst = true)), + DOCUMENTATION(PropertyTypeDetails(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA)), IS_FOR_COMPENSATION(PropertyTypeDetails(PropertyType.IS_FOR_COMPENSATION, "isForCompensation", XmlType.ATTRIBUTE)), ASYNC(PropertyTypeDetails(PropertyType.ASYNC, "flowable:async", XmlType.ATTRIBUTE)), ASSIGNEE(PropertyTypeDetails(PropertyType.ASSIGNEE, "flowable:assignee", XmlType.ATTRIBUTE)), diff --git a/xml-parser-core/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/parser/core/BaseBpmnParser.kt b/xml-parser-core/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/parser/core/BaseBpmnParser.kt index 1b72d0af..b78f7362 100644 --- a/xml-parser-core/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/parser/core/BaseBpmnParser.kt +++ b/xml-parser-core/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/parser/core/BaseBpmnParser.kt @@ -44,14 +44,14 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.OutputStream import java.nio.charset.StandardCharsets +import java.util.Comparator const val CDATA_FIELD = "CDATA" data class PropertyTypeDetails( val propertyType: PropertyType, val xmlPath: String, - val type: XmlType, - val forceFirst: Boolean = false + val type: XmlType ) abstract class BaseBpmnParser: BpmnParser { @@ -137,6 +137,22 @@ abstract class BaseBpmnParser: BpmnParser { protected abstract fun propertyTypeDetails(): List protected abstract fun changeElementType(node: Element, name: String, details: PropertyTypeDetails, value: String?) + protected fun elementOrder(parentElem: String, targetElem: String): Int? { + // TODO - use 'parentElem' + val index = arrayListOf( + "message", + "process", + "documentation", + "extensionElements" + ).indexOf(targetElem) + + if (index < 0) { + return null + } + + return index + } + protected fun mapper(): XmlMapper { val mapper: ObjectMapper = XmlMapper( // FIXME https://github.com/FasterXML/jackson-module-kotlin/issues/138 @@ -247,7 +263,7 @@ abstract class BaseBpmnParser: BpmnParser { } private fun newWaypoint(it: IdentifiableWaypoint, parentEdgeElem: Element) { - val elem = parentEdgeElem.addElement(omgdiNs().named("waypoint")) + val elem = parentEdgeElem.addElementInternal(omgdiNs().named("waypoint")) elem.addAttribute("x", it.x.toString()) elem.addAttribute("y", it.y.toString()) } @@ -300,10 +316,10 @@ abstract class BaseBpmnParser: BpmnParser { val shapeParent = doc.selectSingleNode( "//*[local-name()='BPMNDiagram']/*[local-name()='BPMNPlane'][1]" ) as Element - val newShape = shapeParent.addElement(bpmndiNs().named("BPMNShape")) + val newShape = shapeParent.addElementInternal(bpmndiNs().named("BPMNShape")) newShape.addAttribute("id", update.shape.id.id) newShape.addAttribute("bpmnElement", update.bpmnObject.id.id) - val newBounds = newShape.addElement(omgdcNs().named("Bounds")) + val newBounds = newShape.addElementInternal(omgdcNs().named("Bounds")) val bounds = update.shape.rectBounds() newBounds.addAttribute("x", bounds.x.toString()) newBounds.addAttribute("y", bounds.y.toString()) @@ -367,12 +383,12 @@ abstract class BaseBpmnParser: BpmnParser { ) // Service tasks - is BpmnUserTask -> diagramParent.addElement(modelNs().named("userTask")) - is BpmnScriptTask -> diagramParent.addElement(modelNs().named("scriptTask")) + is BpmnUserTask -> diagramParent.addElementInternal(modelNs().named("userTask")) + is BpmnScriptTask -> diagramParent.addElementInternal(modelNs().named("scriptTask")) is BpmnServiceTask -> createServiceTask(diagramParent) - is BpmnBusinessRuleTask -> diagramParent.addElement(modelNs().named("businessRuleTask")) - is BpmnReceiveTask -> diagramParent.addElement(modelNs().named("receiveTask")) - is BpmnManualTask -> diagramParent.addElement(modelNs().named("manualTask")) + is BpmnBusinessRuleTask -> diagramParent.addElementInternal(modelNs().named("businessRuleTask")) + is BpmnReceiveTask -> diagramParent.addElementInternal(modelNs().named("receiveTask")) + is BpmnManualTask -> diagramParent.addElementInternal(modelNs().named("manualTask")) is BpmnCamelTask -> createServiceTaskWithType(diagramParent, "camel") is BpmnSendEventTask -> createServiceTaskWithType(diagramParent, "send-event") is BpmnHttpTask -> createServiceTaskWithType(diagramParent, "http") @@ -383,17 +399,17 @@ abstract class BaseBpmnParser: BpmnParser { is BpmnShellTask -> createServiceTaskWithType(diagramParent, "shell") // Sub processes - is BpmnCallActivity -> diagramParent.addElement(modelNs().named("callActivity")) - is BpmnSubProcess -> diagramParent.addElement(modelNs().named("subProcess")) + is BpmnCallActivity -> diagramParent.addElementInternal(modelNs().named("callActivity")) + is BpmnSubProcess -> diagramParent.addElementInternal(modelNs().named("subProcess")) is BpmnEventSubprocess -> createEventSubprocess(diagramParent) - is BpmnAdHocSubProcess -> diagramParent.addElement(modelNs().named("adHocSubProcess")) - is BpmnTransactionalSubProcess -> diagramParent.addElement(modelNs().named("transaction")) + is BpmnAdHocSubProcess -> diagramParent.addElementInternal(modelNs().named("adHocSubProcess")) + is BpmnTransactionalSubProcess -> diagramParent.addElementInternal(modelNs().named("transaction")) // Gateways - is BpmnExclusiveGateway -> diagramParent.addElement(modelNs().named("exclusiveGateway")) - is BpmnParallelGateway -> diagramParent.addElement(modelNs().named("parallelGateway")) - is BpmnInclusiveGateway -> diagramParent.addElement(modelNs().named("inclusiveGateway")) - is BpmnEventGateway -> diagramParent.addElement(modelNs().named("eventBasedGateway")) + is BpmnExclusiveGateway -> diagramParent.addElementInternal(modelNs().named("exclusiveGateway")) + is BpmnParallelGateway -> diagramParent.addElementInternal(modelNs().named("parallelGateway")) + is BpmnInclusiveGateway -> diagramParent.addElementInternal(modelNs().named("inclusiveGateway")) + is BpmnEventGateway -> diagramParent.addElementInternal(modelNs().named("eventBasedGateway")) else -> null } @@ -403,43 +419,43 @@ abstract class BaseBpmnParser: BpmnParser { } private fun createBoundaryEventWithType(elem: Element, type: String): Element { - val newElem = elem.addElement(modelNs().named("boundaryEvent")) - newElem.addElement(modelNs().named(type)) + val newElem = elem.addElementInternal(modelNs().named("boundaryEvent")) + newElem.addElementInternal(modelNs().named(type)) return newElem } private fun createStartEventWithType(elem: Element, type: String?): Element { - val newElem = elem.addElement(modelNs().named("startEvent")) - type?.let { newElem.addElement(modelNs().named(it)) } + val newElem = elem.addElementInternal(modelNs().named("startEvent")) + type?.let { newElem.addElementInternal(modelNs().named(it)) } return newElem } private fun createEndEventWithType(elem: Element, type: String?): Element { - val newElem = elem.addElement(modelNs().named("endEvent")) - type?.let { newElem.addElement(modelNs().named(it)) } + val newElem = elem.addElementInternal(modelNs().named("endEvent")) + type?.let { newElem.addElementInternal(modelNs().named(it)) } return newElem } protected fun createIntermediateCatchEventWithType(elem: Element, type: String): Element { - val newElem = elem.addElement(modelNs().named("intermediateCatchEvent")) - newElem.addElement(modelNs().named(type)) + val newElem = elem.addElementInternal(modelNs().named("intermediateCatchEvent")) + newElem.addElementInternal(modelNs().named(type)) return newElem } protected fun createIntermediateThrowEventWithType(elem: Element, type: String?): Element { - val newElem = elem.addElement(modelNs().named("intermediateThrowEvent")) - type?.let { newElem.addElement(modelNs().named(it)) } + val newElem = elem.addElementInternal(modelNs().named("intermediateThrowEvent")) + type?.let { newElem.addElementInternal(modelNs().named(it)) } return newElem } protected fun createServiceTaskWithType(elem: Element, type: String? = null): Element { - val newElem = elem.addElement(modelNs().named("serviceTask")) + val newElem = elem.addElementInternal(modelNs().named("serviceTask")) type?.let { newElem.addAttribute(engineNs().named("type"), it) } return newElem } private fun createEventSubprocess(elem: Element): Element { - val newElem = elem.addElement(modelNs().named("subProcess")) + val newElem = elem.addElementInternal(modelNs().named("subProcess")) newElem.addAttribute("triggeredByEvent", "true") return newElem } @@ -451,7 +467,7 @@ abstract class BaseBpmnParser: BpmnParser { )!! val newNode = when (update.bpmnObject.element) { - is BpmnSequenceFlow -> diagramParent.addElement(modelNs().named("sequenceFlow")) + is BpmnSequenceFlow -> diagramParent.addElementInternal(modelNs().named("sequenceFlow")) else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) } @@ -461,7 +477,7 @@ abstract class BaseBpmnParser: BpmnParser { val shapeParent = doc.selectSingleNode( "//*[local-name()='BPMNDiagram']/*[local-name()='BPMNPlane'][1]" ) as Element - val newShape = shapeParent.addElement(bpmndiNs().named("BPMNEdge")) + val newShape = shapeParent.addElementInternal(bpmndiNs().named("BPMNEdge")) newShape.addAttribute("id", update.edge.id.id) newShape.addAttribute("bpmnElement", update.bpmnObject.id.id) update.edge.waypoint.filter { it.physical }.forEach { newWaypoint(it, newShape) } @@ -548,16 +564,7 @@ abstract class BaseBpmnParser: BpmnParser { return } - // Sorting data in CustomizedXmlWriter is expensive performance-wise - val newElem = if (details.forceFirst) { - val newElem = currentNode.addElement(name) - currentNode.remove(newElem) - currentNode.content().add(0, newElem) - newElem - } else { - currentNode.addElement(name) - } - + val newElem = currentNode.addElementInternal(name) currentNode = newElem // TODO Handle this with setAttributeOrValueOrCdataOrRemoveIfNull ? if (attrName != "\$") { @@ -695,6 +702,29 @@ abstract class BaseBpmnParser: BpmnParser { private fun byPrefix(prefix: String): NS { return listOf(modelNs(), bpmndiNs(), omgdcNs(), omgdiNs(), xsiNs(), engineNs()).firstOrNull { it.namePrefix == prefix }!! } + + private fun Element.addElementInternal(name: QName): Element { + return addOrderedElement(this, name.name) { this.addElement(name) } + } + + private fun Element.addElementInternal(name: String): Element { + return addOrderedElement(this, name) { this.addElement(name) } + } + + private fun addOrderedElement(target: Element, name: String, addElem: () -> Element): Element { + val addedSimpleName = name + elementOrder(name, addedSimpleName) ?: return addElem() + // Sorting data in CustomizedXmlWriter is expensive performance-wise + val existingElems = target.elements().map { it.name }.toMutableList() + existingElems.add(addedSimpleName) + existingElems.sortedWith(Comparator.comparingInt { elementOrder(target.name, it) ?: Int.MAX_VALUE }) + val addToIndex = existingElems.indexOf(addedSimpleName) + + val newElem = addElem() + target.remove(newElem) + target.content().add(addToIndex, newElem) + return newElem + } } data class NS(val namePrefix: String, val url: String) {