From 60accea92e7bc3aaacf7537111b8ac1f20d36fe4 Mon Sep 17 00:00:00 2001 From: valb3r Date: Wed, 13 May 2020 20:51:05 +0300 Subject: [PATCH 01/10] FBP-6. Extracted event interfaces --- .../bpmn/intellij/plugin/events/Events.kt | 7 ++-- .../plugin/events/ProcessModelUpdateEvents.kt | 4 +++ .../plugin/render/BpmnProcessRenderer.kt | 22 ++++++------ .../intellij/plugin/render/EdgeExtension.kt | 34 +++++++++---------- .../intellij/plugin/state/CurrentState.kt | 11 +++--- .../bpmn/api}/events/EventInterfaces.kt | 30 ++++++++++++---- 6 files changed, 66 insertions(+), 42 deletions(-) rename {bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin => xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api}/events/EventInterfaces.kt (57%) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt index 3183ce4a4..844e73aba 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt @@ -4,10 +4,9 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.WithBpmnId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.ShapeElement +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.* import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType -import com.valb3r.bpmn.intellij.plugin.render.EdgeElementState -import com.valb3r.bpmn.intellij.plugin.render.WaypointElementState data class StringValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, val newValue: String): PropertyUpdateWithId @@ -15,7 +14,7 @@ data class BooleanValueUpdatedEvent(override val bpmnElementId: BpmnElementId, o data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float): LocationUpdateWithId -data class NewWaypointsEvent(override val edgeElementId: DiagramElementId, override val waypoints: List): NewWaypoints +data class NewWaypointsEvent(override val edgeElementId: DiagramElementId, override val waypoints: List): NewWaypoints data class DiagramElementRemovedEvent(override val elementId: DiagramElementId): DiagramElementRemoved @@ -23,6 +22,6 @@ data class BpmnElementRemovedEvent(override val elementId: BpmnElementId): BpmnE data class BpmnShapeObjectAddedEvent(override val bpmnObject: WithBpmnId, override val shape: ShapeElement, override val props: Map): BpmnShapeObjectAdded -data class BpmnEdgeObjectAddedEvent(override val bpmnObject: WithBpmnId, override val edge: EdgeElementState, override val props: Map): BpmnEdgeObjectAdded +data class BpmnEdgeObjectAddedEvent(override val bpmnObject: WithBpmnId, override val edge: EdgeWithIdentifiableWaypoints, override val props: Map): BpmnEdgeObjectAdded data class CommittedToFile(val eventCount: Int): Event \ No newline at end of file diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt index 1994e1ce1..c1a6153cf 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt @@ -2,6 +2,10 @@ package com.valb3r.bpmn.intellij.plugin.events import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.Event +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EventOrder +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.LocationUpdateWithId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.PropertyUpdateWithId import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicLong diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt index 9d7f271e5..56f10de00 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt @@ -6,6 +6,8 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.* import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.* +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EdgeWithIdentifiableWaypoints +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType.NAME @@ -52,7 +54,7 @@ class BpmnProcessRenderer { return areaByElement } - private fun drawBpmnEdges(shapes: List, areaByElement: MutableMap, canvas: CanvasPainter, renderMeta: RenderMetadata) { + private fun drawBpmnEdges(shapes: List, areaByElement: MutableMap, canvas: CanvasPainter, renderMeta: RenderMetadata) { shapes.forEach { mergeArea(it.id, areaByElement, drawEdgeElement(canvas, it, renderMeta)) if (isActive(it.id, renderMeta)) { @@ -106,7 +108,7 @@ class BpmnProcessRenderer { areas[id] = AreaWithZindex(target.area, area.dragCenter, target.areaType, target.anchorsForWaypoints, target.anchorsForShape, min(target.index, area.index), target.parentToSelect ?: area.parentToSelect) } - private fun drawEdgeElement(canvas: CanvasPainter, shape: EdgeElementState, meta: RenderMetadata): AreaWithZindex { + private fun drawEdgeElement(canvas: CanvasPainter, shape: EdgeWithIdentifiableWaypoints, meta: RenderMetadata): AreaWithZindex { val active = isActive(shape.id, meta) val area = Area() @@ -122,7 +124,7 @@ class BpmnProcessRenderer { return AreaWithZindex(area, Point2D.Float(0.0f, 0.0f), AreaType.POINT, if (active) mutableSetOf() else trueWaypoints.map { Point2D.Float(it.x, it.y) }.toMutableSet()) } - private fun drawWaypointElements(canvas: CanvasPainter, shape: EdgeElementState, meta: RenderMetadata): Map { + private fun drawWaypointElements(canvas: CanvasPainter, shape: EdgeWithIdentifiableWaypoints, meta: RenderMetadata): Map { val area = HashMap() val trueWaypoints = calculateTrueWaypoints(shape, meta) // draw all endpoints only if none virtual is dragged and not physical @@ -139,10 +141,10 @@ class BpmnProcessRenderer { return area } - private fun drawWaypointAnchors(canvas: CanvasPainter, begin: WaypointElementState, end: WaypointElementState, parent: EdgeElementState, meta: RenderMetadata, isLast: Boolean, endWaypointIndex: Int): Map { + private fun drawWaypointAnchors(canvas: CanvasPainter, begin: IdentifiableWaypoint, end: IdentifiableWaypoint, parent: EdgeWithIdentifiableWaypoints, meta: RenderMetadata, isLast: Boolean, endWaypointIndex: Int): Map { val result = HashMap() - val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: WaypointElementState -> + val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: IdentifiableWaypoint -> if (elem.physical) { dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy)) } else { @@ -156,7 +158,7 @@ class BpmnProcessRenderer { } } - val drawNode = { node: WaypointElementState, index: Int -> + val drawNode = { node: IdentifiableWaypoint, index: Int -> val translatedNode = translateElement(meta, node) val active = isActive(node.id, meta) val color = color(active, if (node.physical) Colors.WAYPOINT_COLOR else Colors.MID_WAYPOINT_COLOR) @@ -194,11 +196,11 @@ class BpmnProcessRenderer { return result } - private fun calculateTrueWaypoints(shape: EdgeElementState, meta: RenderMetadata): List { + private fun calculateTrueWaypoints(shape: EdgeWithIdentifiableWaypoints, meta: RenderMetadata): List { return shape.waypoint.filter { it.physical || isActive(it.id, meta) } } - private fun drawEdge(canvas: CanvasPainter, begin: WaypointElementState, end: WaypointElementState, meta: RenderMetadata, color: Color, isLast: Boolean): Area { + private fun drawEdge(canvas: CanvasPainter, begin: IdentifiableWaypoint, end: IdentifiableWaypoint, meta: RenderMetadata, color: Color, isLast: Boolean): Area { val translatedBegin = translateElement(meta, begin) val translatedEnd = translateElement(meta, end) if (isLast) { @@ -326,7 +328,7 @@ class BpmnProcessRenderer { return elemId.let { meta.selectedIds.contains(it) } } - private fun drawActionsElement(canvas: CanvasPainter, edge: EdgeElementState, ctx: ElementInteractionContext, actions: Map Unit>): Map { + private fun drawActionsElement(canvas: CanvasPainter, edge: EdgeWithIdentifiableWaypoints, ctx: ElementInteractionContext, actions: Map Unit>): Map { val minX = edge.waypoint.minBy { it.x }?.x ?: 0.0f val minY = edge.waypoint.minBy { it.y }?.y ?: 0.0f val maxX = edge.waypoint.maxBy { it.x }?.x ?: 0.0f @@ -342,7 +344,7 @@ class BpmnProcessRenderer { ) } - private fun drawActionsElement(canvas: CanvasPainter, waypoint: WaypointElementState, ctx: ElementInteractionContext, actions: Map Unit>): Map { + private fun drawActionsElement(canvas: CanvasPainter, waypoint: IdentifiableWaypoint, ctx: ElementInteractionContext, actions: Map Unit>): Map { return drawActionsElement( canvas, waypoint.id, diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt index c591dad4e..ed9fcf643 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt @@ -3,17 +3,17 @@ package com.valb3r.bpmn.intellij.plugin.render import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.EdgeElement -import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.Translatable import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WaypointElement -import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WithDiagramId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EdgeWithIdentifiableWaypoints +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint import java.util.* import kotlin.collections.ArrayList data class EdgeElementState ( - val id: DiagramElementId, - val bpmnElement: BpmnElementId?, - val waypoint: MutableList = mutableListOf() -) { + override val id: DiagramElementId, + override val bpmnElement: BpmnElementId?, + override val waypoint: MutableList = mutableListOf() +): EdgeWithIdentifiableWaypoints { constructor(elem: EdgeElement): this(elem.id, elem.bpmnElement, ArrayList()) { elem.waypoint?.withIndex()?.forEach { when { @@ -29,7 +29,7 @@ data class EdgeElementState ( } } - constructor(toCopy: EdgeElementState, newPhysicalWaypoints: List): this(toCopy.id, toCopy.bpmnElement, ArrayList()) { + constructor(toCopy: EdgeWithIdentifiableWaypoints, newPhysicalWaypoints: List): this(toCopy.id, toCopy.bpmnElement, ArrayList()) { newPhysicalWaypoints.withIndex().forEach { when { it.index == 0 -> waypoint += it.value @@ -47,12 +47,12 @@ data class EdgeElementState ( data class WaypointElementState ( override val id: DiagramElementId, - val x: Float, - val y: Float, - val origX: Float, - val origY: Float, - val physical: Boolean -): Translatable, WithDiagramId { + override val x: Float, + override val y: Float, + override val origX: Float, + override val origY: Float, + override val physical: Boolean +): IdentifiableWaypoint { constructor(elem: WaypointElement): this(DiagramElementId(UUID.randomUUID().toString()), elem.x, elem.y, elem.x, elem.y, true) constructor(id: String, x: Float, y: Float): this(DiagramElementId(id), x, y, x, y, false) @@ -61,19 +61,19 @@ data class WaypointElementState ( return WaypointElementState(id, x + dx, y + dy, origX, origY, physical) } - fun moveTo(dx: Float, dy: Float): WaypointElementState { + override fun moveTo(dx: Float, dy: Float): WaypointElementState { return WaypointElementState(id, x + dx, y + dy, x + dx, y + dy, physical) } - fun asPhysical(): WaypointElementState { + override fun asPhysical(): WaypointElementState { return WaypointElementState(id, x, y, origX, origY, true) } - fun originalLocation(): WaypointElementState { + override fun originalLocation(): WaypointElementState { return WaypointElementState(id, origX, origY, origX, origY, true) } - fun asWaypointElement(): WaypointElement { + override fun asWaypointElement(): WaypointElement { return WaypointElement(x, y) } } \ No newline at end of file diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt index f8c8a3a17..0754bc657 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt @@ -5,12 +5,13 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.WithBpmnId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.ShapeElement +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EdgeWithIdentifiableWaypoints +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType import com.valb3r.bpmn.intellij.plugin.events.StringValueUpdatedEvent import com.valb3r.bpmn.intellij.plugin.events.updateEventsRegistry import com.valb3r.bpmn.intellij.plugin.render.EdgeElementState -import com.valb3r.bpmn.intellij.plugin.render.WaypointElementState import java.util.concurrent.atomic.AtomicReference private val currentStateProvider = AtomicReference() @@ -27,7 +28,7 @@ fun currentStateProvider(): CurrentStateProvider { data class CurrentState( val shapes: List, - val edges: List, + val edges: List, val elementByDiagramId: Map, val elementByStaticId: Map, val elemPropertiesByStaticElementId: Map> @@ -104,20 +105,20 @@ class CurrentStateProvider { return elem.copyAndTranslate(dx, dy) } - private fun updateLocationAndInnerTopology(elem: EdgeElementState): EdgeElementState { + private fun updateLocationAndInnerTopology(elem: EdgeWithIdentifiableWaypoints): EdgeWithIdentifiableWaypoints { val hasNoCommittedAnchorUpdates = elem.waypoint.firstOrNull { updateEvents.currentLocationUpdateEventList(it.id).isNotEmpty() } val hasNoNewAnchors = updateEvents.newWaypointStructure(elem.id).isEmpty() if (null == hasNoCommittedAnchorUpdates && hasNoNewAnchors) { return elem } - val waypoints: MutableList = + val waypoints: MutableList = updateEvents.newWaypointStructure(elem.id).lastOrNull()?.event?.waypoints?.toMutableList() ?: elem.waypoint.filter { it.physical }.toMutableList() val updatedLocations = waypoints.filter { it.physical }.map { updateWaypointLocation(it) } return EdgeElementState(elem, updatedLocations) } - private fun updateWaypointLocation(waypoint: WaypointElementState): WaypointElementState { + private fun updateWaypointLocation(waypoint: IdentifiableWaypoint): IdentifiableWaypoint { val updates = updateEvents.currentLocationUpdateEventList(waypoint.id) var dx = 0.0f var dy = 0.0f diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/EventInterfaces.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt similarity index 57% rename from bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/EventInterfaces.kt rename to xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt index c222ca8f0..95934a108 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/EventInterfaces.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt @@ -1,14 +1,14 @@ -package com.valb3r.bpmn.intellij.plugin.events +package com.valb3r.bpmn.intellij.plugin.bpmn.api.events import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.WithBpmnId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.ShapeElement +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.Translatable +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WaypointElement +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WithDiagramId import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType -import com.valb3r.bpmn.intellij.plugin.render.EdgeElementState -import com.valb3r.bpmn.intellij.plugin.render.WaypointElementState - interface Event interface EventOrder { @@ -24,7 +24,7 @@ interface LocationUpdateWithId: Event { interface NewWaypoints: Event { val edgeElementId: DiagramElementId - val waypoints: List + val waypoints: List } interface DiagramElementRemoved: Event { @@ -43,11 +43,29 @@ interface BpmnShapeObjectAdded: Event { interface BpmnEdgeObjectAdded: Event { val bpmnObject: WithBpmnId - val edge: EdgeElementState + val edge: EdgeWithIdentifiableWaypoints val props: Map } interface PropertyUpdateWithId: Event { val bpmnElementId: BpmnElementId val property: PropertyType +} + +interface IdentifiableWaypoint: Translatable, WithDiagramId { + val x: Float + val y: Float + val origX: Float + val origY: Float + val physical: Boolean + + fun moveTo(dx: Float, dy: Float): IdentifiableWaypoint + fun asPhysical(): IdentifiableWaypoint + fun originalLocation(): IdentifiableWaypoint + fun asWaypointElement(): WaypointElement +} + +interface EdgeWithIdentifiableWaypoints: WithDiagramId { + val bpmnElement: BpmnElementId? + val waypoint: MutableList } \ No newline at end of file From 466bdbcbbfc464da2f2c18700d49c0c1f9f93ae4 Mon Sep 17 00:00:00 2001 From: valb3r Date: Thu, 14 May 2020 10:17:44 +0300 Subject: [PATCH 02/10] FBP-6. Initialize XML structure update --- .../bpmn/intellij/plugin/events/Events.kt | 2 +- .../plugin/render/BpmnProcessRenderer.kt | 5 +- .../intellij/plugin/render/EdgeExtension.kt | 29 ++-- flowable-xml-parser/build.gradle | 2 +- .../plugin/flowable/parser/FlowableParser.kt | 136 ++++++++++++++++++ .../flowable/parser/FlowableParserTest.kt | 82 +++++++++++ .../src/test/resources/duplicates.bpmn20.xml | 59 ++++++++ .../intellij/plugin/bpmn/api/BpmnParser.kt | 5 + .../plugin/bpmn/api/events/EventInterfaces.kt | 2 + 9 files changed, 306 insertions(+), 16 deletions(-) create mode 100644 flowable-xml-parser/src/test/resources/duplicates.bpmn20.xml diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt index 844e73aba..6b10d1d72 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt @@ -12,7 +12,7 @@ data class StringValueUpdatedEvent(override val bpmnElementId: BpmnElementId, ov data class BooleanValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, val newValue: Boolean): PropertyUpdateWithId -data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float): LocationUpdateWithId +data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float, override val internalPos: Int?): LocationUpdateWithId data class NewWaypointsEvent(override val edgeElementId: DiagramElementId, override val waypoints: List): NewWaypoints diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt index 56f10de00..ef2f01f9d 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt @@ -65,7 +65,6 @@ class BpmnProcessRenderer { val deleteCallback = { dest: ProcessModelUpdateEvents -> dest.addElementRemovedEvent(DiagramElementRemovedEvent(it.id))} val actionsElem = drawActionsElement(canvas, it, renderMeta.interactionContext, mutableMapOf(Actions.DELETE to deleteCallback)) areaByElement += actionsElem - renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy))} } } } @@ -89,7 +88,7 @@ class BpmnProcessRenderer { } val actionsElem = drawActionsElement(canvas, it, renderMeta.interactionContext, mutableMapOf(Actions.DELETE to deleteCallback, Actions.NEW_LINK to newSequenceCallback)) areaByElement += actionsElem - renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy))} + renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy, null))} } } } @@ -146,7 +145,7 @@ class BpmnProcessRenderer { val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: IdentifiableWaypoint -> if (elem.physical) { - dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy)) + dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy, elem.internalPos)) } else { dest.addWaypointStructureUpdate(NewWaypointsEvent( parent.id, diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt index ed9fcf643..b6125cc6f 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt @@ -1,11 +1,13 @@ package com.valb3r.bpmn.intellij.plugin.render +import com.google.common.hash.Hashing import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.EdgeElement import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WaypointElement import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EdgeWithIdentifiableWaypoints import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint +import java.nio.charset.StandardCharsets.UTF_8 import java.util.* import kotlin.collections.ArrayList @@ -17,12 +19,12 @@ data class EdgeElementState ( constructor(elem: EdgeElement): this(elem.id, elem.bpmnElement, ArrayList()) { elem.waypoint?.withIndex()?.forEach { when { - it.index == 0 -> waypoint += WaypointElementState(it.value) + it.index == 0 -> waypoint += WaypointElementState(it.value, it.index) it.index > 0 -> { val midpointX = (elem.waypoint!![it.index - 1].x + it.value.x) / 2.0f val midpointY = (elem.waypoint!![it.index - 1].y + it.value.y) / 2.0f - val next = WaypointElementState(it.value) - waypoint += WaypointElementState(waypoint[it.index - 1].id.id + ":" + next.id.id, midpointX, midpointY) + val next = WaypointElementState(it.value, it.index) + waypoint += WaypointElementState(childWaypointId(waypoint[it.index - 1], next), midpointX, midpointY, it.index) waypoint += next } } @@ -37,12 +39,16 @@ data class EdgeElementState ( val midpointX = (newPhysicalWaypoints[it.index - 1].x + it.value.x) / 2.0f val midpointY = (newPhysicalWaypoints[it.index - 1].y + it.value.y) / 2.0f val next = it.value - waypoint += WaypointElementState(waypoint[it.index - 1].id.id + ":" + next.id.id, midpointX, midpointY) + waypoint += WaypointElementState(childWaypointId(waypoint[it.index - 1], next), midpointX, midpointY, it.index) waypoint += next } } } } + + fun childWaypointId(start: IdentifiableWaypoint, end: IdentifiableWaypoint): String { + return Hashing.md5().hashString(start.id.id + ":" + end.id.id, UTF_8).toString() + } } data class WaypointElementState ( @@ -51,26 +57,27 @@ data class WaypointElementState ( override val y: Float, override val origX: Float, override val origY: Float, - override val physical: Boolean + override val physical: Boolean, + override val internalPos: Int ): IdentifiableWaypoint { - constructor(elem: WaypointElement): this(DiagramElementId(UUID.randomUUID().toString()), elem.x, elem.y, elem.x, elem.y, true) - constructor(id: String, x: Float, y: Float): this(DiagramElementId(id), x, y, x, y, false) + constructor(elem: WaypointElement, internalPos: Int): this(DiagramElementId(UUID.randomUUID().toString()), elem.x, elem.y, elem.x, elem.y, true, internalPos) + constructor(id: String, x: Float, y: Float, internalPos: Int): this(DiagramElementId(id), x, y, x, y, false, internalPos) override fun copyAndTranslate(dx: Float, dy: Float): WaypointElementState { - return WaypointElementState(id, x + dx, y + dy, origX, origY, physical) + return WaypointElementState(id, x + dx, y + dy, origX, origY, physical, internalPos) } override fun moveTo(dx: Float, dy: Float): WaypointElementState { - return WaypointElementState(id, x + dx, y + dy, x + dx, y + dy, physical) + return WaypointElementState(id, x + dx, y + dy, x + dx, y + dy, physical, internalPos) } override fun asPhysical(): WaypointElementState { - return WaypointElementState(id, x, y, origX, origY, true) + return WaypointElementState(id, x, y, origX, origY, true, internalPos) } override fun originalLocation(): WaypointElementState { - return WaypointElementState(id, origX, origY, origX, origY, true) + return WaypointElementState(id, origX, origY, origX, origY, true, internalPos) } override fun asWaypointElement(): WaypointElement { diff --git a/flowable-xml-parser/build.gradle b/flowable-xml-parser/build.gradle index 5d4228b6c..7dda70735 100644 --- a/flowable-xml-parser/build.gradle +++ b/flowable-xml-parser/build.gradle @@ -29,7 +29,7 @@ apply plugin: 'kotlin-kapt' dependencies { api project(":xml-parser-api") - implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.1" + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.10.1' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.10.1' api 'com.github.pozo:mapstruct-kotlin:1.3.1.2' 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 a82aac1ad..cbbaa1d64 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 @@ -10,14 +10,35 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.readValue import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnParser import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObject +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.* import com.valb3r.bpmn.intellij.plugin.flowable.parser.nodes.BpmnFile +import org.w3c.dom.Document +import org.w3c.dom.Node +import java.io.ByteArrayInputStream import java.io.InputStream +import java.io.OutputStream +import java.io.StringWriter +import java.nio.charset.StandardCharsets.UTF_8 +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult +import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathFactory + const val CDATA_FIELD = "CDATA" class FlowableParser : BpmnParser { + val OMGDI_NS = "http://www.omg.org/spec/DD/20100524/DI" + private val mapper: XmlMapper = mapper() + private val dbFactory = DocumentBuilderFactory.newInstance() + private val transformer = TransformerFactory.newInstance() + private val xpathFactory = XPathFactory.newInstance() + override fun parse(input: String): BpmnProcessObject { val dto = mapper.readValue(input) @@ -29,6 +50,121 @@ class FlowableParser : BpmnParser { return toProcessObject(dto) } + /** + * Impossible to use FasterXML - Multiple objects of same type issue: + * https://github.com/FasterXML/jackson-dataformat-xml/issues/205 + */ + override fun update(input: InputStream, output: OutputStream, events: List){ + val dBuilder = dbFactory.newDocumentBuilder() + val doc = dBuilder.parse(input) + doc.documentElement.normalize() + + doUpdate(doc, events) + + transformer.newTransformer().transform(DOMSource(doc), StreamResult(output)) + } + + /** + * Impossible to use FasterXML - Multiple objects of same type issue: + * https://github.com/FasterXML/jackson-dataformat-xml/issues/205 + */ + override fun update(input: String, events: List): String { + val dBuilder = dbFactory.newDocumentBuilder() + val doc = dBuilder.parse(ByteArrayInputStream(input.toByteArray(UTF_8))) + doc.documentElement.normalize() + + doUpdate(doc, events) + + val writer = StringWriter() + val transformer = transformer.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.transform(DOMSource(doc), StreamResult(writer)) + return writer.buffer.toString() + } + + private fun doUpdate(doc: Document, events: List) { + for (event in events) { + when (event) { + is LocationUpdateWithId -> applyLocationUpdate(doc, event) + is NewWaypoints -> applyNewWaypoints(doc, event) + is DiagramElementRemoved -> applyDiagramElementRemoved(doc, event) + is BpmnElementRemoved -> applyBpmnElementRemoved(doc, event) + is BpmnShapeObjectAdded -> applyBpmnShapeObjectAdded(doc, event) + is BpmnEdgeObjectAdded -> applyBpmnEdgeObjectAdded(doc, event) + is PropertyUpdateWithId -> applyPropertyUpdateWithId(doc, event) + } + } + } + + private fun applyLocationUpdate(doc: Document, update: LocationUpdateWithId) { + val xpath = xpathFactory.newXPath() + val node = xpath.evaluate( + """ + //BPMNShape[@id='${update.diagramElementId.id}']/*[@x][@y][${update.internalPos!! + 1}] + | //BPMNEdge[@id='${update.diagramElementId.id}']/*[@x][@y][${update.internalPos!! + 1}] + """, + doc, + XPathConstants.NODE + ) as Node + + val nx = node.attributes.getNamedItem("x") + val ny = node.attributes.getNamedItem("y") + + nx.nodeValue = (nx.nodeValue.toFloat() + update.dx).toString() + ny.nodeValue = (ny.nodeValue.toFloat() + update.dy).toString() + } + + private fun applyNewWaypoints(doc: Document, update: NewWaypoints) { + val xpath = xpathFactory.newXPath() + val node = xpath.evaluate( + "//BPMNEdge[@id='${update.edgeElementId.id}'][1]", + doc, + XPathConstants.NODE + ) as Node + + val toRemove = mutableListOf() + var wasRemoved = false + for (pos in 0 until node.childNodes.length) { + val target = node.childNodes.item(pos) + if (target.nodeName.contains("waypoint")) { + toRemove.add(target) + wasRemoved = true + continue + } + + if (wasRemoved && "#text" == target.nodeName) { + toRemove.add(target) + } + + wasRemoved = false + } + + toRemove.forEach { node.removeChild(it) } + + update.waypoints.filter { it.physical }.sortedBy { it.internalPos }.forEach { + val elem = doc.createElementNS(OMGDI_NS, "omgdi:waypoint") + elem.setAttribute("x", it.x.toString()) + elem.setAttribute("y", it.y.toString()) + node.appendChild(elem) + } + } + + private fun applyDiagramElementRemoved(doc: Document, update: DiagramElementRemoved) { + } + + private fun applyBpmnElementRemoved(doc: Document, update: BpmnElementRemoved) { + } + + private fun applyBpmnShapeObjectAdded(doc: Document, update: BpmnShapeObjectAdded) { + } + + private fun applyBpmnEdgeObjectAdded(doc: Document, update: BpmnEdgeObjectAdded) { + } + + private fun applyPropertyUpdateWithId(doc: Document, update: PropertyUpdateWithId) { + } + private fun toProcessObject(dto: BpmnFile): BpmnProcessObject { // TODO - Multi process support val process = dto.processes[0].toElement() diff --git a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt index 195deded3..d7cdc4d34 100644 --- a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt +++ b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt @@ -1,9 +1,15 @@ package com.valb3r.bpmn.intellij.plugin.flowable.parser import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObject +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WaypointElement +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.LocationUpdateWithId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.NewWaypoints import org.amshove.kluent.shouldNotBeNull import org.junit.jupiter.api.Test import java.nio.charset.StandardCharsets.UTF_8 +import java.util.* internal class FlowableParserTest { @@ -17,5 +23,81 @@ internal class FlowableParserTest { processObject.shouldNotBeNull() } + @Test + fun `XML process with interlaced elements of same type should be parseable without error`() { + val processObject: BpmnProcessObject? + + processObject = FlowableParser().parse("duplicates.bpmn20.xml".asResource()!!) + + processObject.shouldNotBeNull() + } + + @Test + fun `XML process with interlaced elements of same type should be updatable with location without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: LocationUpdateWithId { + override val diagramElementId: DiagramElementId + get() = DiagramElementId("BPMNEdge_sid-F270B5BF-127E-422B-BF8D-6A6B7E411D31") + override val dx: Float + get() = 10.0f + override val dy: Float + get() = -10.0f + override val internalPos: Int? + get() = 0 + } + )) + + updated.shouldNotBeNull() + } + + @Test + fun `XML process with interlaced elements of same type should be updatable with new waypoints without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: NewWaypoints { + override val edgeElementId: DiagramElementId + get() = DiagramElementId("BPMNEdge_sid-C3BC8962-12B0-482B-B9B5-DCB6551306BD") + override val waypoints: List + get() = listOf( + WaypointElementState(DiagramElementId(UUID.randomUUID().toString()), 10.0f, 20.0f, 0.0f, 0.0f, true, 1), + WaypointElementState(DiagramElementId(UUID.randomUUID().toString()), 30.0f, 40.0f, 0.0f, 0.0f, true, 0) + ) + } + )) + + updated.shouldNotBeNull() + } + + fun String.asResource(): String? = object {}::class.java.classLoader.getResource(this)?.readText(UTF_8) +} + +data class WaypointElementState ( + override val id: DiagramElementId, + override val x: Float, + override val y: Float, + override val origX: Float, + override val origY: Float, + override val physical: Boolean, + override val internalPos: Int +): IdentifiableWaypoint { + + override fun moveTo(dx: Float, dy: Float): IdentifiableWaypoint { + TODO("Not yet implemented") + } + + override fun asPhysical(): IdentifiableWaypoint { + TODO("Not yet implemented") + } + + override fun originalLocation(): IdentifiableWaypoint { + TODO("Not yet implemented") + } + + override fun asWaypointElement(): WaypointElement { + TODO("Not yet implemented") + } + + override fun copyAndTranslate(dx: Float, dy: Float): IdentifiableWaypoint { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/flowable-xml-parser/src/test/resources/duplicates.bpmn20.xml b/flowable-xml-parser/src/test/resources/duplicates.bpmn20.xml new file mode 100644 index 000000000..883011e0c --- /dev/null +++ b/flowable-xml-parser/src/test/resources/duplicates.bpmn20.xml @@ -0,0 +1,59 @@ + + + + Testing duplicates + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/BpmnParser.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/BpmnParser.kt index 7c9edee2a..67c7b24fb 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/BpmnParser.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/BpmnParser.kt @@ -1,9 +1,14 @@ package com.valb3r.bpmn.intellij.plugin.bpmn.api +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.Event import java.io.InputStream +import java.io.OutputStream interface BpmnParser { fun parse(input: InputStream): BpmnProcessObject fun parse(input: String): BpmnProcessObject + + fun update(input: String, events: List): String + fun update(input: InputStream, output: OutputStream, events: List) } \ No newline at end of file diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt index 95934a108..5737b8ef2 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt @@ -20,6 +20,7 @@ interface LocationUpdateWithId: Event { val diagramElementId: DiagramElementId val dx: Float val dy: Float + val internalPos: Int? } interface NewWaypoints: Event { @@ -58,6 +59,7 @@ interface IdentifiableWaypoint: Translatable, WithDiagramI val origX: Float val origY: Float val physical: Boolean + val internalPos: Int fun moveTo(dx: Float, dy: Float): IdentifiableWaypoint fun asPhysical(): IdentifiableWaypoint From 70429560eff12e082c7521e6a104d8ebd4b82245 Mon Sep 17 00:00:00 2001 From: valb3r Date: Thu, 14 May 2020 13:02:19 +0300 Subject: [PATCH 03/10] FBP-6. BPMN objects persistence added --- .../flowable/parser/FlowableObjectFactory.kt | 6 +- .../plugin/flowable/parser/FlowableParser.kt | 197 +++++++++++++++--- .../flowable/parser/FlowableParserTest.kt | 98 ++++++++- .../plugin/bpmn/api/info/PropertyType.kt | 16 +- 4 files changed, 278 insertions(+), 39 deletions(-) diff --git a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt index acc2c6c81..96df9af4e 100644 --- a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt +++ b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt @@ -97,12 +97,12 @@ class FlowableObjectFactory: BpmnObjectFactory { val propertyTree = mapper.valueToTree(dto) for (type in PropertyType.values()) { - if (type.id.contains(".")) { + if (type.path.contains(".")) { tryParseNestedValue(type, propertyTree, result) continue } - propertyTree[type.id]?.apply { + propertyTree[type.path]?.apply { parseValue(result, type) } } @@ -111,7 +111,7 @@ class FlowableObjectFactory: BpmnObjectFactory { } private fun tryParseNestedValue(type: PropertyType, propertyTree: JsonNode, result: MutableMap) { - val split = type.id.split(".", limit = 2) + val split = type.path.split(".", limit = 2) val targetId = split[0] propertyTree[targetId]?.apply { if (split[1].contains(".")) { 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 cbbaa1d64..3f9b3dccc 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 @@ -10,14 +10,17 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.readValue import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnParser import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObject +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.* import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.* +import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property +import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType +import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyValueType +import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyValueType.* import com.valb3r.bpmn.intellij.plugin.flowable.parser.nodes.BpmnFile import org.w3c.dom.Document +import org.w3c.dom.Element import org.w3c.dom.Node -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.io.OutputStream -import java.io.StringWriter +import java.io.* import java.nio.charset.StandardCharsets.UTF_8 import javax.xml.parsers.DocumentBuilderFactory import javax.xml.transform.OutputKeys @@ -33,6 +36,8 @@ const val CDATA_FIELD = "CDATA" class FlowableParser : BpmnParser { val OMGDI_NS = "http://www.omg.org/spec/DD/20100524/DI" + val BPMDI_NS = "http://www.omg.org/spec/BPMN/20100524/DI" + val OMGDC_NS = "http://www.omg.org/spec/DD/20100524/DC" private val mapper: XmlMapper = mapper() private val dbFactory = DocumentBuilderFactory.newInstance() @@ -57,11 +62,8 @@ class FlowableParser : BpmnParser { override fun update(input: InputStream, output: OutputStream, events: List){ val dBuilder = dbFactory.newDocumentBuilder() val doc = dBuilder.parse(input) - doc.documentElement.normalize() - - doUpdate(doc, events) - transformer.newTransformer().transform(DOMSource(doc), StreamResult(output)) + parseAndWrite(doc, OutputStreamWriter(output), events) } /** @@ -71,16 +73,20 @@ class FlowableParser : BpmnParser { override fun update(input: String, events: List): String { val dBuilder = dbFactory.newDocumentBuilder() val doc = dBuilder.parse(ByteArrayInputStream(input.toByteArray(UTF_8))) + + return parseAndWrite(doc, StringWriter(), events).buffer.toString() + } + + private fun parseAndWrite(doc: Document, writer: T, events: List): T { doc.documentElement.normalize() doUpdate(doc, events) - val writer = StringWriter() - val transformer = transformer.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + val transformer = transformer.newTransformer() + transformer.setOutputProperty(OutputKeys.INDENT, "yes") + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4") transformer.transform(DOMSource(doc), StreamResult(writer)) - return writer.buffer.toString() + return writer } private fun doUpdate(doc: Document, events: List) { @@ -97,6 +103,20 @@ class FlowableParser : BpmnParser { } } + fun trimWhitespace(node: Node, recurse: Boolean = true) { + val children = node.childNodes + val toRemove = mutableListOf() + for (i in 0 until children.length) { + val child = children.item(i) + if (child.nodeType == Node.TEXT_NODE && child.textContent.trim().isBlank()) { + toRemove.add(child) + } else if (recurse) { + trimWhitespace(child) + } + } + toRemove.forEach { it.parentNode.removeChild(it) } + } + private fun applyLocationUpdate(doc: Document, update: LocationUpdateWithId) { val xpath = xpathFactory.newXPath() val node = xpath.evaluate( @@ -124,47 +144,174 @@ class FlowableParser : BpmnParser { ) as Node val toRemove = mutableListOf() - var wasRemoved = false for (pos in 0 until node.childNodes.length) { val target = node.childNodes.item(pos) if (target.nodeName.contains("waypoint")) { toRemove.add(target) - wasRemoved = true continue } - - if (wasRemoved && "#text" == target.nodeName) { - toRemove.add(target) - } - - wasRemoved = false } toRemove.forEach { node.removeChild(it) } + trimWhitespace(node) update.waypoints.filter { it.physical }.sortedBy { it.internalPos }.forEach { - val elem = doc.createElementNS(OMGDI_NS, "omgdi:waypoint") - elem.setAttribute("x", it.x.toString()) - elem.setAttribute("y", it.y.toString()) - node.appendChild(elem) + newWaypoint(doc, it, node) } } + private fun newWaypoint(doc: Document, it: IdentifiableWaypoint, parentEdgeElem: Node) { + val elem = doc.createElementNS(OMGDI_NS, "omgdi:waypoint") + elem.setAttribute("x", it.x.toString()) + elem.setAttribute("y", it.y.toString()) + parentEdgeElem.appendChild(elem) + } + private fun applyDiagramElementRemoved(doc: Document, update: DiagramElementRemoved) { + val xpath = xpathFactory.newXPath() + val node = xpath.evaluate( + "//BPMNDiagram/BPMNPlane/*[@id='${update.elementId.id}'][1]", + doc, + XPathConstants.NODE + ) as Node + + val parent = node.parentNode + node.parentNode.removeChild(node) + trimWhitespace(parent, false) } private fun applyBpmnElementRemoved(doc: Document, update: BpmnElementRemoved) { + val xpath = xpathFactory.newXPath() + val node = xpath.evaluate( + "//process/*[@id='${update.elementId.id}'][1]", + doc, + XPathConstants.NODE + ) as Node + + val parent = node.parentNode + node.parentNode.removeChild(node) + trimWhitespace(parent, false) } private fun applyBpmnShapeObjectAdded(doc: Document, update: BpmnShapeObjectAdded) { + val xpath = xpathFactory.newXPath() + val diagramParent = xpath.evaluate( + "//process[1]", + doc, + XPathConstants.NODE + ) as Node + + val newNode = when(update.bpmnObject) { + is BpmnStartEvent -> doc.createElement("startEvent") + is BpmnCallActivity -> doc.createElement("callActivity") + is BpmnExclusiveGateway -> doc.createElement("exclusiveGateway") + is BpmnSequenceFlow -> doc.createElement("sequenceFlow") + is BpmnServiceTask -> doc.createElement("serviceTask") + is BpmnEndEvent -> doc.createElement("endEvent") + else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) + } + + update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value) } + trimWhitespace(diagramParent, false) + diagramParent.appendChild(newNode) + + val shapeParent = xpath.evaluate( + "//BPMNDiagram/BPMNPlane[1]", + doc, + XPathConstants.NODE + ) as Node + val newShape = doc.createElementNS(BPMDI_NS, "bpmndi:BPMNShape") + newShape.setAttribute("id", update.shape.id.id) + newShape.setAttribute("bpmnElement", update.bpmnObject.id.id) + shapeParent.appendChild(newShape) + val newBounds = doc.createElementNS(OMGDC_NS, "omgdc:Bounds") + newBounds.setAttribute("x", update.shape.bounds.x.toString()) + newBounds.setAttribute("y", update.shape.bounds.y.toString()) + newBounds.setAttribute("width", update.shape.bounds.width.toString()) + newBounds.setAttribute("height", update.shape.bounds.height.toString()) + newShape.appendChild(newBounds) + trimWhitespace(shapeParent, false) } private fun applyBpmnEdgeObjectAdded(doc: Document, update: BpmnEdgeObjectAdded) { + val xpath = xpathFactory.newXPath() + val diagramParent = xpath.evaluate( + "//process[1]", + doc, + XPathConstants.NODE + ) as Node + + val newNode = when(update.bpmnObject) { + is BpmnSequenceFlow -> doc.createElement("sequenceFlow") + else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) + } + + update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value) } + trimWhitespace(diagramParent, false) + diagramParent.appendChild(newNode) + + val shapeParent = xpath.evaluate( + "//BPMNDiagram/BPMNPlane[1]", + doc, + XPathConstants.NODE + ) as Node + val newShape = doc.createElementNS(BPMDI_NS, "bpmndi:BPMNEdge") + newShape.setAttribute("id", update.edge.id.id) + newShape.setAttribute("bpmnElement", update.bpmnObject.id.id) + shapeParent.appendChild(newShape) + update.edge.waypoint.filter { it.physical }.forEach { newWaypoint(doc, it, newShape) } + trimWhitespace(shapeParent, false) } private fun applyPropertyUpdateWithId(doc: Document, update: PropertyUpdateWithId) { } + private fun setPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Property) { + when { + type.xmlPath.contains(".") -> setNestedPropertyToNode(doc, node, type, value) + else -> node.setAttribute(type.xmlPath, asString(type.valueType, value)) + } + } + + private fun setNestedPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Property) { + val segments = type.xmlPath.split(".") + val childOf: ((Element, String) -> Element?) = child@{target, name -> + for (pos in 0 until target.childNodes.length) { + if (target.childNodes.item(pos).nodeName.contains(name)) { + return@child target.childNodes.item(pos) as Element + } + } + return@child null + } + + var currentNode = node + for (segment in 0 until segments.size - 1) { + val name = segments[segment] + val child = childOf(currentNode, name) + if (null == child) { + val newElem = doc.createElement(name) + currentNode.appendChild(newElem) + currentNode = newElem + } else { + currentNode = child + } + } + + if (type.isCdata) { + val cdata = doc.createCDATASection(asString(type.valueType, value)) + currentNode.appendChild(cdata) + } else { + currentNode.setAttribute(segments[segments.size - 1], asString(type.valueType, value)) + } + } + + private fun asString(type: PropertyValueType, property: Property): String { + return when(type) { + STRING, CLASS, EXPRESSION -> property.value as String + BOOLEAN -> (property.value as Boolean).toString() + } + } + private fun toProcessObject(dto: BpmnFile): BpmnProcessObject { // TODO - Multi process support val process = dto.processes[0].toElement() diff --git a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt index d7cdc4d34..dbc4574d8 100644 --- a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt +++ b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt @@ -1,11 +1,17 @@ package com.valb3r.bpmn.intellij.plugin.flowable.parser import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObject +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.BpmnExclusiveGateway +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.BpmnSequenceFlow +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.WithBpmnId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.BoundsElement +import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.ShapeElement import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WaypointElement -import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint -import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.LocationUpdateWithId -import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.NewWaypoints +import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.* +import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property +import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType import org.amshove.kluent.shouldNotBeNull import org.junit.jupiter.api.Test import java.nio.charset.StandardCharsets.UTF_8 @@ -14,6 +20,9 @@ import java.util.* internal class FlowableParserTest { + private val bmpnElemId = BpmnElementId(UUID.randomUUID().toString()) + private val diagramElementId = DiagramElementId(UUID.randomUUID().toString()) + @Test fun `XML process with all Flowable elements is parseable without error`() { val processObject: BpmnProcessObject? @@ -58,8 +67,79 @@ internal class FlowableParserTest { get() = DiagramElementId("BPMNEdge_sid-C3BC8962-12B0-482B-B9B5-DCB6551306BD") override val waypoints: List get() = listOf( - WaypointElementState(DiagramElementId(UUID.randomUUID().toString()), 10.0f, 20.0f, 0.0f, 0.0f, true, 1), - WaypointElementState(DiagramElementId(UUID.randomUUID().toString()), 30.0f, 40.0f, 0.0f, 0.0f, true, 0) + WaypointElementState(diagramElementId, 10.0f, 20.0f, 0.0f, 0.0f, true, 1), + WaypointElementState(diagramElementId, 30.0f, 40.0f, 0.0f, 0.0f, true, 0) + ) + } + )) + + updated.shouldNotBeNull() + } + + @Test + fun `XML process with interlaced elements of same type should be updatable with diagram element removal without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: DiagramElementRemoved { + override val elementId: DiagramElementId + get() = DiagramElementId("BPMNEdge_sid-C3BC8962-12B0-482B-B9B5-DCB6551306BD") + } + )) + + updated.shouldNotBeNull() + } + + @Test + fun `XML process with interlaced elements of same type should be updatable with BPMN element removal without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: BpmnElementRemoved { + override val elementId: BpmnElementId + get() = BpmnElementId("serviceTaskStart") + } + )) + + updated.shouldNotBeNull() + } + + @Test + fun `XML process with interlaced elements of same type should be updatable with BPMN element addition without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: BpmnShapeObjectAdded { + override val bpmnObject: WithBpmnId + get() = BpmnExclusiveGateway(bmpnElemId, "Exclusive gateway", null, null) + override val shape: ShapeElement + get() = ShapeElement(diagramElementId, bpmnObject.id, BoundsElement(0.0f, 0.0f, 30.0f, 40.0f)) + override val props: Map + get() = mapOf( + PropertyType.ID to Property(bpmnObject.id.id), + PropertyType.CONDITION_EXPR_VALUE to Property("condition 1"), + PropertyType.CONDITION_EXPR_TYPE to Property("a type") + ) + } + )) + + updated.shouldNotBeNull() + } + + @Test + fun `XML process with interlaced elements of same type should be updatable with BPMN edge element addition without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: BpmnEdgeObjectAdded { + override val bpmnObject: WithBpmnId + get() = BpmnSequenceFlow(bmpnElemId, "Exclusive gateway", null, "source", "target", null) + override val edge: EdgeWithIdentifiableWaypoints + get() = EdgeElementState( + diagramElementId, + bpmnObject.id, + mutableListOf( + WaypointElementState(diagramElementId, 10.0f, 20.0f, 0.0f, 0.0f, true, 1), + WaypointElementState(diagramElementId, 30.0f, 40.0f, 0.0f, 0.0f, true, 0) + ) + ) + override val props: Map + get() = mapOf( + PropertyType.ID to Property(bpmnObject.id.id), + PropertyType.CONDITION_EXPR_VALUE to Property("condition 1"), + PropertyType.CONDITION_EXPR_TYPE to Property("a type") ) } )) @@ -100,4 +180,10 @@ data class WaypointElementState ( override fun copyAndTranslate(dx: Float, dy: Float): IdentifiableWaypoint { TODO("Not yet implemented") } -} \ No newline at end of file +} + +data class EdgeElementState ( + override val id: DiagramElementId, + override val bpmnElement: BpmnElementId?, + override val waypoint: MutableList = mutableListOf() +): EdgeWithIdentifiableWaypoints \ No newline at end of file diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt index b4067f6df..b4f00609f 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt @@ -2,9 +2,15 @@ package com.valb3r.bpmn.intellij.plugin.bpmn.api.info import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyValueType.* -enum class PropertyType(val id: String, val caption: String, val valueType: PropertyValueType) { - - ID("id.id", "ID", STRING), +enum class PropertyType( + val id: String, + val caption: String, + val valueType: PropertyValueType, + val path: String = id, + val xmlPath: String = path, + val isCdata: Boolean = false +) { + ID("id", "ID", STRING, "id.id", "id"), NAME("name", "Name", STRING), DOCUMENTATION("documentation", "Documentation", STRING), ASYNC("async", "Asynchronous", BOOLEAN), @@ -17,7 +23,7 @@ enum class PropertyType(val id: String, val caption: String, val valueType: Prop IS_TRIGGERABLE("triggerable", "Is activity triggerable?", BOOLEAN), SOURCE_REF("sourceRef","Source reference", STRING), TARGET_REF("targetRef", "Target reference", STRING), - CONDITION_EXPR_VALUE("conditionExpression.text", "Condition expression", EXPRESSION), - CONDITION_EXPR_TYPE("conditionExpression.type", "Condition expression type", STRING), + CONDITION_EXPR_VALUE("conditionExpression", "Condition expression", EXPRESSION, "conditionExpression.text", "conditionExpression.text", true), + CONDITION_EXPR_TYPE("conditionExpression", "Condition expression type", STRING, "conditionExpression.type", "conditionExpression.xsi:type"), DEFAULT_FLOW("defaultElement", "Default flow element", STRING) } \ No newline at end of file From a625306d52ffb4dbd347c3b748dd8d3fce2572ed Mon Sep 17 00:00:00 2001 From: valb3r Date: Thu, 14 May 2020 13:12:45 +0300 Subject: [PATCH 04/10] FBP-6. Complete update events coverage --- .../bpmn/intellij/plugin/events/Events.kt | 4 +-- .../plugin/flowable/parser/FlowableParser.kt | 27 +++++++++++++------ .../flowable/parser/FlowableParserTest.kt | 16 +++++++++++ .../plugin/bpmn/api/events/EventInterfaces.kt | 1 + 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt index 6b10d1d72..a7e11ffb6 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt @@ -8,9 +8,9 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.* import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType -data class StringValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, val newValue: String): PropertyUpdateWithId +data class StringValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, override val newValue: String): PropertyUpdateWithId -data class BooleanValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, val newValue: Boolean): PropertyUpdateWithId +data class BooleanValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, override val newValue: Boolean): PropertyUpdateWithId data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float, override val internalPos: Int?): LocationUpdateWithId 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 3f9b3dccc..121dc1847 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 @@ -12,7 +12,6 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnParser import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObject import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.* import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.* -import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.Property import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyType import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyValueType import com.valb3r.bpmn.intellij.plugin.bpmn.api.info.PropertyValueType.* @@ -211,7 +210,7 @@ class FlowableParser : BpmnParser { else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) } - update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value) } + update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value.value) } trimWhitespace(diagramParent, false) diagramParent.appendChild(newNode) @@ -246,7 +245,7 @@ class FlowableParser : BpmnParser { else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) } - update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value) } + update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value.value) } trimWhitespace(diagramParent, false) diagramParent.appendChild(newNode) @@ -264,16 +263,24 @@ class FlowableParser : BpmnParser { } private fun applyPropertyUpdateWithId(doc: Document, update: PropertyUpdateWithId) { + val xpath = xpathFactory.newXPath() + val node = xpath.evaluate( + "//process/*[@id='${update.bpmnElementId.id}'][1]", + doc, + XPathConstants.NODE + ) as Element + + setPropertyToNode(doc, node, update.property, asString(update.property.valueType, update.newValue)) } - private fun setPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Property) { + private fun setPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Any?) { when { type.xmlPath.contains(".") -> setNestedPropertyToNode(doc, node, type, value) else -> node.setAttribute(type.xmlPath, asString(type.valueType, value)) } } - private fun setNestedPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Property) { + private fun setNestedPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Any?) { val segments = type.xmlPath.split(".") val childOf: ((Element, String) -> Element?) = child@{target, name -> for (pos in 0 until target.childNodes.length) { @@ -305,10 +312,14 @@ class FlowableParser : BpmnParser { } } - private fun asString(type: PropertyValueType, property: Property): String { + private fun asString(type: PropertyValueType, value: Any?): String? { + if (null == value) { + return null + } + return when(type) { - STRING, CLASS, EXPRESSION -> property.value as String - BOOLEAN -> (property.value as Boolean).toString() + STRING, CLASS, EXPRESSION -> value as String + BOOLEAN -> (value as Boolean).toString() } } diff --git a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt index dbc4574d8..38c30af4f 100644 --- a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt +++ b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt @@ -148,6 +148,22 @@ internal class FlowableParserTest { } + @Test + fun `XML process with interlaced elements of same type should be updatable with property update event without error`() { + val updated = FlowableParser().update("duplicates.bpmn20.xml".asResource()!!, listOf( + object: PropertyUpdateWithId { + override val bpmnElementId: BpmnElementId + get() = BpmnElementId("activityStart") + override val property: PropertyType + get() = PropertyType.NAME + override val newValue: Any + get() = "A new name" + } + )) + + updated.shouldNotBeNull() + } + fun String.asResource(): String? = object {}::class.java.classLoader.getResource(this)?.readText(UTF_8) } diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt index 5737b8ef2..f96a89708 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt @@ -51,6 +51,7 @@ interface BpmnEdgeObjectAdded: Event { interface PropertyUpdateWithId: Event { val bpmnElementId: BpmnElementId val property: PropertyType + val newValue: Any } interface IdentifiableWaypoint: Translatable, WithDiagramId { From 4f77e696295a27effa8b250d40d0ac9bfa86f350 Mon Sep 17 00:00:00 2001 From: valb3r Date: Thu, 14 May 2020 17:42:41 +0300 Subject: [PATCH 05/10] FBP-6. Fixed issues with gateway serialization --- .../bpmn/intellij/plugin/CanvasBuilder.kt | 8 ++-- .../bpmn/intellij/plugin/events/Events.kt | 2 +- .../plugin/events/ProcessModelUpdateEvents.kt | 39 ++++++++++++---- .../plugin/properties/PropertiesVisualizer.kt | 39 ++++++++-------- .../plugin/render/BpmnProcessRenderer.kt | 4 +- .../bpmn/intellij/plugin/render/Canvas.kt | 7 ++- .../intellij/plugin/state/CurrentState.kt | 26 +++++------ .../plugin/flowable/parser/FlowableParser.kt | 46 +++++++++++++------ .../plugin/bpmn/api/events/EventInterfaces.kt | 1 + 9 files changed, 107 insertions(+), 65 deletions(-) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/CanvasBuilder.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/CanvasBuilder.kt index 5fad095dd..7ff0efc29 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/CanvasBuilder.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/CanvasBuilder.kt @@ -2,6 +2,7 @@ package com.valb3r.bpmn.intellij.plugin import com.intellij.psi.PsiFile import com.intellij.ui.EditorTextField +import com.valb3r.bpmn.intellij.plugin.events.setUpdateEventsRegistry import com.valb3r.bpmn.intellij.plugin.events.updateEventsRegistry import com.valb3r.bpmn.intellij.plugin.flowable.parser.FlowableObjectFactory import com.valb3r.bpmn.intellij.plugin.flowable.parser.FlowableParser @@ -13,13 +14,14 @@ import javax.swing.JTable class CanvasBuilder { - private val updateEvents = updateEventsRegistry() private var newObjectsFactory: NewElementsProvider? = null fun build(properties: JTable, editorFactory: (value: String) -> EditorTextField, canvas: Canvas, bpmnFile: PsiFile) { - updateEvents.reset() + val parser = FlowableParser() + setUpdateEventsRegistry(parser, bpmnFile.virtualFile) + updateEventsRegistry().reset() bpmnFile.virtualFile.inputStream.use { - val process = FlowableParser().parse(it) + val process = parser.parse(it) newObjectsFactory = newElementsFactory(FlowableObjectFactory()) canvas.reset(properties, editorFactory, process.toView(newObjectsFactory!!), BpmnProcessRenderer()) } diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt index a7e11ffb6..59b1ed3ab 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt @@ -12,7 +12,7 @@ data class StringValueUpdatedEvent(override val bpmnElementId: BpmnElementId, ov data class BooleanValueUpdatedEvent(override val bpmnElementId: BpmnElementId, override val property: PropertyType, override val newValue: Boolean): PropertyUpdateWithId -data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float, override val internalPos: Int?): LocationUpdateWithId +data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float, override val parentElementId: DiagramElementId?, override val internalPos: Int?): LocationUpdateWithId data class NewWaypointsEvent(override val edgeElementId: DiagramElementId, override val waypoints: List): NewWaypoints diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt index c1a6153cf..9741b7185 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/ProcessModelUpdateEvents.kt @@ -1,11 +1,16 @@ package com.valb3r.bpmn.intellij.plugin.events +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.vfs.VirtualFile +import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnParser import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.Event import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EventOrder import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.LocationUpdateWithId import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.PropertyUpdateWithId +import java.io.ByteArrayOutputStream +import java.io.IOException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicLong @@ -13,18 +18,16 @@ import java.util.concurrent.atomic.AtomicReference private val updateEvents = AtomicReference() -fun updateEventsRegistry(): ProcessModelUpdateEvents { - return updateEvents.updateAndGet { - if (null == it) { - return@updateAndGet ProcessModelUpdateEvents(CopyOnWriteArrayList()) - } +fun setUpdateEventsRegistry(parser: BpmnParser, file: VirtualFile) { + updateEvents.set(ProcessModelUpdateEvents(parser, file, CopyOnWriteArrayList())) +} - return@updateAndGet it - } +fun updateEventsRegistry(): ProcessModelUpdateEvents { + return updateEvents.get()!! } // Global singleton -class ProcessModelUpdateEvents(private val updates: MutableList>) { +class ProcessModelUpdateEvents(private val parser: BpmnParser, private val file: VirtualFile, private val updates: MutableList>) { private val order: AtomicLong = AtomicLong() private val fileCommitListeners: MutableList = ArrayList() @@ -50,48 +53,68 @@ class ProcessModelUpdateEvents(private val updates: MutableList } fun commitToFile() { + val lastCommit = updates.filter { it.event is CommittedToFile }.maxBy { it.order } + val bos = ByteArrayOutputStream() + file.inputStream.use {input -> + parser.update(input, bos, updates.filter { it.order > (lastCommit?.order ?: -1) }.map { it.event }) + } + val toStore = Order(order.getAndIncrement(), CommittedToFile(0)) + updates.add(toStore) + + WriteAction.run { + file.getOutputStream(null).use { + it.write(bos.toByteArray()) + } + } } fun addPropertyUpdateEvent(event: PropertyUpdateWithId) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) propertyUpdatesByStaticId.computeIfAbsent(event.bpmnElementId) { CopyOnWriteArrayList() } += toStore + commitToFile() } fun addLocationUpdateEvent(event: LocationUpdateWithId) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) locationUpdatesByStaticId.computeIfAbsent(event.diagramElementId) { CopyOnWriteArrayList() } += toStore + commitToFile() } fun addWaypointStructureUpdate(event: NewWaypointsEvent) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) parentCreatesByStaticId.computeIfAbsent(event.edgeElementId) { CopyOnWriteArrayList() } += toStore + commitToFile() } fun addElementRemovedEvent(event: DiagramElementRemovedEvent) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) deletionsByStaticId.computeIfAbsent(event.elementId) { CopyOnWriteArrayList() } += toStore + commitToFile() } fun addElementRemovedEvent(event: BpmnElementRemovedEvent) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) deletionsByStaticBpmnId.computeIfAbsent(event.elementId) { CopyOnWriteArrayList() } += toStore + commitToFile() } fun addObjectEvent(event: BpmnShapeObjectAddedEvent) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) newShapeElements.add(toStore) + commitToFile() } fun addObjectEvent(event: BpmnEdgeObjectAddedEvent) { val toStore = Order(order.getAndIncrement(), event) updates.add(toStore) newDiagramElements.add(toStore) + commitToFile() } fun currentPropertyUpdateEventList(elementId: BpmnElementId): List> { diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/properties/PropertiesVisualizer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/properties/PropertiesVisualizer.kt index 9d4dc7f4b..ef635cdf3 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/properties/PropertiesVisualizer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/properties/PropertiesVisualizer.kt @@ -11,15 +11,27 @@ import com.valb3r.bpmn.intellij.plugin.events.BooleanValueUpdatedEvent import com.valb3r.bpmn.intellij.plugin.events.StringValueUpdatedEvent import com.valb3r.bpmn.intellij.plugin.events.updateEventsRegistry import com.valb3r.bpmn.intellij.plugin.ui.components.FirstColumnReadOnlyModel -import java.awt.event.FocusEvent -import java.awt.event.FocusListener import javax.swing.JTable class PropertiesVisualizer(val table: JTable, val editorFactory: (value: String) -> EditorTextField) { - private val updateRegistry = updateEventsRegistry() private var listenersForCurrentView: MutableList<(() -> Unit)> = mutableListOf() + @Synchronized + fun clear() { + // fire de-focus to move changes to memory, component listeners doesn't seem to work with EditorTextField + listenersForCurrentView.forEach { it() } + listenersForCurrentView.clear() + + // drop and re-create table model + val model = FirstColumnReadOnlyModel() + model.addColumn("") + model.addColumn("") + table.model = model + table.columnModel.getColumn(1).preferredWidth = 500 + model.fireTableDataChanged() + } + @Synchronized fun visualize(bpmnElementId: BpmnElementId, properties: Map) { // fire de-focus to move changes to memory, component listeners doesn't seem to work with EditorTextField @@ -51,7 +63,7 @@ class PropertiesVisualizer(val table: JTable, val editorFactory: (value: String) listenersForCurrentView.add { if (initialValue != field.text) { - updateRegistry.addPropertyUpdateEvent(StringValueUpdatedEvent(bpmnElementId, type, field.text)) + updateEventsRegistry().addPropertyUpdateEvent(StringValueUpdatedEvent(bpmnElementId, type, field.text)) } } return field @@ -64,7 +76,7 @@ class PropertiesVisualizer(val table: JTable, val editorFactory: (value: String) listenersForCurrentView.add { if (initialValue != field.isSelected) { - updateRegistry.addPropertyUpdateEvent(StringValueUpdatedEvent(bpmnElementId, type, field.text)) + updateEventsRegistry().addPropertyUpdateEvent(BooleanValueUpdatedEvent(bpmnElementId, type, field.isSelected)) } } return field @@ -88,7 +100,7 @@ class PropertiesVisualizer(val table: JTable, val editorFactory: (value: String) val initialValue = field.text listenersForCurrentView.add { if (initialValue != field.text) { - updateRegistry.addPropertyUpdateEvent(StringValueUpdatedEvent(bpmnElementId, type, removeQuotes(field.text))) + updateEventsRegistry().addPropertyUpdateEvent(StringValueUpdatedEvent(bpmnElementId, type, removeQuotes(field.text))) } } } @@ -98,7 +110,7 @@ class PropertiesVisualizer(val table: JTable, val editorFactory: (value: String) } private fun lastStringValueFromRegistry(bpmnElementId: BpmnElementId, type: PropertyType): String? { - return (updateRegistry.currentPropertyUpdateEventList(bpmnElementId) + return (updateEventsRegistry().currentPropertyUpdateEventList(bpmnElementId) .map { it.event } .filter { it.property.id == type.id } .lastOrNull { it is StringValueUpdatedEvent } as StringValueUpdatedEvent?) @@ -106,21 +118,10 @@ class PropertiesVisualizer(val table: JTable, val editorFactory: (value: String) } private fun lastBooleanValueFromRegistry(bpmnElementId: BpmnElementId, type: PropertyType): Boolean? { - return (updateRegistry.currentPropertyUpdateEventList(bpmnElementId) + return (updateEventsRegistry().currentPropertyUpdateEventList(bpmnElementId) .map { it.event } .filter { it.property.id == type.id } .lastOrNull { it is BooleanValueUpdatedEvent } as BooleanValueUpdatedEvent?) ?.newValue } - - private class FocusEventListener(val onFocusLost: ((e: FocusEvent?) -> Unit)): FocusListener { - - override fun focusLost(e: FocusEvent?) { - onFocusLost(e) - } - - override fun focusGained(e: FocusEvent?) { - // NOP - } - } } \ No newline at end of file diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt index ef2f01f9d..192dac8be 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt @@ -88,7 +88,7 @@ class BpmnProcessRenderer { } val actionsElem = drawActionsElement(canvas, it, renderMeta.interactionContext, mutableMapOf(Actions.DELETE to deleteCallback, Actions.NEW_LINK to newSequenceCallback)) areaByElement += actionsElem - renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy, null))} + renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy, null, null))} } } } @@ -145,7 +145,7 @@ class BpmnProcessRenderer { val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: IdentifiableWaypoint -> if (elem.physical) { - dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy, elem.internalPos)) + dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy, parent.id, elem.internalPos)) } else { dest.addWaypointStructureUpdate(NewWaypointsEvent( parent.id, diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt index 5a9536770..b4b06aad6 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt @@ -30,7 +30,6 @@ class Canvas: JPanel() { private val defaultCameraOrigin = Point2D.Float(0f, 0f) private val defaultZoomRatio = 1f private val stateProvider = currentStateProvider() - private val updateEvents = updateEventsRegistry() private var selectedElements: MutableSet = mutableSetOf() private var camera = Camera(defaultCameraOrigin, Point2D.Float(defaultZoomRatio, defaultZoomRatio)) @@ -67,7 +66,7 @@ class Canvas: JPanel() { fun click(location: Point2D.Float) { val clickedElements = elemsUnderCursor(location) - clickedElements.forEach { interactionCtx.clickCallbacks.get(it)?.invoke(updateEvents) } + clickedElements.forEach { interactionCtx.clickCallbacks.get(it)?.invoke(updateEventsRegistry()) } this.selectedElements.clear() interactionCtx = ElementInteractionContext(mutableSetOf(), mutableMapOf(), mutableMapOf(), emptySet(), Point2D.Float(), Point2D.Float()) @@ -80,7 +79,7 @@ class Canvas: JPanel() { .elementByDiagramId[elementIdForPropertiesTable] ?.let { elemId -> stateProvider.currentState().elemPropertiesByStaticElementId[elemId]?.let { propertiesVisualizer?.visualize(elemId, it) } - } + } ?: propertiesVisualizer?.clear() } fun dragCanvas(start: Point2D.Float, current: Point2D.Float) { @@ -171,7 +170,7 @@ class Canvas: JPanel() { interactionCtx = attractToAnchors(interactionCtx) val dx = interactionCtx.current.x - interactionCtx.start.x val dy = interactionCtx.current.y - interactionCtx.start.y - interactionCtx.draggedIds.forEach { interactionCtx.dragEndCallbacks[it]?.invoke(dx, dy, updateEvents) } + interactionCtx.draggedIds.forEach { interactionCtx.dragEndCallbacks[it]?.invoke(dx, dy, updateEventsRegistry()) } } interactionCtx = interactionCtx.copy(draggedIds = emptySet()) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt index 0754bc657..c8123ae3b 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt @@ -36,8 +36,6 @@ data class CurrentState( // Global singleton class CurrentStateProvider { - - private val updateEvents = updateEventsRegistry() private var fileState = CurrentState(emptyList(), emptyList(), emptyMap(), emptyMap(), emptyMap()) private var currentState = CurrentState(emptyList(), emptyList(), emptyMap(), emptyMap(), emptyMap()) @@ -50,13 +48,13 @@ class CurrentStateProvider { processObject.elemPropertiesByElementId ) currentState = fileState - updateEvents.reset() + updateEventsRegistry().reset() } fun currentState(): CurrentState { - val newShapes = updateEvents.newShapeElements().map { it.event } - val newEdges = updateEvents.newEdgeElements().map { it.event } - val newEdgeElems = updateEvents.newEdgeElements().map { it.event } + val newShapes = updateEventsRegistry().newShapeElements().map { it.event } + val newEdges = updateEventsRegistry().newEdgeElements().map { it.event } + val newEdgeElems = updateEventsRegistry().newEdgeElements().map { it.event } val newElementByDiagramId: MutableMap = HashMap() val newElementByStaticId: MutableMap = HashMap() @@ -76,7 +74,7 @@ class CurrentStateProvider { val updatedProperties: MutableMap> = HashMap() allProperties.forEach { prop -> updatedProperties[prop.key] = prop.value.toMutableMap() - updateEvents.currentPropertyUpdateEventList(prop.key) + updateEventsRegistry().currentPropertyUpdateEventList(prop.key) .map { it.event } .filterIsInstance() .forEach { @@ -86,10 +84,10 @@ class CurrentStateProvider { return CurrentState( shapes = fileState.shapes.toList().union(newShapes.map { it.shape }) - .filter { !updateEvents.isDeleted(it.id) && !updateEvents.isDeleted(it.bpmnElement) } + .filter { !updateEventsRegistry().isDeleted(it.id) && !updateEventsRegistry().isDeleted(it.bpmnElement) } .map { updateLocationAndInnerTopology(it) }, edges = fileState.edges.toList().union(newEdgeElems.map { it.edge }) - .filter { !updateEvents.isDeleted(it.id) && !(it.bpmnElement?.let { updateEvents.isDeleted(it) } ?: false)} + .filter { !updateEventsRegistry().isDeleted(it.id) && !(it.bpmnElement?.let { updateEventsRegistry().isDeleted(it) } ?: false)} .map { updateLocationAndInnerTopology(it) }, elementByDiagramId = fileState.elementByDiagramId.toMutableMap().plus(newElementByDiagramId), elementByStaticId = fileState.elementByStaticId.toMutableMap().plus(newElementByStaticId), @@ -98,7 +96,7 @@ class CurrentStateProvider { } private fun updateLocationAndInnerTopology(elem: ShapeElement): ShapeElement { - val updates = updateEvents.currentLocationUpdateEventList(elem.id) + val updates = updateEventsRegistry().currentLocationUpdateEventList(elem.id) var dx = 0.0f var dy = 0.0f updates.forEach { dx += it.event.dx; dy += it.event.dy } @@ -106,20 +104,20 @@ class CurrentStateProvider { } private fun updateLocationAndInnerTopology(elem: EdgeWithIdentifiableWaypoints): EdgeWithIdentifiableWaypoints { - val hasNoCommittedAnchorUpdates = elem.waypoint.firstOrNull { updateEvents.currentLocationUpdateEventList(it.id).isNotEmpty() } - val hasNoNewAnchors = updateEvents.newWaypointStructure(elem.id).isEmpty() + val hasNoCommittedAnchorUpdates = elem.waypoint.firstOrNull { updateEventsRegistry().currentLocationUpdateEventList(it.id).isNotEmpty() } + val hasNoNewAnchors = updateEventsRegistry().newWaypointStructure(elem.id).isEmpty() if (null == hasNoCommittedAnchorUpdates && hasNoNewAnchors) { return elem } val waypoints: MutableList = - updateEvents.newWaypointStructure(elem.id).lastOrNull()?.event?.waypoints?.toMutableList() ?: elem.waypoint.filter { it.physical }.toMutableList() + updateEventsRegistry().newWaypointStructure(elem.id).lastOrNull()?.event?.waypoints?.toMutableList() ?: elem.waypoint.filter { it.physical }.toMutableList() val updatedLocations = waypoints.filter { it.physical }.map { updateWaypointLocation(it) } return EdgeElementState(elem, updatedLocations) } private fun updateWaypointLocation(waypoint: IdentifiableWaypoint): IdentifiableWaypoint { - val updates = updateEvents.currentLocationUpdateEventList(waypoint.id) + val updates = updateEventsRegistry().currentLocationUpdateEventList(waypoint.id) var dx = 0.0f var dy = 0.0f updates.forEach { dx += it.event.dx; dy += it.event.dy } 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 121dc1847..12850cccd 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 @@ -118,14 +118,20 @@ class FlowableParser : BpmnParser { private fun applyLocationUpdate(doc: Document, update: LocationUpdateWithId) { val xpath = xpathFactory.newXPath() - val node = xpath.evaluate( - """ - //BPMNShape[@id='${update.diagramElementId.id}']/*[@x][@y][${update.internalPos!! + 1}] - | //BPMNEdge[@id='${update.diagramElementId.id}']/*[@x][@y][${update.internalPos!! + 1}] - """, - doc, - XPathConstants.NODE - ) as Node + val node = if (null != update.internalPos) { + // Internal waypoint update + xpath.evaluate( + "//BPMNEdge[@id='${update.parentElementId!!.id}']/*[@x][@y][${update.internalPos!! + 1}]", + doc, + XPathConstants.NODE + ) as Node + } else { + xpath.evaluate( + "//BPMNShape[@id='${update.diagramElementId.id}']/*[@x][@y]", + doc, + XPathConstants.NODE + ) as Node + } val nx = node.attributes.getNamedItem("x") val ny = node.attributes.getNamedItem("y") @@ -270,13 +276,13 @@ class FlowableParser : BpmnParser { XPathConstants.NODE ) as Element - setPropertyToNode(doc, node, update.property, asString(update.property.valueType, update.newValue)) + setPropertyToNode(doc, node, update.property, update.newValue) } private fun setPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Any?) { when { type.xmlPath.contains(".") -> setNestedPropertyToNode(doc, node, type, value) - else -> node.setAttribute(type.xmlPath, asString(type.valueType, value)) + else -> setOrRemoveIfNull(node, type.xmlPath, asString(type.valueType, value)) } } @@ -305,15 +311,27 @@ class FlowableParser : BpmnParser { } if (type.isCdata) { - val cdata = doc.createCDATASection(asString(type.valueType, value)) - currentNode.appendChild(cdata) + if (null == asString(type.valueType, value) && currentNode.textContent.isNullOrEmpty()) { + currentNode.textContent = "" + } else { + val cdata = doc.createCDATASection(asString(type.valueType, value)) + currentNode.appendChild(cdata) + } } else { - currentNode.setAttribute(segments[segments.size - 1], asString(type.valueType, value)) + setOrRemoveIfNull(currentNode, segments[segments.size - 1], asString(type.valueType, value)) + } + } + + private fun setOrRemoveIfNull(node: Element, name: String, value: String?) { + if (null == value && node.hasAttribute(name)) { + node.removeAttribute(name) } + + node.setAttribute(name, value) } private fun asString(type: PropertyValueType, value: Any?): String? { - if (null == value) { + if (null == value || "" == value) { return null } diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt index f96a89708..12d4008f5 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt @@ -20,6 +20,7 @@ interface LocationUpdateWithId: Event { val diagramElementId: DiagramElementId val dx: Float val dy: Float + val parentElementId: DiagramElementId? val internalPos: Int? } From 8648aee814a4e1f06d582721fc7e1e598ea5e857 Mon Sep 17 00:00:00 2001 From: valb3r Date: Fri, 15 May 2020 07:18:05 +0300 Subject: [PATCH 06/10] FBP-6. Fixed endpoint update indexes --- .../plugin/render/BpmnProcessRenderer.kt | 2 +- .../intellij/plugin/render/EdgeExtension.kt | 26 ++++++++++--------- .../plugin/flowable/parser/FlowableParser.kt | 2 +- .../flowable/parser/FlowableParserTest.kt | 2 +- .../plugin/bpmn/api/events/EventInterfaces.kt | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt index 192dac8be..76e3533c0 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt @@ -145,7 +145,7 @@ class BpmnProcessRenderer { val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: IdentifiableWaypoint -> if (elem.physical) { - dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy, parent.id, elem.internalPos)) + dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy, parent.id, elem.internalPhysicalPos)) } else { dest.addWaypointStructureUpdate(NewWaypointsEvent( parent.id, diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt index b6125cc6f..266332d82 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt @@ -8,8 +8,6 @@ import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.WaypointElement import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.EdgeWithIdentifiableWaypoints import com.valb3r.bpmn.intellij.plugin.bpmn.api.events.IdentifiableWaypoint import java.nio.charset.StandardCharsets.UTF_8 -import java.util.* -import kotlin.collections.ArrayList data class EdgeElementState ( override val id: DiagramElementId, @@ -19,11 +17,11 @@ data class EdgeElementState ( constructor(elem: EdgeElement): this(elem.id, elem.bpmnElement, ArrayList()) { elem.waypoint?.withIndex()?.forEach { when { - it.index == 0 -> waypoint += WaypointElementState(it.value, it.index) + it.index == 0 -> waypoint += WaypointElementState(waypointId(0), it.value, 0) it.index > 0 -> { val midpointX = (elem.waypoint!![it.index - 1].x + it.value.x) / 2.0f val midpointY = (elem.waypoint!![it.index - 1].y + it.value.y) / 2.0f - val next = WaypointElementState(it.value, it.index) + val next = WaypointElementState(waypointId(it.index), it.value, it.index) waypoint += WaypointElementState(childWaypointId(waypoint[it.index - 1], next), midpointX, midpointY, it.index) waypoint += next } @@ -34,11 +32,11 @@ data class EdgeElementState ( constructor(toCopy: EdgeWithIdentifiableWaypoints, newPhysicalWaypoints: List): this(toCopy.id, toCopy.bpmnElement, ArrayList()) { newPhysicalWaypoints.withIndex().forEach { when { - it.index == 0 -> waypoint += it.value + it.index == 0 -> waypoint += WaypointElementState(waypointId(0), it.value.asWaypointElement(), 0) it.index > 0 -> { val midpointX = (newPhysicalWaypoints[it.index - 1].x + it.value.x) / 2.0f val midpointY = (newPhysicalWaypoints[it.index - 1].y + it.value.y) / 2.0f - val next = it.value + val next = WaypointElementState(waypointId(it.index), it.value.asWaypointElement(), it.index) waypoint += WaypointElementState(childWaypointId(waypoint[it.index - 1], next), midpointX, midpointY, it.index) waypoint += next } @@ -46,6 +44,10 @@ data class EdgeElementState ( } } + fun waypointId(internalOrder: Int): String { + return Hashing.md5().hashString(id.id + ":" + internalOrder, UTF_8).toString() + } + fun childWaypointId(start: IdentifiableWaypoint, end: IdentifiableWaypoint): String { return Hashing.md5().hashString(start.id.id + ":" + end.id.id, UTF_8).toString() } @@ -58,26 +60,26 @@ data class WaypointElementState ( override val origX: Float, override val origY: Float, override val physical: Boolean, - override val internalPos: Int + override val internalPhysicalPos: Int ): IdentifiableWaypoint { - constructor(elem: WaypointElement, internalPos: Int): this(DiagramElementId(UUID.randomUUID().toString()), elem.x, elem.y, elem.x, elem.y, true, internalPos) + constructor(id: String, elem: WaypointElement, internalPos: Int): this(DiagramElementId(id), elem.x, elem.y, elem.x, elem.y, true, internalPos) constructor(id: String, x: Float, y: Float, internalPos: Int): this(DiagramElementId(id), x, y, x, y, false, internalPos) override fun copyAndTranslate(dx: Float, dy: Float): WaypointElementState { - return WaypointElementState(id, x + dx, y + dy, origX, origY, physical, internalPos) + return WaypointElementState(id, x + dx, y + dy, origX, origY, physical, internalPhysicalPos) } override fun moveTo(dx: Float, dy: Float): WaypointElementState { - return WaypointElementState(id, x + dx, y + dy, x + dx, y + dy, physical, internalPos) + return WaypointElementState(id, x + dx, y + dy, x + dx, y + dy, physical, internalPhysicalPos) } override fun asPhysical(): WaypointElementState { - return WaypointElementState(id, x, y, origX, origY, true, internalPos) + return WaypointElementState(id, x, y, origX, origY, true, internalPhysicalPos) } override fun originalLocation(): WaypointElementState { - return WaypointElementState(id, origX, origY, origX, origY, true, internalPos) + return WaypointElementState(id, origX, origY, origX, origY, true, internalPhysicalPos) } override fun asWaypointElement(): WaypointElement { 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 12850cccd..9068c48bc 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 @@ -160,7 +160,7 @@ class FlowableParser : BpmnParser { toRemove.forEach { node.removeChild(it) } trimWhitespace(node) - update.waypoints.filter { it.physical }.sortedBy { it.internalPos }.forEach { + update.waypoints.filter { it.physical }.sortedBy { it.internalPhysicalPos }.forEach { newWaypoint(doc, it, node) } } diff --git a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt index 38c30af4f..3e875bd99 100644 --- a/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt +++ b/flowable-xml-parser/src/test/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableParserTest.kt @@ -174,7 +174,7 @@ data class WaypointElementState ( override val origX: Float, override val origY: Float, override val physical: Boolean, - override val internalPos: Int + override val internalPhysicalPos: Int ): IdentifiableWaypoint { override fun moveTo(dx: Float, dy: Float): IdentifiableWaypoint { diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt index 12d4008f5..d7478cc75 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt @@ -61,7 +61,7 @@ interface IdentifiableWaypoint: Translatable, WithDiagramI val origX: Float val origY: Float val physical: Boolean - val internalPos: Int + val internalPhysicalPos: Int fun moveTo(dx: Float, dy: Float): IdentifiableWaypoint fun asPhysical(): IdentifiableWaypoint From 7906631598fcf09aa37ab53144e9fb5ac610a852 Mon Sep 17 00:00:00 2001 From: valb3r Date: Fri, 15 May 2020 07:37:46 +0300 Subject: [PATCH 07/10] FBP-6. Fixed problem with topology updates --- .../bpmn/intellij/plugin/events/Events.kt | 2 +- .../plugin/render/BpmnProcessRenderer.kt | 9 +++++---- .../intellij/plugin/render/EdgeExtension.kt | 19 ++++++++++--------- .../intellij/plugin/state/CurrentState.kt | 10 ++++++---- .../plugin/bpmn/api/events/EventInterfaces.kt | 2 ++ 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt index 59b1ed3ab..4cad875be 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/events/Events.kt @@ -14,7 +14,7 @@ data class BooleanValueUpdatedEvent(override val bpmnElementId: BpmnElementId, o data class DraggedToEvent(override val diagramElementId: DiagramElementId, override val dx: Float, override val dy: Float, override val parentElementId: DiagramElementId?, override val internalPos: Int?): LocationUpdateWithId -data class NewWaypointsEvent(override val edgeElementId: DiagramElementId, override val waypoints: List): NewWaypoints +data class NewWaypointsEvent(override val edgeElementId: DiagramElementId, override val waypoints: List, override val epoch: Int): NewWaypoints data class DiagramElementRemovedEvent(override val elementId: DiagramElementId): DiagramElementRemoved diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt index 76e3533c0..efc1f3162 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt @@ -151,8 +151,9 @@ class BpmnProcessRenderer { parent.id, parent.waypoint .filter { it.physical || it.id == elem.id } - .map { if (it.id == elem.id && !it.physical) it.moveTo(dx, dy).asPhysical() else it.originalLocation() } - .toList() + .map { if (it.id == elem.id && !it.physical) it.moveTo(dx, dy).asPhysical() else it } + .toList(), + parent.epoch + 1 )) } } @@ -177,8 +178,8 @@ class BpmnProcessRenderer { parent.waypoint .filter { it.physical } .filter { it.id != node.id } - .map { it.originalLocation() } - .toList() + .toList(), + parent.epoch + 1 ))} result += drawActionsElement(canvas, translatedNode, meta.interactionContext, mapOf(Actions.DELETE to callback)) } diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt index 266332d82..2a59e743f 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/EdgeExtension.kt @@ -12,16 +12,17 @@ import java.nio.charset.StandardCharsets.UTF_8 data class EdgeElementState ( override val id: DiagramElementId, override val bpmnElement: BpmnElementId?, - override val waypoint: MutableList = mutableListOf() + override val waypoint: MutableList = mutableListOf(), + override val epoch: Int ): EdgeWithIdentifiableWaypoints { - constructor(elem: EdgeElement): this(elem.id, elem.bpmnElement, ArrayList()) { + constructor(elem: EdgeElement): this(elem.id, elem.bpmnElement, ArrayList(), 0) { elem.waypoint?.withIndex()?.forEach { when { - it.index == 0 -> waypoint += WaypointElementState(waypointId(0), it.value, 0) + it.index == 0 -> waypoint += WaypointElementState(waypointId(epoch, 0), it.value, 0) it.index > 0 -> { val midpointX = (elem.waypoint!![it.index - 1].x + it.value.x) / 2.0f val midpointY = (elem.waypoint!![it.index - 1].y + it.value.y) / 2.0f - val next = WaypointElementState(waypointId(it.index), it.value, it.index) + val next = WaypointElementState(waypointId(epoch, it.index), it.value, it.index) waypoint += WaypointElementState(childWaypointId(waypoint[it.index - 1], next), midpointX, midpointY, it.index) waypoint += next } @@ -29,14 +30,14 @@ data class EdgeElementState ( } } - constructor(toCopy: EdgeWithIdentifiableWaypoints, newPhysicalWaypoints: List): this(toCopy.id, toCopy.bpmnElement, ArrayList()) { + constructor(toCopy: EdgeWithIdentifiableWaypoints, newPhysicalWaypoints: List, newEpoch: Int): this(toCopy.id, toCopy.bpmnElement, ArrayList(), newEpoch) { newPhysicalWaypoints.withIndex().forEach { when { - it.index == 0 -> waypoint += WaypointElementState(waypointId(0), it.value.asWaypointElement(), 0) + it.index == 0 -> waypoint += WaypointElementState(waypointId(epoch, 0), it.value.asWaypointElement(), 0) it.index > 0 -> { val midpointX = (newPhysicalWaypoints[it.index - 1].x + it.value.x) / 2.0f val midpointY = (newPhysicalWaypoints[it.index - 1].y + it.value.y) / 2.0f - val next = WaypointElementState(waypointId(it.index), it.value.asWaypointElement(), it.index) + val next = WaypointElementState(waypointId(epoch, it.index), it.value.asWaypointElement(), it.index) waypoint += WaypointElementState(childWaypointId(waypoint[it.index - 1], next), midpointX, midpointY, it.index) waypoint += next } @@ -44,8 +45,8 @@ data class EdgeElementState ( } } - fun waypointId(internalOrder: Int): String { - return Hashing.md5().hashString(id.id + ":" + internalOrder, UTF_8).toString() + fun waypointId(currentEpoch: Int, internalOrder: Int): String { + return Hashing.md5().hashString(currentEpoch.toString() + ":" + id.id + ":" + internalOrder, UTF_8).toString() } fun childWaypointId(start: IdentifiableWaypoint, end: IdentifiableWaypoint): String { diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt index c8123ae3b..9f165e862 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/state/CurrentState.kt @@ -110,10 +110,12 @@ class CurrentStateProvider { return elem } - val waypoints: MutableList = - updateEventsRegistry().newWaypointStructure(elem.id).lastOrNull()?.event?.waypoints?.toMutableList() ?: elem.waypoint.filter { it.physical }.toMutableList() - val updatedLocations = waypoints.filter { it.physical }.map { updateWaypointLocation(it) } - return EdgeElementState(elem, updatedLocations) + val event = updateEventsRegistry().newWaypointStructure(elem.id).lastOrNull()?.event + val waypoints = event?.waypoints?.toMutableList() ?: elem.waypoint.filter { it.physical }.toMutableList() + val epoch = event?.epoch ?: 0 + val newState = EdgeElementState(elem, waypoints, epoch) + val updatedWaypoints = newState.waypoint.filter { it.physical }.map { updateWaypointLocation(it) } + return EdgeElementState(newState, updatedWaypoints, epoch) } private fun updateWaypointLocation(waypoint: IdentifiableWaypoint): IdentifiableWaypoint { diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt index d7478cc75..da483c900 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/events/EventInterfaces.kt @@ -27,6 +27,7 @@ interface LocationUpdateWithId: Event { interface NewWaypoints: Event { val edgeElementId: DiagramElementId val waypoints: List + val epoch: Int } interface DiagramElementRemoved: Event { @@ -72,4 +73,5 @@ interface IdentifiableWaypoint: Translatable, WithDiagramI interface EdgeWithIdentifiableWaypoints: WithDiagramId { val bpmnElement: BpmnElementId? val waypoint: MutableList + val epoch: Int } \ No newline at end of file From fb7b1937443bcc1966635e91c3ddb3e7ced13ea2 Mon Sep 17 00:00:00 2001 From: valb3r Date: Fri, 15 May 2020 07:53:09 +0300 Subject: [PATCH 08/10] FBP-6. Fixed problem with merged element list --- .../plugin/flowable/parser/nodes/diagram/BPMNPlane.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/diagram/BPMNPlane.kt b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/diagram/BPMNPlane.kt index 576d781a8..406b32a3a 100644 --- a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/diagram/BPMNPlane.kt +++ b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/diagram/BPMNPlane.kt @@ -1,15 +1,21 @@ package com.valb3r.bpmn.intellij.plugin.flowable.parser.nodes.diagram +import com.fasterxml.jackson.annotation.JsonMerge import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.elements.PlaneElement import com.valb3r.bpmn.intellij.plugin.flowable.parser.nodes.process.BpmnElementIdMapper import org.mapstruct.Mapper +// For mixed lists in XML we need to have JsonSetter/JsonMerge on field +// https://github.com/FasterXML/jackson-dataformat-xml/issues/363 +// unfortunately this has failed with Kotlin 'data' classes data class BPMNPlane( @JacksonXmlProperty(isAttribute = true) val id: String, @JacksonXmlProperty(isAttribute = true) val bpmnElement: String, + @JsonMerge @JacksonXmlProperty(localName = "BPMNShape") @JacksonXmlElementWrapper(useWrapping = false) val bpmnShape: List?, + @JsonMerge @JacksonXmlProperty(localName = "BPMNEdge") @JacksonXmlElementWrapper(useWrapping = false) val bpmnEdge: List? ) { @Mapper(uses = [DiagramElementIdMapper::class, BpmnElementIdMapper::class]) From df09329438261294bfe5aad5904410b79e2c7530 Mon Sep 17 00:00:00 2001 From: valb3r Date: Fri, 15 May 2020 09:19:56 +0300 Subject: [PATCH 09/10] FBP-6. Fixed another set of annoying bugs --- .../flowable/parser/FlowableObjectFactory.kt | 2 +- .../plugin/flowable/parser/FlowableParser.kt | 108 +++++++++++++----- .../plugin/bpmn/api/info/PropertyType.kt | 10 +- 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt index 96df9af4e..c6751093f 100644 --- a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt +++ b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/FlowableObjectFactory.kt @@ -131,7 +131,7 @@ class FlowableObjectFactory: BpmnObjectFactory { private fun bounds(forBpmnObject: WithBpmnId): BoundsElement { return when(forBpmnObject) { - is BpmnStartEvent, is BpmnEndEvent, is BpmnExclusiveGateway -> BoundsElement(0.0f, 0.0f, 30.0f, 30.0f) + is BpmnStartEvent, is BpmnEndEvent, is BpmnExclusiveGateway -> BoundsElement(0.0f, 0.0f, 40.0f, 40.0f) else -> BoundsElement(0.0f, 0.0f, 100.0f, 80.0f) } } 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 9068c48bc..e7f69942d 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 @@ -32,6 +32,35 @@ import javax.xml.xpath.XPathFactory const val CDATA_FIELD = "CDATA" +enum class PropertyTypeDetails( + val propertyType: PropertyType, + val xmlPath: String, + val type: XmlType +) { + ID(PropertyType.ID, "id", XmlType.ATTRIBUTE), + NAME(PropertyType.NAME,"name", XmlType.ATTRIBUTE), + DOCUMENTATION(PropertyType.DOCUMENTATION, "documentation.text", XmlType.CDATA), + ASYNC(PropertyType.ASYNC, "flowable:async", XmlType.ATTRIBUTE), + CALLED_ELEM(PropertyType.CALLED_ELEM, "calledElement", XmlType.ATTRIBUTE), + CALLED_ELEM_TYPE(PropertyType.CALLED_ELEM_TYPE, "flowable:calledElementType", XmlType.ATTRIBUTE), + INHERIT_VARS(PropertyType.INHERIT_VARS, "flowable:inheritVariables", XmlType.ATTRIBUTE), + FALLBACK_TO_DEF_TENANT(PropertyType.FALLBACK_TO_DEF_TENANT, "flowable:fallbackToDefaultTenant", XmlType.ATTRIBUTE), + EXCLUSIVE(PropertyType.EXCLUSIVE,"flowable:exclusive", XmlType.ATTRIBUTE), + DELEGATE_EXPRESSION(PropertyType.DELEGATE_EXPRESSION, "flowable:delegateExpression", XmlType.ATTRIBUTE), + IS_TRIGGERABLE(PropertyType.IS_TRIGGERABLE, "flowable:triggerable", XmlType.ATTRIBUTE), + SOURCE_REF(PropertyType.SOURCE_REF,"sourceRef", XmlType.ATTRIBUTE), + TARGET_REF(PropertyType.TARGET_REF, "targetRef", XmlType.ATTRIBUTE), + CONDITION_EXPR_VALUE(PropertyType.CONDITION_EXPR_VALUE, "conditionExpression.text", XmlType.CDATA), + CONDITION_EXPR_TYPE(PropertyType.CONDITION_EXPR_TYPE, "conditionExpression.xsi:type", XmlType.ATTRIBUTE), + DEFAULT_FLOW(PropertyType.DEFAULT_FLOW, "default", XmlType.ATTRIBUTE) +} + +enum class XmlType { + + CDATA, + ATTRIBUTE +} + class FlowableParser : BpmnParser { val OMGDI_NS = "http://www.omg.org/spec/DD/20100524/DI" @@ -216,7 +245,7 @@ class FlowableParser : BpmnParser { else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) } - update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value.value) } + update.props.forEach { setToNode(doc, newNode, it.key, it.value.value) } trimWhitespace(diagramParent, false) diagramParent.appendChild(newNode) @@ -251,7 +280,7 @@ class FlowableParser : BpmnParser { else -> throw IllegalArgumentException("Can't store: " + update.bpmnObject) } - update.props.forEach { setPropertyToNode(doc, newNode, it.key, it.value.value) } + update.props.forEach { setToNode(doc, newNode, it.key, it.value.value) } trimWhitespace(diagramParent, false) diagramParent.appendChild(newNode) @@ -276,32 +305,36 @@ class FlowableParser : BpmnParser { XPathConstants.NODE ) as Element - setPropertyToNode(doc, node, update.property, update.newValue) + setToNode(doc, node, update.property, update.newValue) } - private fun setPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Any?) { + + private fun setToNode(doc: Document, node: Element, type: PropertyType, value: Any?) { + val details = PropertyTypeDetails.values().filter { it.propertyType == type }.firstOrNull()!! when { - type.xmlPath.contains(".") -> setNestedPropertyToNode(doc, node, type, value) - else -> setOrRemoveIfNull(node, type.xmlPath, asString(type.valueType, value)) + details.xmlPath.contains(".") -> setNestedToNode(doc, node, type, details, value) + else -> setAttributeOrValueOrCdataOrRemoveIfNull(node, details.xmlPath, details, asString(type.valueType, value)) } } - private fun setNestedPropertyToNode(doc: Document, node: Element, type: PropertyType, value: Any?) { - val segments = type.xmlPath.split(".") - val childOf: ((Element, String) -> Element?) = child@{target, name -> - for (pos in 0 until target.childNodes.length) { - if (target.childNodes.item(pos).nodeName.contains(name)) { - return@child target.childNodes.item(pos) as Element - } - } - return@child null - } + private fun setNestedToNode(doc: Document, node: Element, type: PropertyType, details: PropertyTypeDetails, value: Any?) { + val segments = details.xmlPath.split(".") + val childOf: ((Element, String) -> Element?) = {target, name -> nodeChildByName(target, name)} var currentNode = node for (segment in 0 until segments.size - 1) { val name = segments[segment] + if ("" == name) { + continue + } + val child = childOf(currentNode, name) if (null == child) { + // do not create elements for null values + if (null == value ) { + return + } + val newElem = doc.createElement(name) currentNode.appendChild(newElem) currentNode = newElem @@ -310,26 +343,47 @@ class FlowableParser : BpmnParser { } } - if (type.isCdata) { - if (null == asString(type.valueType, value) && currentNode.textContent.isNullOrEmpty()) { - currentNode.textContent = "" - } else { - val cdata = doc.createCDATASection(asString(type.valueType, value)) - currentNode.appendChild(cdata) + setAttributeOrValueOrCdataOrRemoveIfNull(currentNode, segments[segments.size - 1], details, asString(type.valueType, value)) + } + + private fun nodeChildByName(target: Element, name: String): Element? { + for (pos in 0 until target.childNodes.length) { + if (target.childNodes.item(pos).nodeName.contains(name)) { + return target.childNodes.item(pos) as Element } - } else { - setOrRemoveIfNull(currentNode, segments[segments.size - 1], asString(type.valueType, value)) + } + return null + } + + private fun setAttributeOrValueOrCdataOrRemoveIfNull(node: Element, name: String, details: PropertyTypeDetails, value: String?) { + when (details.type) { + XmlType.ATTRIBUTE -> setAttribute(node, name, value) + XmlType.CDATA -> setCdata(node, name, value) } } - private fun setOrRemoveIfNull(node: Element, name: String, value: String?) { - if (null == value && node.hasAttribute(name)) { - node.removeAttribute(name) + private fun setAttribute(node: Element, name: String, value: String?) { + if (value.isNullOrEmpty()) { + if (node.hasAttribute(name)) { + node.removeAttribute(name) + } + return } node.setAttribute(name, value) } + private fun setCdata(node: Element, name: String, value: String?) { + if (value.isNullOrEmpty()) { + if (node.textContent.isNotBlank()) { + node.textContent = null + } + return + } + + node.textContent = value + } + private fun asString(type: PropertyValueType, value: Any?): String? { if (null == value || "" == value) { return null diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt index b4f00609f..8a81d06cc 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/info/PropertyType.kt @@ -6,11 +6,9 @@ enum class PropertyType( val id: String, val caption: String, val valueType: PropertyValueType, - val path: String = id, - val xmlPath: String = path, - val isCdata: Boolean = false + val path: String = id ) { - ID("id", "ID", STRING, "id.id", "id"), + ID("id", "ID", STRING, "id.id"), NAME("name", "Name", STRING), DOCUMENTATION("documentation", "Documentation", STRING), ASYNC("async", "Asynchronous", BOOLEAN), @@ -23,7 +21,7 @@ enum class PropertyType( IS_TRIGGERABLE("triggerable", "Is activity triggerable?", BOOLEAN), SOURCE_REF("sourceRef","Source reference", STRING), TARGET_REF("targetRef", "Target reference", STRING), - CONDITION_EXPR_VALUE("conditionExpression", "Condition expression", EXPRESSION, "conditionExpression.text", "conditionExpression.text", true), - CONDITION_EXPR_TYPE("conditionExpression", "Condition expression type", STRING, "conditionExpression.type", "conditionExpression.xsi:type"), + CONDITION_EXPR_VALUE("conditionExpression.text", "Condition expression", EXPRESSION, "conditionExpression.text"), + CONDITION_EXPR_TYPE("conditionExpression.type", "Condition expression type", STRING, "conditionExpression.type"), DEFAULT_FLOW("defaultElement", "Default flow element", STRING) } \ No newline at end of file From aacd0a926722edbdbe4189b9a7796cf3a09ecc9c Mon Sep 17 00:00:00 2001 From: valb3r Date: Fri, 15 May 2020 10:30:21 +0300 Subject: [PATCH 10/10] FBP-6. Fixed problem with element selection --- .../plugin/render/BpmnProcessRenderer.kt | 16 ++++++++++++---- .../bpmn/intellij/plugin/render/Canvas.kt | 18 +++++++++++++++++- .../plugin/render/ElementInteractionContext.kt | 3 ++- .../plugin/flowable/parser/FlowableParser.kt | 2 +- .../parser/nodes/process/SequenceFlow.kt | 8 ++++---- .../bpmn/api/bpmn/elements/BpmnSequenceFlow.kt | 8 ++++---- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt index efc1f3162..79ac1b0bf 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/BpmnProcessRenderer.kt @@ -88,7 +88,7 @@ class BpmnProcessRenderer { } val actionsElem = drawActionsElement(canvas, it, renderMeta.interactionContext, mutableMapOf(Actions.DELETE to deleteCallback, Actions.NEW_LINK to newSequenceCallback)) areaByElement += actionsElem - renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy, null, null))} + renderMeta.interactionContext.dragEndCallbacks[it.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents, droppedOn: BpmnElementId? -> dest.addLocationUpdateEvent(DraggedToEvent(it.id, dx, dy, null, null))} } } } @@ -143,9 +143,17 @@ class BpmnProcessRenderer { private fun drawWaypointAnchors(canvas: CanvasPainter, begin: IdentifiableWaypoint, end: IdentifiableWaypoint, parent: EdgeWithIdentifiableWaypoints, meta: RenderMetadata, isLast: Boolean, endWaypointIndex: Int): Map { val result = HashMap() - val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: IdentifiableWaypoint -> + val dragCallback = {dx: Float, dy: Float, dest: ProcessModelUpdateEvents, elem: IdentifiableWaypoint, droppedOn: BpmnElementId? -> + val index = parent.waypoint.indexOf(elem) if (elem.physical) { dest.addLocationUpdateEvent(DraggedToEvent(elem.id, dx, dy, parent.id, elem.internalPhysicalPos)) + if (null != droppedOn && null != parent.bpmnElement) { + if (parent.waypoint.size - 1 == index ) { + dest.addPropertyUpdateEvent(StringValueUpdatedEvent(parent.bpmnElement!!, PropertyType.TARGET_REF, droppedOn.id)) + } else if (0 == index) { + dest.addPropertyUpdateEvent(StringValueUpdatedEvent(parent.bpmnElement!!, PropertyType.SOURCE_REF, droppedOn.id)) + } + } } else { dest.addWaypointStructureUpdate(NewWaypointsEvent( parent.id, @@ -171,7 +179,7 @@ class BpmnProcessRenderer { ANCHOR_Z_INDEX, parent.id ) - meta.interactionContext.dragEndCallbacks[node.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dragCallback(dx, dy, dest, node)} + meta.interactionContext.dragEndCallbacks[node.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents, droppedOn: BpmnElementId? -> dragCallback(dx, dy, dest, node, droppedOn)} if (active && node.physical && index > 0 && (index < parent.waypoint.size - 1)) { val callback = { dest: ProcessModelUpdateEvents -> dest.addWaypointStructureUpdate(NewWaypointsEvent( parent.id, @@ -192,7 +200,7 @@ class BpmnProcessRenderer { } drawNode(begin, endWaypointIndex - 1) - meta.interactionContext.dragEndCallbacks[begin.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents -> dragCallback(dx, dy, dest, begin)} + meta.interactionContext.dragEndCallbacks[begin.id] = { dx: Float, dy: Float, dest: ProcessModelUpdateEvents, droppedOn: BpmnElementId? -> dragCallback(dx, dy, dest, begin, droppedOn)} return result } diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt index b4b06aad6..5081ac408 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/Canvas.kt @@ -4,6 +4,8 @@ import com.google.common.cache.CacheBuilder import com.intellij.ui.EditorTextField import com.valb3r.bpmn.intellij.plugin.Colors import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObjectView +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.BpmnSequenceFlow import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.events.updateEventsRegistry import com.valb3r.bpmn.intellij.plugin.properties.PropertiesVisualizer @@ -170,7 +172,7 @@ class Canvas: JPanel() { interactionCtx = attractToAnchors(interactionCtx) val dx = interactionCtx.current.x - interactionCtx.start.x val dy = interactionCtx.current.y - interactionCtx.start.y - interactionCtx.draggedIds.forEach { interactionCtx.dragEndCallbacks[it]?.invoke(dx, dy, updateEventsRegistry()) } + interactionCtx.draggedIds.forEach { interactionCtx.dragEndCallbacks[it]?.invoke(dx, dy, updateEventsRegistry(), bpmnElemsUnderDragCurrent()) } } interactionCtx = interactionCtx.copy(draggedIds = emptySet()) @@ -199,6 +201,20 @@ class Canvas: JPanel() { return camera.fromCameraView(point) } + private fun bpmnElemsUnderDragCurrent(): BpmnElementId? { + val onScreen = camera.toCameraView(interactionCtx.current) + val cursor = cursorRect(onScreen) + val elems = areaByElement?.filter { it.value.area.intersects(cursor) } + + return elems?.map { stateProvider.currentState().elementByDiagramId[it.key] } + ?.filterNotNull() + ?.map { stateProvider.currentState().elementByStaticId[it] } + ?.filter { it !is BpmnSequenceFlow } + ?.filterNotNull() + ?.map { it.id } + ?.firstOrNull() + } + private fun elemsUnderCursor(cursorPoint: Point2D.Float): List { val cursor = cursorRect(cursorPoint) val intersection = areaByElement?.filter { it.value.area.intersects(cursor) } diff --git a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/ElementInteractionContext.kt b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/ElementInteractionContext.kt index 8c275ab9e..0433bcb8b 100644 --- a/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/ElementInteractionContext.kt +++ b/bpmn-intellij-plugin/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/render/ElementInteractionContext.kt @@ -1,12 +1,13 @@ package com.valb3r.bpmn.intellij.plugin.render +import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId import com.valb3r.bpmn.intellij.plugin.bpmn.api.diagram.DiagramElementId import com.valb3r.bpmn.intellij.plugin.events.ProcessModelUpdateEvents import java.awt.geom.Point2D data class ElementInteractionContext( val draggedIds: Set, - val dragEndCallbacks: MutableMap Unit>, + val dragEndCallbacks: MutableMap Unit>, val clickCallbacks: MutableMap Unit>, val anchorsHit: Set>, val start: Point2D.Float, 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 e7f69942d..89919d828 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 @@ -334,7 +334,7 @@ class FlowableParser : BpmnParser { if (null == value ) { return } - + val newElem = doc.createElement(name) currentNode.appendChild(newElem) currentNode = newElem diff --git a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/process/SequenceFlow.kt b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/process/SequenceFlow.kt index 1ffd2e0c9..e2f94e8a3 100644 --- a/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/process/SequenceFlow.kt +++ b/flowable-xml-parser/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/flowable/parser/nodes/process/SequenceFlow.kt @@ -14,8 +14,8 @@ data class SequenceFlow( @JacksonXmlProperty(isAttribute = true) val id: String, @JacksonXmlProperty(isAttribute = true) val name: String?, val documentation: String?, - @JacksonXmlProperty(isAttribute = true) val sourceRef: String, - @JacksonXmlProperty(isAttribute = true) val targetRef: String, + @JacksonXmlProperty(isAttribute = true) val sourceRef: String?, + @JacksonXmlProperty(isAttribute = true) val targetRef: String?, val conditionExpression: ConditionExpression? ): BpmnMappable { @@ -30,6 +30,6 @@ data class SequenceFlow( } data class ConditionExpression( - val type: String, - @JsonProperty(CDATA_FIELD) @JacksonXmlText @JacksonXmlCData val text: String + val type: String?, + @JsonProperty(CDATA_FIELD) @JacksonXmlText @JacksonXmlCData val text: String? ) \ No newline at end of file diff --git a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/bpmn/elements/BpmnSequenceFlow.kt b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/bpmn/elements/BpmnSequenceFlow.kt index ad1960303..e2bf23033 100644 --- a/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/bpmn/elements/BpmnSequenceFlow.kt +++ b/xml-parser-api/src/main/kotlin/com/valb3r/bpmn/intellij/plugin/bpmn/api/bpmn/elements/BpmnSequenceFlow.kt @@ -8,13 +8,13 @@ data class BpmnSequenceFlow( override val id: BpmnElementId, val name: String?, val documentation: String?, - val sourceRef: String, - val targetRef: String, + val sourceRef: String?, // can't be null in reality, but malformed XMLs should be editable too + val targetRef: String?, // can't be null in reality, but malformed XMLs should be editable too val conditionExpression: ConditionExpression? ): WithBpmnId @KotlinBuilder data class ConditionExpression( - val type: String, - val text: String + val type: String?, // can't be null in reality, but malformed XMLs should be editable too + val text: String? // can't be null in reality, but malformed XMLs should be editable too ) \ No newline at end of file