From a8591c81c4273af54543f48ebec3fe0a31b85b76 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 12 Apr 2024 10:00:59 +0300 Subject: [PATCH 1/3] Deep traversal algorithm: find first from root --- .../core/algorithms/DeepTraversal.java | 92 +++++++++++++++++++ .../core/algorithms/DeepTraversalTest.java | 53 +++++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java create mode 100644 src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java new file mode 100644 index 0000000..e850577 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java @@ -0,0 +1,92 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms; + +import org.cqfn.astranaut.core.Node; + +/** + * Performs a deep traversal of the syntax tree. + * + * @since 1.1.5 + */ +public class DeepTraversal { + /** + * The root node of the tree being traversed. + */ + private final Node root; + + /** + * Constructor. + * @param root The root node of the tree being traversed + */ + public DeepTraversal(final Node root) { + this.root = root; + } + + /** + * Processes nodes starting from the root. Processes a node first. + * If the stopping criterion is not reached, recursively processes all children of it, + * starting from the first one. Once a node is found that satisfies the criterion, + * stops traversal. + * @param visitor Visitor that processes nodes + * @return Found node or {@code null} if no node is found + */ + public Node findFirstFromRoot(final Visitor visitor) { + return DeepTraversal.findFirstFromRoot(this.root, visitor); + } + + /** + * Recursive method that implements the "Find the first starting from the root" algorithm. + * @param node Current node to be processed + * @param visitor Visitor that processes nodes + * @return Found node or {@code null} if no node is found + */ + private static Node findFirstFromRoot(final Node node, final Visitor visitor) { + Node result = null; + final boolean stop = visitor.process(node); + if (stop) { + result = node; + } else { + final int count = node.getChildCount(); + for (int index = 0; index < count && result == null; index = index + 1) { + result = DeepTraversal.findFirstFromRoot(node.getChild(index), visitor); + } + } + return result; + } + + /** + * Payload interface for the traversal algorithm. + * + * @since 1.1.5 + */ + public interface Visitor { + /** + * Processes a node. + * @param node Node + * @return Whether to stop traversal after node processing + */ + boolean process(Node node); + } +} diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java new file mode 100644 index 0000000..6dbf680 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java @@ -0,0 +1,53 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms; + +import org.cqfn.astranaut.core.DraftNode; +import org.cqfn.astranaut.core.Node; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Testing {@link DeepTraversal} class. + * + * @since 1.1.5 + */ +class DeepTraversalTest { + @Test + void testFindFirstFromRoot() { + final Node root = DraftNode.createByDescription("A(B,C,D(E<\"test\">,F<\"xxx\">)))"); + final DeepTraversal traversal = new DeepTraversal(root); + final Node node = traversal.findFirstFromRoot(node1 -> !node1.getData().isEmpty()); + Assertions.assertNotNull(node); + Assertions.assertEquals("E", node.getTypeName()); + } + + @Test + void testNotFoundFirstFromRoot() { + final Node root = DraftNode.createByDescription("A(B,C,D)"); + final DeepTraversal traversal = new DeepTraversal(root); + final Node node = traversal.findFirstFromRoot(node1 -> !node1.getData().isEmpty()); + Assertions.assertNull(node); + } +} From c9c93059eee54178e178f5ee3162424e798fe090 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 12 Apr 2024 13:11:24 +0300 Subject: [PATCH 2/3] Deep traversal algorithm: find all from root --- .../core/algorithms/DeepTraversal.java | 47 ++++++++++++++++--- .../core/algorithms/DeepTraversalTest.java | 29 ++++++++++-- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java index e850577..ae79d69 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java @@ -23,6 +23,8 @@ */ package org.cqfn.astranaut.core.algorithms; +import java.util.ArrayList; +import java.util.List; import org.cqfn.astranaut.core.Node; /** @@ -48,21 +50,36 @@ public DeepTraversal(final Node root) { * Processes nodes starting from the root. Processes a node first. * If the stopping criterion is not reached, recursively processes all children of it, * starting from the first one. Once a node is found that satisfies the criterion, - * stops traversal. + * stops traversal.
+ * And yes, you can use this algorithm not only to find nodes, but also just to traverse + * the tree in the specific order. * @param visitor Visitor that processes nodes * @return Found node or {@code null} if no node is found */ - public Node findFirstFromRoot(final Visitor visitor) { - return DeepTraversal.findFirstFromRoot(this.root, visitor); + public Node findFirst(final Visitor visitor) { + return DeepTraversal.findFirst(this.root, visitor); } /** - * Recursive method that implements the "Find the first starting from the root" algorithm. + * Processes nodes starting from the root. + * If a node matches the criterion, adds it to the set and does not check + * the children of this node, otherwise it does. + * @param visitor Visitor that processes nodes + * @return List of found nodes (can be empty, but not {@code null}) + */ + public List findAll(final Visitor visitor) { + final List list = new ArrayList<>(0); + DeepTraversal.findAll(this.root, visitor, list); + return list; + } + + /** + * Recursive method that implements the "Find first starting from the root" algorithm. * @param node Current node to be processed * @param visitor Visitor that processes nodes * @return Found node or {@code null} if no node is found */ - private static Node findFirstFromRoot(final Node node, final Visitor visitor) { + private static Node findFirst(final Node node, final Visitor visitor) { Node result = null; final boolean stop = visitor.process(node); if (stop) { @@ -70,12 +87,30 @@ private static Node findFirstFromRoot(final Node node, final Visitor visitor) { } else { final int count = node.getChildCount(); for (int index = 0; index < count && result == null; index = index + 1) { - result = DeepTraversal.findFirstFromRoot(node.getChild(index), visitor); + result = DeepTraversal.findFirst(node.getChild(index), visitor); } } return result; } + /** + * Recursive method that implements the "Find all starting from the root" algorithm. + * @param node Current node to be processed + * @param visitor Visitor that processes nodes + * @param list List of found nodes + */ + private static void findAll(final Node node, final Visitor visitor, final List list) { + final boolean found = visitor.process(node); + if (found) { + list.add(node); + } else { + final int count = node.getChildCount(); + for (int index = 0; index < count; index = index + 1) { + DeepTraversal.findAll(node.getChild(index), visitor, list); + } + } + } + /** * Payload interface for the traversal algorithm. * diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java index 6dbf680..a2a6a19 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java @@ -23,6 +23,7 @@ */ package org.cqfn.astranaut.core.algorithms; +import java.util.List; import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Node; import org.junit.jupiter.api.Assertions; @@ -35,19 +36,37 @@ */ class DeepTraversalTest { @Test - void testFindFirstFromRoot() { - final Node root = DraftNode.createByDescription("A(B,C,D(E<\"test\">,F<\"xxx\">)))"); + void testFindFirst() { + final Node root = DraftNode.createByDescription("A(B,C,D(E<\"eee\">,F<\"fff\">)))"); final DeepTraversal traversal = new DeepTraversal(root); - final Node node = traversal.findFirstFromRoot(node1 -> !node1.getData().isEmpty()); + final Node node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); Assertions.assertNotNull(node); Assertions.assertEquals("E", node.getTypeName()); } @Test - void testNotFoundFirstFromRoot() { + void testNotFoundFirst() { final Node root = DraftNode.createByDescription("A(B,C,D)"); final DeepTraversal traversal = new DeepTraversal(root); - final Node node = traversal.findFirstFromRoot(node1 -> !node1.getData().isEmpty()); + final Node node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); Assertions.assertNull(node); } + + @Test + void testFindAll() { + final Node root = DraftNode.createByDescription("A(B,C<\"ccc\">,D(E<\"eee\">)))"); + final DeepTraversal traversal = new DeepTraversal(root); + final List list = traversal.findAll(node1 -> !node1.getData().isEmpty()); + Assertions.assertNotNull(list); + Assertions.assertEquals(2, list.size()); + } + + @Test + void testFindNothing() { + final Node root = DraftNode.createByDescription("A(X,Y,Z)"); + final DeepTraversal traversal = new DeepTraversal(root); + final List list = traversal.findAll(node1 -> !node1.getData().isEmpty()); + Assertions.assertNotNull(list); + Assertions.assertTrue(list.isEmpty()); + } } From e048840a0aea3b49d7901989e24afa7cdb02dda7 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 17 Apr 2024 16:16:14 +0300 Subject: [PATCH 3/3] Make optional return value instead of nullable --- .../cqfn/astranaut/core/algorithms/DeepTraversal.java | 7 ++++--- .../astranaut/core/algorithms/DeepTraversalTest.java | 11 ++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java index ae79d69..f24d99f 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.cqfn.astranaut.core.Node; /** @@ -54,10 +55,10 @@ public DeepTraversal(final Node root) { * And yes, you can use this algorithm not only to find nodes, but also just to traverse * the tree in the specific order. * @param visitor Visitor that processes nodes - * @return Found node or {@code null} if no node is found + * @return Found node (optional) */ - public Node findFirst(final Visitor visitor) { - return DeepTraversal.findFirst(this.root, visitor); + public Optional findFirst(final Visitor visitor) { + return Optional.ofNullable(DeepTraversal.findFirst(this.root, visitor)); } /** diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java index a2a6a19..3b76c48 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java @@ -24,6 +24,7 @@ package org.cqfn.astranaut.core.algorithms; import java.util.List; +import java.util.Optional; import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Node; import org.junit.jupiter.api.Assertions; @@ -39,17 +40,17 @@ class DeepTraversalTest { void testFindFirst() { final Node root = DraftNode.createByDescription("A(B,C,D(E<\"eee\">,F<\"fff\">)))"); final DeepTraversal traversal = new DeepTraversal(root); - final Node node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); - Assertions.assertNotNull(node); - Assertions.assertEquals("E", node.getTypeName()); + final Optional node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); + Assertions.assertTrue(node.isPresent()); + Assertions.assertEquals("E", node.get().getTypeName()); } @Test void testNotFoundFirst() { final Node root = DraftNode.createByDescription("A(B,C,D)"); final DeepTraversal traversal = new DeepTraversal(root); - final Node node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); - Assertions.assertNull(node); + final Optional node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); + Assertions.assertFalse(node.isPresent()); } @Test