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..f24d99f --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DeepTraversal.java @@ -0,0 +1,128 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.Optional; +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.
+ * 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 (optional) + */ + public Optional findFirst(final Visitor visitor) { + return Optional.ofNullable(DeepTraversal.findFirst(this.root, visitor)); + } + + /** + * 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 findFirst(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.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. + * + * @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..3b76c48 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DeepTraversalTest.java @@ -0,0 +1,73 @@ +/* + * 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 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; +import org.junit.jupiter.api.Test; + +/** + * Testing {@link DeepTraversal} class. + * + * @since 1.1.5 + */ +class DeepTraversalTest { + @Test + void testFindFirst() { + final Node root = DraftNode.createByDescription("A(B,C,D(E<\"eee\">,F<\"fff\">)))"); + final DeepTraversal traversal = new DeepTraversal(root); + 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 Optional node = traversal.findFirst(node1 -> !node1.getData().isEmpty()); + Assertions.assertFalse(node.isPresent()); + } + + @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()); + } +}