
software.amazon.smithy.syntax.TreeCursor Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.smithy.syntax;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;
import software.amazon.smithy.model.FromSourceLocation;
import software.amazon.smithy.model.SourceLocation;
/**
* Externally traverses a {@link TokenTree} to provide access to parents and siblings.
*
* @see TokenTree#zipper()
*/
public final class TreeCursor implements FromSourceLocation {
private final TokenTree tree;
private final TreeCursor parent;
private TreeCursor(TokenTree tree, TreeCursor parent) {
this.tree = tree;
this.parent = parent;
}
/**
* Create a TreeCursor from the given TokenTree, treating it as the root of the tree.
*
* @param tree Tree to create a cursor from.
* @return Returns the created cursor.
*/
public static TreeCursor of(TokenTree tree) {
return new TreeCursor(tree, null);
}
@Override
public SourceLocation getSourceLocation() {
return getTree().getSourceLocation();
}
/**
* Get the wrapped {@link TokenTree}.
*
* @return Return the token tree.
*/
public TokenTree getTree() {
return tree;
}
/**
* Get the parent cursor, or null if not present.
*
* @return Nullable parent cursor.
*/
public TreeCursor getParent() {
return parent;
}
/**
* Get the root of the tree, returning itself if the tree has no parent.
*
* @return Non-nullable root tree.
*/
public TreeCursor getRoot() {
TreeCursor current = this;
while (current.parent != null) {
current = current.parent;
}
return current;
}
/**
* Get a list of tree cursors that lead up to the current tree, starting from the root as the first element, and
* including the current tree as the last element.
*
* @return Returns the path to the current tree from the root.
*/
public List getPathToCursor() {
List path = new ArrayList<>();
TreeCursor current = this;
do {
path.add(current);
current = current.parent;
} while (current != null);
Collections.reverse(path);
return path;
}
/**
* Get the previous sibling of this tree, if present.
*
* @return Return the nullable previous sibling.
*/
public TreeCursor getPreviousSibling() {
return getSibling(-1);
}
/**
* Get the next sibling of this tree, if present.
*
* @return Return the nullable next sibling.
*/
public TreeCursor getNextSibling() {
return getSibling(1);
}
private TreeCursor getSibling(int offset) {
if (parent == null) {
return null;
}
List siblings = parent.getTree().getChildren();
int myPosition = siblings.indexOf(this.tree);
if (myPosition == -1) {
return null;
}
int target = myPosition + offset;
if (target < 0 || target > siblings.size() - 1) {
return null;
}
return new TreeCursor(siblings.get(target), parent);
}
/**
* Get all children of the tree as a list of cursors.
*
* @return Return the cursors to each child.
*/
public List getChildren() {
List result = new ArrayList<>(getTree().getChildren().size());
for (TokenTree child : tree.getChildren()) {
result.add(new TreeCursor(child, this));
}
return result;
}
/**
* Get a stream of child cursors.
*
* @return Returns children as a stream.
*/
Stream children() {
return getTree().getChildren().stream().map(child -> new TreeCursor(child, this));
}
/**
* Get direct children from the current tree of a specific type.
*
* @param types Types of children to get.
* @return Returns the collected children, or an empty list.
*/
public List getChildrenByType(TreeType... types) {
List result = new ArrayList<>();
for (int i = 0; i < tree.getChildren().size(); i++) {
TokenTree child = tree.getChildren().get(i);
for (TreeType type : types) {
if (child.getType() == type) {
result.add(new TreeCursor(child, this));
break;
}
}
}
return result;
}
/**
* Get the first child of the wrapped tree.
*
* @return Return the first child, or null if the tree has no children.
*/
public TreeCursor getFirstChild() {
if (tree.getChildren().isEmpty()) {
return null;
} else {
return new TreeCursor(tree.getChildren().get(0), this);
}
}
/**
* Get the first child of the wrapped tree with the given type.
*
* @param type Child type to get.
* @return Return the first child, or null if a matching child is not found.
*/
public TreeCursor getFirstChild(TreeType type) {
for (TokenTree child : getTree().getChildren()) {
if (child.getType() == type) {
return new TreeCursor(child, this);
}
}
return null;
}
/**
* Get the last child of the wrapped tree.
*
* @return Return the last child, or null if the tree has no children.
*/
public TreeCursor getLastChild() {
if (tree.getChildren().isEmpty()) {
return null;
} else {
return new TreeCursor(tree.getChildren().get(tree.getChildren().size() - 1), this);
}
}
/**
* Get the last child of the wrapped tree with the given type.
*
* @param type Child type to get.
* @return Return the last child, or null if a matching child is not found.
*/
public TreeCursor getLastChild(TreeType type) {
List children = tree.getChildren();
ListIterator iterator = children.listIterator(children.size());
while (iterator.hasPrevious()) {
TokenTree child = iterator.previous();
if (child.getType() == type) {
return new TreeCursor(child, this);
}
}
return null;
}
/**
* Recursively find every node in the tree that has the given {@code TreeType}.
*
* @param types Types of children to return.
* @return Returns the matching tree cursors.
*/
public List findChildrenByType(TreeType... types) {
return findChildren(c -> {
TreeType treeType = c.getTree().getType();
for (TreeType type : types) {
if (treeType == type) {
return true;
}
}
return false;
});
}
/**
* Recursively finds every node in the tree that matches the given predicate.
*
* @param predicate Predicate to test each recursive child against.
* @return Returns the matching tree cursors, or an empty list if none are found.
*/
private List findChildren(Predicate predicate) {
List result = new ArrayList<>();
recursiveFindChildren(this, result, predicate);
return result;
}
private void recursiveFindChildren(TreeCursor cursor, List cursors, Predicate predicate) {
for (TreeCursor tree : cursor.getChildren()) {
if (predicate.test(tree)) {
cursors.add(tree);
}
recursiveFindChildren(tree, cursors, predicate);
}
}
/**
* Find the innermost tree that contains the given coordinates.
*
* @param line Line to find.
* @param column Column to find.
* @return Returns the innermost tree that contains the coordinates.
*/
public TreeCursor findAt(int line, int column) {
TreeCursor current = this;
outer: while (true) {
for (TreeCursor child : current.getChildren()) {
TokenTree childTree = child.getTree();
int startLine = childTree.getStartLine();
int endLine = childTree.getEndLine();
int startColumn = childTree.getStartColumn();
int endColumn = childTree.getEndColumn();
boolean isMatch = false;
if (line == startLine && line == endLine) {
// Column span checks are exclusive to not match the ends of tokens.
isMatch = column >= startColumn && column < endColumn;
} else if (line == startLine && column >= startColumn) {
isMatch = true;
} else if (line == endLine && column < endColumn) {
isMatch = true;
} else if (line > startLine && line < endLine) {
isMatch = true;
}
if (isMatch) {
current = child;
continue outer;
}
}
return current;
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
TreeCursor cursor = (TreeCursor) o;
return getTree().equals(cursor.getTree()) && Objects.equals(getParent(), cursor.getParent());
}
@Override
public int hashCode() {
return Objects.hash(getTree(), getParent());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy