package org.codefx.libfx.collection.tree.navigate; import java.awt.Component; import java.awt.Container; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import javax.swing.JComponent; /** * A {@link TreeNavigator} for a Swing component hierarchy. * <p> * Note that {@link #getParent(JComponent) getParent} will return an (optional) {@link JComponent}. This is not possible * if the parent is not of a subtype (e.g. a {@link javax.swing.JFrame JFrame}) in which case an empty {@code Optional} * is returned. Similarly, children of {@code JComponent}s might not be {@code JComponent}s themselves. For this reason * {@link #getChild(JComponent, int) getChild} will return an empty {@code Optional} for a child which is no * {@code JComponent}. If this is undesired, consider using a {@link ComponentHierarchyNavigator} instead. * <p> * This implementation is thread-safe in the sense that individual method calls will not fail if the component hierarchy * is changed concurrently. But it can not prevent return values from getting stale so chaining calls might lead to * unexpected results, e.g.: * * <pre> * JComponent component = ... * if (getParent(component).isPresent()) { * // the component has a parent, so it should have a child index, too; * // but the component hierarchy may have changed, so 'indexPresent' may be false * boolean indexPresent = getChildIndex(component).isPresent(); * } * </pre> * Similarly: * * <pre> * JComponent parent = ... * Optional<JComponent> child1 = getChild(parent, 0); * Optional<JComponent> child2 = getChild(parent, 0); * // if the component hierarchy changed between the two calls, this may be false * boolean sameChildren = child1.equals(child2); * </pre> */ public class JComponentHierarchyNavigator implements TreeNavigator<JComponent> { @Override public Optional<JComponent> getParent(JComponent child) { Objects.requireNonNull(child, "The argument 'child' must not be null."); Container parent = child.getParent(); if (!(parent instanceof JComponent)) return Optional.empty(); return Optional.ofNullable((JComponent) parent); } @Override public OptionalInt getChildIndex(JComponent node) { Objects.requireNonNull(node, "The argument 'node' must not be null."); Component parent = node.getParent(); if (parent == null) return OptionalInt.empty(); if (!(parent instanceof JComponent)) return OptionalInt.empty(); Component[] siblings = ((JComponent) parent).getComponents(); return getIndex(node, siblings); } private static OptionalInt getIndex(Component node, Component[] siblings) { try { for (int i = 0; i < siblings.length; i++) if (siblings[i] == node) return OptionalInt.of(i); return OptionalInt.empty(); } catch (ArrayIndexOutOfBoundsException ex) { return OptionalInt.empty(); } } @Override public int getChildrenCount(JComponent parent) { Objects.requireNonNull(parent, "The argument 'parent' must not be null."); return parent.getComponents().length; } @Override public Optional<JComponent> getChild(JComponent 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.getComponents().length <= childIndex) return Optional.empty(); try { // even though we checked first, due to threading this might fail Component child = parent.getComponent(childIndex); if (!(child instanceof JComponent)) return Optional.empty(); return Optional.of((JComponent) child); } catch (ArrayIndexOutOfBoundsException ex) { return Optional.empty(); } } }