You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by jo...@apache.org on 2021/09/10 11:01:18 UTC
[isis] 01/07: ISIS-2846 Create a LinkTreeDiagram (via PlantUML
mindmap) from History/LogEntry
This is an automated email from the ASF dual-hosted git repository.
joergrade pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
commit f46347040f2abaadadf4bbec99bececd1a7462a4
Author: Jörg Rade <jo...@kuehne-nagel.com>
AuthorDate: Tue Aug 31 14:06:48 2021 +0200
ISIS-2846 Create a LinkTreeDiagram (via PlantUML mindmap) from History/LogEntry
---
.../isis/client/kroviz/core/event/LogEntry.kt | 5 +
.../client/kroviz/core/event/LogEntryDecorator.kt | 84 ++++------------
.../client/kroviz/ui/diagram/LinkTreeDiagram.kt | 112 ++++++++++++++++-----
.../isis/client/kroviz/ui/dialog/DiagramDialog.kt | 27 ++++-
.../isis/client/kroviz/ui/dialog/EventLogDetail.kt | 35 +++----
.../client/kroviz/ui/panel/DynamicMenuBuilder.kt | 15 +--
.../apache/isis/client/kroviz/utils/StringUtils.kt | 12 +++
.../kroviz/core/event/LogEntryDecoratorTest.kt | 23 +++++
.../isis/client/kroviz/util/StringUtilsTest.kt | 24 +++++
9 files changed, 212 insertions(+), 125 deletions(-)
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
index 2fe5973..a7db164 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
@@ -250,6 +250,11 @@ data class LogEntry(
return null
}
+ fun upLink(): Link? {
+ getLinks().forEach { if (it.relation() == Relation.UP) return it }
+ return null
+ }
+
fun getLinks(): List<Link> {
return if (obj is HasLinks) {
(obj as HasLinks).getLinks()
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt
index b5e41ac..68e9626 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt
@@ -21,41 +21,12 @@ package org.apache.isis.client.kroviz.core.event
import org.apache.isis.client.kroviz.to.Link
import org.apache.isis.client.kroviz.to.Relation
import org.apache.isis.client.kroviz.ui.core.Constants
-import org.apache.isis.client.kroviz.utils.StringUtils
class LogEntryDecorator(val logEntry: LogEntry) {
- val href: String
- val links: List<Link>
- val linked: List<LogEntry>
-
- init {
- href = logEntry.selfHref()
- links = logEntry.getLinks()
- linked = EventStore.getLinked()
- }
-
- fun findOrphans(children: Set<LogEntry>): Set<String> {
- console.log("[LED.findOrphans] $href")
- val kids = children.map { it.url }
- val orphans = mutableSetOf<String>()
- links.forEach {
-// console.log(it)
- val rel = it.relation()
- when {
- (rel == Relation.UP) -> {
- }
- (rel == Relation.SELF) -> {
- }
- else -> {
- val url = it.href
- if (!kids.contains(url))
- orphans.add(url)
- }
- }
- }
- return orphans
- }
+ val href: String = logEntry.selfHref()
+ val links: List<Link> = logEntry.getLinks()
+ val linked: List<LogEntry> = EventStore.getLinked()
fun findChildren(): Set<LogEntry> {
val children = findChildrenByUpRelation()
@@ -77,7 +48,7 @@ class LogEntryDecorator(val logEntry: LogEntry) {
}
private fun findChildrenByLinks(): Set<LogEntry> {
- console.log("[LED.findChildrenByLinks] $href")
+ console.log("[LED.findChildrenByLinks]")
val children = mutableSetOf<LogEntry>()
links.forEach {
console.log(it.toString())
@@ -113,29 +84,30 @@ class LogEntryDecorator(val logEntry: LogEntry) {
return children
}
- fun selfType(): String {
- val selfLink = logEntry.selfLink()
- if (selfLink != null) {
- return selfLink.representation().type
- } else return ""
- }
-
- private fun hasUp(): Boolean {
- links.forEach {
- if (it.relation() == Relation.UP) {
- return true
+ fun findChildrenIn(aggregatedList: List<LogEntry>): List<LogEntry> {
+ console.log("[LED.findChildrenIn]")
+ val selfUrl = href
+ val children = mutableListOf<LogEntry>()
+ aggregatedList.forEach {
+ if (it.url != selfUrl && it.response.contains(selfUrl)) {
+ children.add(it)
}
}
- return false
+ return children
}
- fun hasParent(): Boolean {
- val answer = hasUp()
- if (answer) return true
- return findParent() != null
+ fun selfType(): String {
+ val selfLink = logEntry.selfLink()
+ return if (selfLink != null) {
+ selfLink.representation().type
+ } else {
+ console.log("[LED.selfType]")
+ console.log(logEntry)
+ ""
+ }
}
- private fun findParent(): LogEntry? {
+ fun findParent(): LogEntry? {
val url = logEntry.url
linked.forEach {
when {
@@ -146,16 +118,4 @@ class LogEntryDecorator(val logEntry: LogEntry) {
return null
}
- fun shortTitle(): String {
- var result = logEntry.url
- val signature = Constants.restInfix
- if (logEntry.url.contains(signature)) {
- // strip off protocol, host, port
- // val protocolHostPort = UiManager.getUrl()
-// result = result.replace(protocolHostPort + signature, "")
- result = StringUtils.removeHexCode(result)
- }
- return result
- }
-
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
index d49321c..c6f6533 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
@@ -1,45 +1,107 @@
package org.apache.isis.client.kroviz.ui.diagram
+import org.apache.isis.client.kroviz.core.aggregator.BaseAggregator
import org.apache.isis.client.kroviz.core.event.EventStore
import org.apache.isis.client.kroviz.core.event.LogEntry
import org.apache.isis.client.kroviz.core.event.LogEntryDecorator
+import org.apache.isis.client.kroviz.core.event.ResourceSpecification
+import org.apache.isis.client.kroviz.to.HasLinks
+import org.apache.isis.client.kroviz.ui.core.UiManager
+import org.apache.isis.client.kroviz.utils.StringUtils
object LinkTreeDiagram {
- private val NL = "\n"
- private val SEP = " | "
- private val OPEN = "{"
- private val CLOSE = "}"
- private val PLUS = "+"
-
- fun build(): String {
- var code = "@startsalt$NL$OPEN$NL$OPEN T#$NL"
- val roots: List<LogEntry> = findRoots()
- roots.forEach {
- code += iterateOverChildren(it, PLUS)
+ private const val NL = "\n"
+ private const val prolog = "@startmindmap$NL"
+ private const val epilog = "@endmindmap$NL"
+ private val protocolHostPort = UiManager.getUrl()
+
+ fun build(aggregator: BaseAggregator): String {
+ var code = prolog
+ val entryList: List<LogEntry> = EventStore.findAllBy(aggregator)
+ val root = findRoot(entryList)
+ if (root == null) {
+ code += "* /$NL"
+ code += createNodes(entryList, 2)
+ } else {
+ code += root.asPumlNode(1)
+ val led = LogEntryDecorator(root)
+ val childList = led.findChildrenIn(entryList)
+ console.log("[LTD.build]")
+ console.log(aggregator)
+ console.log(entryList)
+ code += createChildNodes(childList, 2)
+ }
+ code += epilog
+ return code
+ }
+
+ private fun createChildNodes(childList: List<LogEntry>, level: Int): String {
+ var code = ""
+ childList.forEach {
+ code += createNode(it, level)
+ val led = LogEntryDecorator(it)
+ val kidSet = led.findChildrenIn(childList)
+ code += createChildNodes(kidSet, level +1)
}
- code += "$CLOSE$NL$CLOSE$NL@endsalt"
return code
}
- private fun findRoots(): List<LogEntry> {
- val rootSet = mutableListOf<LogEntry>()
- EventStore.getLinked().forEach { le ->
- val led = LogEntryDecorator(le)
- if (!led.hasParent()) rootSet.add(le) // this may still include
+ private fun createNode(le: LogEntry, level: Int): String {
+ var code = ""
+ if (isInEventStore(le.url)) {
+ code += le.asPumlNode(level)
}
- return rootSet
+ return code
}
- private fun iterateOverChildren(logEntry: LogEntry, prefix: String): String {
- val led = LogEntryDecorator(logEntry)
- val children = led.findChildren()
- val orphans = led.findOrphans(children)
- var code = prefix + " " + led.shortTitle() + SEP + led.selfType() + SEP + orphans.toString() + NL
- children.forEach {
- code += iterateOverChildren(it, prefix + PLUS)
+ private fun createNodes(entryList: List<LogEntry>, level: Int): String {
+ var code = ""
+ entryList.forEach {
+ code += createNode(it, level)
}
return code
}
+ private fun findRoot(entryList: List<LogEntry>): LogEntry? {
+ entryList.forEach {
+ val led = LogEntryDecorator(it)
+ val parent = led.findParent()
+ if (parent != null && !entryList.contains(parent)) {
+ return parent
+ }
+ }
+ return null
+ }
+
+ private fun isInEventStore(url: String): Boolean {
+ val rs = ResourceSpecification(url)
+ val le = EventStore.findBy(rs)
+ return (le != null)
+ }
+
+ fun LogEntry.asPumlNode(level: Int): String {
+ val led = LogEntryDecorator(this)
+ val title = StringUtils.shortTitle(this.url, protocolHostPort)
+ val type = led.selfType()
+ val depth = "*".repeat(level)
+ var answer = "$depth:..//<<$type>>//..$NL**$title**$NL"
+ answer += "----$NL"
+ answer += traceInfo(this)
+ return answer + ";" + NL
+ }
+
+ private fun traceInfo(logEntry: LogEntry) : String {
+ val obj = logEntry.obj!!
+ var answer = "__" + obj::class.simpleName + "__" + NL
+ if (obj is HasLinks) {
+ obj.links.forEach {
+ answer += StringUtils.shortTitle(it.href, protocolHostPort) + NL
+ }
+ }
+ console.log("[LTD.traceInfo]")
+ console.log(answer)
+ return answer
+ }
+
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt
index 447b97a..750c3b1 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt
@@ -56,7 +56,7 @@ class DiagramDialog(
)
}
- override fun execute(action:String?) {
+ override fun execute(action: String?) {
pin()
}
@@ -84,14 +84,35 @@ class DiagramDialog(
fun buildMenu(): List<KvisionHtmlLink> {
val menu = mutableListOf<KvisionHtmlLink>()
+ menu.add(buildPinAction())
+ menu.add(buildDownloadAction())
+ return menu
+ }
+
+ private fun buildPinAction(): io.kvision.html.Link {
val action = MenuFactory.buildActionLink(
label = "Pin",
menuTitle = "Pin")
action.onClick {
pin()
}
- menu.add(action)
- return menu
+ return action
+ }
+
+ private fun buildDownloadAction(): io.kvision.html.Link {
+ val action = MenuFactory.buildActionLink(
+ label = "Download",
+ menuTitle = "Download")
+ action.onClick {
+ download()
+ }
+ return action
+ }
+
+ private fun download() {
+ val svgCode = getDiagramCode()
+ DownloadDialog(fileName = "diagram.svg", svgCode).open()
+ dialog.close()
}
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt
index f73a26a..6eb29f6 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt
@@ -18,7 +18,6 @@
*/
package org.apache.isis.client.kroviz.ui.dialog
-import org.apache.isis.client.kroviz.core.aggregator.CollectionAggregator
import org.apache.isis.client.kroviz.core.event.EventStore
import org.apache.isis.client.kroviz.core.event.LogEntry
import org.apache.isis.client.kroviz.core.event.LogEntryDecorator
@@ -30,11 +29,13 @@ import org.apache.isis.client.kroviz.ui.core.FormItem
import org.apache.isis.client.kroviz.ui.core.RoDialog
import org.apache.isis.client.kroviz.ui.diagram.JsonDiagram
import org.apache.isis.client.kroviz.ui.diagram.LayoutDiagram
+import org.apache.isis.client.kroviz.ui.diagram.LinkTreeDiagram
import org.apache.isis.client.kroviz.utils.StringUtils
import org.apache.isis.client.kroviz.utils.XmlHelper
class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
private var logEntry: LogEntry
+ private lateinit var dialog: RoDialog
init {
// For a yet unknown reason, aggregators are not transmitted via tabulator.
@@ -45,7 +46,7 @@ class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
// callback parameter
private val LOG: String = "log"
- private val OBJ: String = "obj"
+ private val LNK: String = "lnk"
fun open() {
val responseStr = if (logEntry.subType == Constants.subTypeJson) {
@@ -58,23 +59,21 @@ class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
val children = led.findChildren()
var kids = ""
children.forEach { kids += it.url + "\n" }
- var orphans = ""
- led.findOrphans(children).forEach { orphans += it + "\n" }
val formItems = mutableListOf<FormItem>()
formItems.add(FormItem("Url", ValueType.TEXT, logEntry.title))
formItems.add(FormItem("Response", ValueType.TEXT_AREA, responseStr, 10))
formItems.add(FormItem("Aggregators", ValueType.TEXT, content = logEntry.aggregators))
formItems.add(FormItem("Children", ValueType.TEXT_AREA, kids, size = 5))
- formItems.add(FormItem("Orphans", ValueType.TEXT_AREA, orphans, size = 5))
- formItems.add(FormItem("Object Diagram", ValueType.BUTTON, null, callBack = this, callBackAction = OBJ))
+ formItems.add(FormItem("Link Tree Diagram", ValueType.BUTTON, null, callBack = this, callBackAction = LNK))
formItems.add(FormItem("Console", ValueType.BUTTON, null, callBack = this, callBackAction = LOG))
- RoDialog(
+ dialog = RoDialog(
caption = "Details :" + logEntry.title,
items = formItems,
command = this,
defaultAction = "Diagram",
- widthPerc = 60).open()
+ widthPerc = 60)
+ dialog.open()
}
override fun execute(action: String?) {
@@ -83,8 +82,8 @@ class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
action == LOG -> {
console.log(logEntry)
}
- action == OBJ -> {
- objectDiagram()
+ action == LNK -> {
+ linkTreeDiagram()
}
else -> {
console.log(logEntry)
@@ -93,18 +92,12 @@ class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
}
}
- private fun objectDiagram() {
+ private fun linkTreeDiagram() {
logEntry.aggregators.forEach {
- console.log(it)
- if (it is CollectionAggregator) {
- val displayModel = it.dpm
- // https://github.com/moll/json-stringify-safe/blob/master/stringify.js
- val jsonStr = JSON.stringify(displayModel)
- console.log(jsonStr)
- val pumlCode = JsonDiagram.build(jsonStr)
- DiagramDialog("Object Diagram", pumlCode).open()
- }
+ val code = LinkTreeDiagram.build(it)
+ DiagramDialog("Link Tree Diagram", code).open()
}
+ dialog.close()
}
private fun defaultAction() {
@@ -118,7 +111,7 @@ class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
JsonDiagram.build(str)
else -> "{}"
}
- DiagramDialog("Response Diagram", pumlCode).open()
+ DiagramDialog("Layout Diagram", pumlCode).open()
}
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt
index 055df30..7f01d61 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt
@@ -25,8 +25,6 @@ import org.apache.isis.client.kroviz.core.event.ResourceProxy
import org.apache.isis.client.kroviz.to.TObject
import org.apache.isis.client.kroviz.ui.chart.ChartFactory
import org.apache.isis.client.kroviz.ui.core.UiManager
-import org.apache.isis.client.kroviz.ui.diagram.LinkTreeDiagram
-import org.apache.isis.client.kroviz.ui.dialog.DiagramDialog
import org.apache.isis.client.kroviz.ui.dialog.EventExportDialog
import org.apache.isis.client.kroviz.utils.IconManager
import org.apache.isis.client.kroviz.utils.StringUtils
@@ -40,7 +38,7 @@ class DynamicMenuBuilder {
val title = StringUtils.deCamel(it.id)
val icon = IconManager.find(title)
val invokeLink = it.getInvokeLink()!!
- val command = { ResourceProxy().fetch(invokeLink) }
+ val command = { ResourceProxy().fetch(invokeLink) }
val me = buildMenuEntry(icon, title, command)
menu.add(me)
}
@@ -50,10 +48,6 @@ class DynamicMenuBuilder {
fun buildTableMenu(table: EventLogTable): dynamic {
val menu = mutableListOf<dynamic>()
- val a1 = buildMenuEntry("Hierarchy", "Link Tree Diagram",
- { this.linkTreeDiagram() })
- menu.add(a1)
-
val a2 = buildMenuEntry("Export", "Export Events ...", {
EventExportDialog().open()
})
@@ -82,13 +76,6 @@ class DynamicMenuBuilder {
}
}
- private fun linkTreeDiagram() {
- val code = LinkTreeDiagram.build()
- console.log("[DMB.linkTreeDiagram]")
- console.log(code)
- DiagramDialog("Link Tree Diagram", code).open()
- }
-
private fun downLoadCsv(table: EventLogTable) {
table.tabulator.downloadCSV("data.csv")
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt
index 311b8d7..a042665 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt
@@ -21,6 +21,7 @@ package org.apache.isis.client.kroviz.utils
import org.apache.isis.client.kroviz.to.Argument
import org.apache.isis.client.kroviz.to.Link
import org.apache.isis.client.kroviz.to.TObject
+import org.apache.isis.client.kroviz.ui.core.Constants
object StringUtils {
@@ -62,6 +63,17 @@ object StringUtils {
return if (input == outputWithoutWhiteSpace) input else output
}
+ fun shortTitle(url: String, protocolHostPort: String): String {
+ var title = url
+ val signature = Constants.restInfix
+ if (title.contains(signature)) {
+ // strip off protocol, host, port
+ title = title.replace(protocolHostPort + signature, "")
+ title = StringUtils.removeHexCode(title)
+ }
+ return title
+ }
+
fun removeHexCode(input: String): String {
var output = ""
val list: List<String> = input.split("/")
diff --git a/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecoratorTest.kt b/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecoratorTest.kt
new file mode 100644
index 0000000..2aa8a54
--- /dev/null
+++ b/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecoratorTest.kt
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.client.kroviz.core.event
+
+class LogEntryDecoratorTest {
+
+}
diff --git a/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt b/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt
new file mode 100644
index 0000000..ba15e96
--- /dev/null
+++ b/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt
@@ -0,0 +1,24 @@
+package org.apache.isis.client.kroviz.util
+
+import org.apache.isis.client.kroviz.ui.core.Constants
+import org.apache.isis.client.kroviz.ui.core.UiManager
+import org.apache.isis.client.kroviz.utils.StringUtils
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class StringUtilsTest {
+ @Test
+ fun testShortTitle() {
+ // given
+ UiManager.login(Constants.demoUrl, Constants.demoUser, Constants.demoPass)
+ val url = "http://localhost:8080/restful/domain-types/demo.JavaLangStrings/collections/entities"
+
+ // when
+ val actual = StringUtils.shortTitle(url, UiManager.getUrl())
+
+ // then
+ val expected = "/domain-types/demo.JavaLangStrings/collections/entities"
+ assertEquals(expected, actual)
+ }
+
+}