package org.codefx.libfx.collection.tree.navigate; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import javafx.scene.Node; import javafx.scene.Parent; /** * A {@link TreeNavigator} for a JavaFX scene graph. * <p> * This implementation is thread-safe in the sense that individual method calls will not fail if the scene graph is * changed concurrently. But it can not prevent return values from getting stale so chaining calls might lead to * unexpected results, e.g.: * * <pre> * Node node = ... * if (getParent(node).isPresent()) { * // the node has a parent, so it should have a child index, too; * // but the scene graph may have changed, so 'indexPresent' may be false * boolean indexPresent = getChildIndex(node).isPresent(); * } * </pre> * Similarly: * * <pre> * Node parent = ... * Optional<Node> child1 = getChild(parent, 0); * Optional<Node> child2 = getChild(parent, 0); * // if the scene graph changed between the two calls, this may be false * boolean sameChildren = child1.equals(child2); * </pre> */ public class SceneGraphNavigator implements TreeNavigator<Node> { @Override public Optional<Node> getParent(Node child) { Objects.requireNonNull(child, "The argument 'child' must not be null."); return Optional.ofNullable(child.getParent()); } @Override public OptionalInt getChildIndex(Node node) { Objects.requireNonNull(node, "The argument 'node' must not be null."); Parent parent = node.getParent(); if (parent == null) return OptionalInt.empty(); int childIndex = parent.getChildrenUnmodifiable().indexOf(node); if (childIndex == -1) return OptionalInt.empty(); return OptionalInt.of(childIndex); } @Override public int getChildrenCount(Node parent) { Objects.requireNonNull(parent, "The argument 'parent' must not be null."); if (!(parent instanceof Parent)) return 0; return ((Parent) parent).getChildrenUnmodifiable().size(); } @Override public Optional<Node> getChild(Node parent, int childIndex) { Objects.requireNonNull(parent, "The argument 'parent' must not be null."); if (childIndex < 0) throw new IllegalArgumentException("The argument 'childIndex' must be non-negative."); if (!(parent instanceof Parent)) return Optional.empty(); return getChild((Parent) parent, childIndex); } private static Optional<Node> getChild(Parent parent, int childIndex) { if (parent.getChildrenUnmodifiable().size() <= childIndex) return Optional.empty(); try { // even though we checked first, due to threading this might fail return Optional.of(parent.getChildrenUnmodifiable().get(childIndex)); } catch (IndexOutOfBoundsException ex) { return Optional.empty(); } } }