From 99b54e54d5e8bacab82c8c7e912c4389524cfad2 Mon Sep 17 00:00:00 2001 From: Damian Pedra Date: Wed, 6 Mar 2024 16:24:47 -0300 Subject: [PATCH 1/8] Publish 5.5.0-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a755988c07..42a2eddc61 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ val ivyLocal = Resolver.file("ivy", file(Path.userHome.absolutePath + "/.ivy2/lo name := "amf-core" ThisBuild / scalaVersion := "2.12.15" -ThisBuild / version := "5.4.9" +ThisBuild / version := "5.5.0-SNAPSHOT" publish := {} From f004bfc422b342f7d1ec9fe7eba26bfa6a558754 Mon Sep 17 00:00:00 2001 From: Damian Pedra Date: Wed, 6 Mar 2024 16:51:11 -0300 Subject: [PATCH 2/8] Update codeowners name from amf to calypso --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f633ffef98..12616e0e8f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -#GUSINFO:MS AMF,MS AMF +#GUSINFO:MS Calypso,MS Calypso * @aml-org/team-amf From e4d77a33997918748e416cdded94876a115cac74 Mon Sep 17 00:00:00 2001 From: Damian Pedra Date: Tue, 12 Mar 2024 17:48:45 -0300 Subject: [PATCH 3/8] W:12689961: Annotation for ServerVariable --- .../annotations/DeclaredServerVariable.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 shared/src/main/scala/amf/core/internal/annotations/DeclaredServerVariable.scala diff --git a/shared/src/main/scala/amf/core/internal/annotations/DeclaredServerVariable.scala b/shared/src/main/scala/amf/core/internal/annotations/DeclaredServerVariable.scala new file mode 100644 index 0000000000..99e6629a19 --- /dev/null +++ b/shared/src/main/scala/amf/core/internal/annotations/DeclaredServerVariable.scala @@ -0,0 +1,16 @@ +package amf.core.internal.annotations + +import amf.core.client.scala.model.domain._ + +/* + This annotation is used in Async 2.4 to differentiate parameter declarations from Server Variable declarations + */ +case class DeclaredServerVariable() extends SerializableAnnotation with PerpetualAnnotation { + override val name: String = "declared-server-variable" + override val value: String = "" +} + +object DeclaredServerVariable extends AnnotationGraphLoader { + override def unparse(value: String, objects: Map[String, AmfElement]): Option[Annotation] = + Some(DeclaredServerVariable()) +} From a120a44c63f6c2a4601fade6a94bf64920206cb7 Mon Sep 17 00:00:00 2001 From: Javier Isoldi Date: Wed, 13 Mar 2024 19:52:32 -0300 Subject: [PATCH 4/8] W-15207889. Some performance improvements. --- .nvmrc | 1 + .sdkmanrc | 5 +++ .../amf/core/internal/remote/Cache.scala | 39 +++++++++++++++---- .../transform/stages/UrlShortenerStage.scala | 23 ++++++----- 4 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 .nvmrc create mode 100644 .sdkmanrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..e0325e5adb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v16.17.1 diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000000..113d5f7f5b --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,5 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=17.0.10-tem +scala=2.12.15 +sbt=1.7.3 \ No newline at end of file diff --git a/shared/src/main/scala/amf/core/internal/remote/Cache.scala b/shared/src/main/scala/amf/core/internal/remote/Cache.scala index 7a064c5ec9..763f036c6a 100644 --- a/shared/src/main/scala/amf/core/internal/remote/Cache.scala +++ b/shared/src/main/scala/amf/core/internal/remote/Cache.scala @@ -19,15 +19,40 @@ class Cache { dependencyGraph.update(to, fromNodes.+(from)) } - protected def findCycles(node: String, acc: Set[String] = Set()): Boolean = { - if (acc.contains(node)) { - true - } else { - val sources = dependencyGraph.getOrElse(node, Set()) - sources.exists { source => - findCycles(source, acc + node) + protected def findCycles(initialNode: String): Boolean = { + // Mutable collections are used to improve performance + val allVisited = mutable.Set[String]() + val visitedStack = mutable.Stack[String]() + + def innerFindCycles(currentNode: String): Boolean = { + if (visitedStack.contains(currentNode)) { + // There is a cycle + return true + } + + if (allVisited.contains(currentNode)) { + // This node has already been visited + return false + } + + allVisited.add(currentNode) + visitedStack.push(currentNode) + + val newNodes = dependencyGraph.getOrElse(currentNode, Set()) + for (newNode <- newNodes) { + if (findCycles(newNode)) { + // Cycle found + visitedStack.pop() + return true + } } + + // So far no cycles + visitedStack.pop() + false } + + innerFindCycles(initialNode) } protected def beforeLast(elms: List[String]): Option[String] = { diff --git a/shared/src/main/scala/amf/core/internal/transform/stages/UrlShortenerStage.scala b/shared/src/main/scala/amf/core/internal/transform/stages/UrlShortenerStage.scala index 81e66cbc1a..af94a8af21 100644 --- a/shared/src/main/scala/amf/core/internal/transform/stages/UrlShortenerStage.scala +++ b/shared/src/main/scala/amf/core/internal/transform/stages/UrlShortenerStage.scala @@ -1,13 +1,12 @@ package amf.core.internal.transform.stages import amf.core.client.scala.AMFGraphConfiguration import amf.core.client.scala.errorhandling.AMFErrorHandler -import amf.core.internal.metamodel.Type.{ArrayLike, Iri} -import amf.core.internal.metamodel.domain.LinkableElementModel import amf.core.client.scala.model.document.BaseUnit import amf.core.client.scala.model.domain._ import amf.core.client.scala.transform.TransformationStep -import amf.core.internal.parser.domain.FieldEntry import amf.core.client.scala.vocabulary.Namespace +import amf.core.internal.metamodel.Type.{ArrayLike, Iri} +import amf.core.internal.metamodel.domain.LinkableElementModel import amf.core.internal.parser.domain.{Annotations, FieldEntry, Value} import scala.collection.mutable @@ -19,17 +18,23 @@ class UrlShortenerStage() extends TransformationStep { errorHandler: AMFErrorHandler, configuration: AMFGraphConfiguration ): BaseUnit = { - val ids: Set[String] = Set(model.id) ++ obtainNestedReferenceIds(model) + val ids: mutable.Set[String] = mutable.Set() + collectNestedReferenceIdsInSet(model, ids) + ids.add(model.id) + shorten(model, ids) model.withId(base) } - private def obtainNestedReferenceIds[T <: BaseUnit](model: T): Seq[String] = { - val ids = model.references.map(_.id) - ids ++ model.references.flatMap(obtainNestedReferenceIds) + private def collectNestedReferenceIdsInSet[T <: BaseUnit](model: T, set: mutable.Set[String]): Unit = { + if (!set.contains(model.id)) { + set += model.id + + model.references.foreach(collectNestedReferenceIdsInSet(_, set)) + } } - def shorten(element: AmfElement, ids: Set[String]): Unit = { + def shorten(element: AmfElement, ids: Traversable[String]): Unit = { element match { case o: AmfObject => val shorthenId = shortener.shorten(o.id) @@ -64,7 +69,7 @@ class UrlShortenerStage() extends TransformationStep { shorten(element.annotations) } - private def shortenIriValue(element: AmfElement, ids: Set[String]): AmfElement = { + private def shortenIriValue(element: AmfElement, ids: Traversable[String]): AmfElement = { val stringValue = element.toString if (ids.exists(i => stringValue.startsWith(i))) AmfScalar(shortener.shorten(stringValue), element.annotations) From c86af8b35f261541162778684ad1c9c76cb25703 Mon Sep 17 00:00:00 2001 From: arielmirra Date: Thu, 4 Apr 2024 11:02:51 -0300 Subject: [PATCH 5/8] publish 5.5.0-RC.0 --- Jenkinsfile | 1 + build.sbt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e137057ec3..6c3e0218f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,6 +62,7 @@ pipeline { anyOf { branch 'master' branch 'develop' + branch 'release/*' } } steps { diff --git a/build.sbt b/build.sbt index 42a2eddc61..c105f83635 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ val ivyLocal = Resolver.file("ivy", file(Path.userHome.absolutePath + "/.ivy2/lo name := "amf-core" ThisBuild / scalaVersion := "2.12.15" -ThisBuild / version := "5.5.0-SNAPSHOT" +ThisBuild / version := "5.5.0-RC.0" publish := {} From 7d60ec9d2029b51aa83713de65a7a6f189558453 Mon Sep 17 00:00:00 2001 From: Javier Isoldi Date: Thu, 11 Apr 2024 14:43:48 -0300 Subject: [PATCH 6/8] W-15092041. Find cycle algorithm refactored. --- .../amf/core/internal/remote/Cache.scala | 39 +----- .../internal/utils/GraphCycleDetector.scala | 41 ++++++ .../utils/GraphCycleDetectorTest.scala | 130 ++++++++++++++++++ 3 files changed, 173 insertions(+), 37 deletions(-) create mode 100644 shared/src/main/scala/amf/core/internal/utils/GraphCycleDetector.scala create mode 100644 shared/src/test/scala/amf/core/internal/utils/GraphCycleDetectorTest.scala diff --git a/shared/src/main/scala/amf/core/internal/remote/Cache.scala b/shared/src/main/scala/amf/core/internal/remote/Cache.scala index 763f036c6a..a02f4b1823 100644 --- a/shared/src/main/scala/amf/core/internal/remote/Cache.scala +++ b/shared/src/main/scala/amf/core/internal/remote/Cache.scala @@ -1,6 +1,7 @@ package amf.core.internal.remote import amf.core.client.scala.model.document.BaseUnit +import amf.core.internal.utils.GraphCycleDetector import scala.collection.mutable import scala.concurrent.{ExecutionContext, Future} @@ -19,42 +20,6 @@ class Cache { dependencyGraph.update(to, fromNodes.+(from)) } - protected def findCycles(initialNode: String): Boolean = { - // Mutable collections are used to improve performance - val allVisited = mutable.Set[String]() - val visitedStack = mutable.Stack[String]() - - def innerFindCycles(currentNode: String): Boolean = { - if (visitedStack.contains(currentNode)) { - // There is a cycle - return true - } - - if (allVisited.contains(currentNode)) { - // This node has already been visited - return false - } - - allVisited.add(currentNode) - visitedStack.push(currentNode) - - val newNodes = dependencyGraph.getOrElse(currentNode, Set()) - for (newNode <- newNodes) { - if (findCycles(newNode)) { - // Cycle found - visitedStack.pop() - return true - } - } - - // So far no cycles - visitedStack.pop() - false - } - - innerFindCycles(initialNode) - } - protected def beforeLast(elms: List[String]): Option[String] = { val lastTwo = elms.takeRight(2) if (lastTwo.size == 2) { @@ -70,7 +35,7 @@ class Cache { beforeLast(context.history) foreach { from => addFromToEdge(from, url) } - if (findCycles(url)) { + if (GraphCycleDetector.hasCycles(dependencyGraph, url)) { if (cache(url).isCompleted) { cache(url) } else { diff --git a/shared/src/main/scala/amf/core/internal/utils/GraphCycleDetector.scala b/shared/src/main/scala/amf/core/internal/utils/GraphCycleDetector.scala new file mode 100644 index 0000000000..c6ee1aed2d --- /dev/null +++ b/shared/src/main/scala/amf/core/internal/utils/GraphCycleDetector.scala @@ -0,0 +1,41 @@ +package amf.core.internal.utils + +import scala.annotation.tailrec +import scala.collection.mutable + +object GraphCycleDetector { + type Graph[N] = mutable.Map[N, Set[N]] + + private case class NodeWithBranch[N](node: N, branch: List[N]) + + def hasCycles[N](graph: Graph[N], initialNode: N): Boolean = { + // Mutable collections are used to improve performance + val allVisited = mutable.Set[N]() + + @tailrec + def innerHasCycles(pendingNodes: List[NodeWithBranch[N]]): Boolean = pendingNodes match { + case Nil => false + case ::(NodeWithBranch(currentNode, currentBranch), remainingNodes) => + if (currentBranch.contains(currentNode)) { + // There is a cycle + return true + } + + if (allVisited.contains(currentNode)) { + // This node has already been visited + innerHasCycles(remainingNodes) + } else { + allVisited.add(currentNode) + val nextBranch: List[N] = currentNode +: currentBranch + + val newNodes = graph.getOrElse(currentNode, Set()).toList + val newNodesWithBranches = newNodes.map { NodeWithBranch(_, nextBranch) } + + val newPendingNodes: List[NodeWithBranch[N]] = newNodesWithBranches ::: pendingNodes + innerHasCycles(newPendingNodes) + } + } + + innerHasCycles(List(NodeWithBranch(initialNode, List()))) + } +} diff --git a/shared/src/test/scala/amf/core/internal/utils/GraphCycleDetectorTest.scala b/shared/src/test/scala/amf/core/internal/utils/GraphCycleDetectorTest.scala new file mode 100644 index 0000000000..1e0c7b35ab --- /dev/null +++ b/shared/src/test/scala/amf/core/internal/utils/GraphCycleDetectorTest.scala @@ -0,0 +1,130 @@ +package amf.core.internal.utils + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +import scala.collection.mutable + +class GraphCycleDetectorTest extends AnyFunSuite with Matchers { + + test("Simple linear graph should not have cycles") { + val graph = mutable.Map( + "A" -> Set("B"), + "B" -> Set("C"), + "C" -> Set("D") + ) + + GraphCycleDetector.hasCycles(graph, "A") shouldBe false + } + + test("Simple linear graph should not have cycles starting in other node") { + val graph = mutable.Map( + "A" -> Set("B"), + "B" -> Set("C"), + "C" -> Set("D") + ) + + GraphCycleDetector.hasCycles(graph, "B") shouldBe false + } + + test("Graph with simple cycle should be detected from A") { + val graph = mutable.Map( + "A" -> Set("B"), + "B" -> Set("C"), + "C" -> Set("A") + ) + + GraphCycleDetector.hasCycles(graph, "A") shouldBe true + } + + test("Graph with simple cycle should be detected from B") { + val graph = mutable.Map( + "A" -> Set("B"), + "B" -> Set("C"), + "C" -> Set("A") + ) + + GraphCycleDetector.hasCycles(graph, "B") shouldBe true + } + + test("Graph with branches should not have cycles") { + val graph = mutable.Map( + "A" -> Set("B"), + "A" -> Set("C"), + "B" -> Set("D"), + "B" -> Set("E"), + "C" -> Set("F"), + "C" -> Set("G") + ) + + GraphCycleDetector.hasCycles(graph, "A") shouldBe false + } + + test("Graph with shared parents should not have cycles") { + val graph = mutable.Map( + "A" -> Set("B"), + "A" -> Set("C"), + "B" -> Set("D"), + "B" -> Set("E"), + "C" -> Set("D"), + "C" -> Set("E"), + "D" -> Set("F"), + "E" -> Set("F"), + "F" -> Set("G") + ) + + GraphCycleDetector.hasCycles(graph, "A") shouldBe false + } + + test("Graph with shared parents and cycles should be detected") { + val graph = mutable.Map( + "A" -> Set("B"), + "A" -> Set("C"), + "B" -> Set("D"), + "B" -> Set("E"), + "C" -> Set("D"), + "C" -> Set("E"), + "D" -> Set("F"), + "E" -> Set("F"), + "F" -> Set("A") + ) + + GraphCycleDetector.hasCycles(graph, "A") shouldBe true + } + + test("Graph with shared parents and cycles should be detected starting from other node") { + val graph = mutable.Map( + "A" -> Set("B"), + "A" -> Set("C"), + "B" -> Set("D"), + "B" -> Set("E"), + "C" -> Set("D"), + "C" -> Set("E"), + "D" -> Set("F"), + "E" -> Set("F"), + "F" -> Set("A") + ) + + GraphCycleDetector.hasCycles(graph, "B") shouldBe true + } + + test("Graph with shared parents and cycles should not be detected is starting from node without cycles") { + val graph = mutable.Map( + "A" -> Set("B"), + "A" -> Set("C"), + "B" -> Set("D"), + "B" -> Set("E"), + "B" -> Set("G"), + "G" -> Set("H"), + "G" -> Set("I"), + "C" -> Set("D"), + "C" -> Set("E"), + "D" -> Set("F"), + "E" -> Set("F"), + "F" -> Set("A") + ) + + GraphCycleDetector.hasCycles(graph, "G") shouldBe false + } + +} From 6a7f25852aca38008cee663be4ac8f5d5816460a Mon Sep 17 00:00:00 2001 From: arielmirra Date: Thu, 11 Apr 2024 15:37:11 -0300 Subject: [PATCH 7/8] publish amf-core 5.5.0-RC.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c105f83635..3017c3b78f 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ val ivyLocal = Resolver.file("ivy", file(Path.userHome.absolutePath + "/.ivy2/lo name := "amf-core" ThisBuild / scalaVersion := "2.12.15" -ThisBuild / version := "5.5.0-RC.0" +ThisBuild / version := "5.5.0-RC.1" publish := {} From 0ba116a92fa452e44f1501d74c898bbb2f8a96c7 Mon Sep 17 00:00:00 2001 From: arielmirra Date: Fri, 12 Apr 2024 11:58:06 -0300 Subject: [PATCH 8/8] publish 5.5.0 --- Jenkinsfile | 1 - build.sbt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6c3e0218f2..e137057ec3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,7 +62,6 @@ pipeline { anyOf { branch 'master' branch 'develop' - branch 'release/*' } } steps { diff --git a/build.sbt b/build.sbt index 3017c3b78f..a1ba72bf9d 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ val ivyLocal = Resolver.file("ivy", file(Path.userHome.absolutePath + "/.ivy2/lo name := "amf-core" ThisBuild / scalaVersion := "2.12.15" -ThisBuild / version := "5.5.0-RC.1" +ThisBuild / version := "5.5.0" publish := {}