public int currentBranchLength() {
        return getMoveNumber() + head.getDepth();
    }

package featurecat.lizzie.rules;

import featurecat.lizzie.analysis.GameInfo;
import java.util.List;
import java.util.Optional;

/**
 * Linked list data structure to store board history
 */
public class BoardHistoryList {

    private GameInfo gameInfo;

    private BoardHistoryNode head;

    /**
     * Initialize a new board history list, whose first node is data
     *
     * @param data the data to be stored for the first entry
     */
    public BoardHistoryList(BoardData data) {
        head = new BoardHistoryNode(data);
        gameInfo = new GameInfo();
    }

    public GameInfo getGameInfo() {
        return gameInfo;
    }

    public void setGameInfo(GameInfo gameInfo) {
        this.gameInfo = gameInfo;
    }

    public BoardHistoryList shallowCopy() {
        BoardHistoryList copy = new BoardHistoryList(null);
        copy.head = head;
        copy.gameInfo = gameInfo;
        return copy;
    }

    /**
     * Clear history.
     */
    public void clear() {
        head.clear();
    }

    /**
     * Add new data after head. Overwrites any data that may have been stored after head.
     *
     * @param data the data to add
     */
    public void add(BoardData data) {
        head = head.add(new BoardHistoryNode(data));
    }

    public void addOrGoto(BoardData data) {
        addOrGoto(data, false);
    }

    public void addOrGoto(BoardData data, boolean newBranch) {
        head = head.addOrGoto(data, newBranch);
    }

    public void addOrGoto(BoardData data, boolean newBranch, boolean changeMove) {
        head = head.addOrGoto(data, newBranch, changeMove);
    }

    /**
     * moves the pointer to the left, returns the data stored there
     *
     * @return data of previous node, Optional.empty if there is no previous node
     */
    public Optional<BoardData> previous() {
        if (!head.previous().isPresent())
            return Optional.empty();
        else
            head = head.previous().get();
        return Optional.of(head.getData());
    }

    public void toStart() {
        while (previous().isPresent()) ;
    }

    public void toBranchTop() {
        BoardHistoryNode start = head;
        while (start.previous().isPresent()) {
            BoardHistoryNode pre = start.previous().get();
            if (pre.next(true).isPresent() && pre.next(true).get() != start) {
                previous();
                break;
            }
            previous();
            start = pre;
        }
    }

    /**
     * moves the pointer to the right, returns the data stored there
     *
     * @return the data of next node, Optional.empty if there is no next node
     */
    public Optional<BoardData> next() {
        return next(false);
    }

    public Optional<BoardData> next(boolean includeDummay) {
        Optional<BoardHistoryNode> n = head.next(includeDummay);
        n.ifPresent(x -> head = x);
        return n.map(x -> x.getData());
    }

    /**
     * Moves the pointer to the variation number idx, returns the data stored there.
     *
     * @return the data of next node, Optional.empty if there is no variation with index.
     */
    public Optional<BoardData> nextVariation(int idx) {
        Optional<BoardHistoryNode> n = head.getVariation(idx);
        n.ifPresent(x -> head = x);
        return n.map(x -> x.getData());
    }

    /**
     * Does not change the pointer position
     *
     * @return the data stored at the next index, if any, Optional.empty otherwise.
     */
    public Optional<BoardData> getNext() {
        return getNext(false);
    }

    public Optional<BoardData> getNext(boolean includeDummy) {
        return head.next(includeDummy).map(x -> x.getData());
    }

    /**
     * @return nexts for display
     */
    public List<BoardHistoryNode> getNexts() {
        return head.getVariations();
    }

    /**
     * Does not change the pointer position.
     *
     * @return the data stored at the previous index, if any, Optional.empty otherwise.
     */
    public Optional<BoardData> getPrevious() {
        return head.previous().map(p -> p.getData());
    }

    /**
     * @return the data of the current node
     */
    public BoardData getData() {
        return head.getData();
    }

    public void setStone(int[] coordinates, Stone stone) {
        if (!Board.isValid(coordinates[0], coordinates[1]))
            return;
        int index = Board.getIndex(coordinates[0], coordinates[1]);
        head.getData().stones[index] = stone;
        head.getData().zobrist.toggleStone(coordinates[0], coordinates[1], stone);
    }

    public Stone[] getStones() {
        return head.getData().stones;
    }

    public Optional<int[]> getLastMove() {
        return head.getData().lastMove;
    }

    public Optional<int[]> getNextMove() {
        return getNext().flatMap(n -> n.lastMove);
    }

    public Stone getLastMoveColor() {
        return head.getData().lastMoveColor;
    }

    public boolean isBlacksTurn() {
        return head.getData().blackToPlay;
    }

    public Zobrist getZobrist() {
        return head.getData().zobrist.clone();
    }

    public int getMoveNumber() {
        return head.getData().moveNumber;
    }

    public int getMoveMNNumber() {
        return head.getData().moveMNNumber;
    }

    public int[] getMoveNumberList() {
        return head.getData().moveNumberList;
    }

    public BoardHistoryNode getCurrentHistoryNode() {
        return head;
    }

    /**
     * @param data the board position to check against superko
     * @return whether or not the given position violates the superko rule at the head's state
     */
    public boolean violatesSuperko(BoardData data) {
        BoardHistoryNode head = this.head;
        // check to see if this position has occurred before
        while (head.previous().isPresent()) {
            // if two zobrist hashes are equal, and it's the same player to coordinate, they are the same
            // position
            if (data.zobrist.equals(head.getData().zobrist) && data.blackToPlay == head.getData().blackToPlay)
                return true;
            head = head.previous().get();
        }
        // no position matched this position, so it's valid
        return false;
    }

    public boolean violatesKoRule(BoardData data) {
        // check if the current position is identical to the position two moves ago
        return this.head.previous().map(p -> p != null && data.zobrist.equals(p.getData().zobrist)).orElse(false);
    }

    /**
     * Returns the root node
     *
     * @return root node
     */
    public BoardHistoryNode root() {
        BoardHistoryNode top = head;
        while (top.previous().isPresent()) {
            top = top.previous().get();
        }
        return top;
    }

    /**
     * Returns the length of current branch
     *
     * @return length of current branch
     */

    /**
     * Returns the length of main trunk
     *
     * @return length of main trunk
     */
    public int mainTrunkLength() {
        return root().getDepth();
    }

    public BoardHistoryNode getEnd() {
        BoardHistoryNode e = head;
        while (e.next().isPresent()) {
            e = e.next().get();
        }
        return e;
    }

    public void pass(Stone color) {
        pass(color, false, false, false);
    }

    public void pass(Stone color, boolean newBranch) {
        pass(color, newBranch, false, false);
    }

    public void pass(Stone color, boolean newBranch, boolean dummy) {
        pass(color, newBranch, dummy, false);
    }

    public void pass(Stone color, boolean newBranch, boolean dummy, boolean changeMove) {
        synchronized (this) {
            // check to see if this move is being replayed in history
            if (this.getNext().map(n -> !n.lastMove.isPresent()).orElse(false) && !newBranch) {
                // this is the next move in history. Just increment history so that we don't erase the
                // redo's
                this.next();
                return;
            }
            Stone[] stones = this.getStones().clone();
            Zobrist zobrist = this.getZobrist();
            int moveNumber = this.getMoveNumber() + 1;
            int[] moveNumberList = newBranch && this.getNext(true).isPresent() ? new int[Board.boardWidth * Board.boardHeight] : this.getMoveNumberList().clone();
            // build the new game state
            BoardData newState = new BoardData(stones, Optional.empty(), color, color.equals(Stone.WHITE), zobrist, moveNumber, moveNumberList, this.getData().blackCaptures, this.getData().whiteCaptures, 0, 0, 0);
            newState.dummy = dummy;
            // update history with pass
            this.addOrGoto(newState, newBranch, changeMove);
        }
    }

    public void place(int x, int y, Stone color) {
        place(x, y, color, false);
    }

    public void place(int x, int y, Stone color, boolean newBranch) {
        place(x, y, color, newBranch, false);
    }

    public void place(int x, int y, Stone color, boolean newBranch, boolean changeMove) {
        synchronized (this) {
            if (!Board.isValid(x, y) || (this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY && !newBranch))
                return;
            double nextWinrate = -100;
            if (this.getData().winrate >= 0)
                nextWinrate = 100 - this.getData().winrate;
            double nextScoreMean = -this.getData().scoreMean;
            // check to see if this coordinate is being replayed in history
            Optional<int[]> nextLast = this.getNext().flatMap(n -> n.lastMove);
            if (nextLast.isPresent() && nextLast.get()[0] == x && nextLast.get()[1] == y && !newBranch && !changeMove) {
                // this is the next coordinate in history. Just increment history so that we don't erase the
                // redo's
                this.next();
                return;
            }
            // load a copy of the data at the current node of history
            Stone[] stones = this.getStones().clone();
            Zobrist zobrist = this.getZobrist();
            Optional<int[]> lastMove = Optional.of(new int[] { x, y });
            int moveNumber = this.getMoveNumber() + 1;
            int moveMNNumber = this.getMoveMNNumber() > -1 && !newBranch ? this.getMoveMNNumber() + 1 : -1;
            int[] moveNumberList = newBranch && this.getNext(true).isPresent() ? new int[Board.boardWidth * Board.boardHeight] : this.getMoveNumberList().clone();
            moveNumberList[Board.getIndex(x, y)] = moveMNNumber > -1 ? moveMNNumber : moveNumber;
            // set the stone at (x, y) to color
            stones[Board.getIndex(x, y)] = color;
            zobrist.toggleStone(x, y, color);
            // remove enemy stones
            int capturedStones = 0;
            capturedStones += Board.removeDeadChain(x + 1, y, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x, y + 1, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x - 1, y, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x, y - 1, color.opposite(), stones, zobrist);
            // check to see if the player made a suicidal coordinate
            int isSuicidal = Board.removeDeadChain(x, y, color, stones, zobrist);
            for (int i = 0; i < Board.boardWidth * Board.boardHeight; i++) {
                if (stones[i].equals(Stone.EMPTY)) {
                    moveNumberList[i] = 0;
                }
            }
            int bc = this.getData().blackCaptures;
            int wc = this.getData().whiteCaptures;
            if (color.isBlack())
                bc += capturedStones;
            else
                wc += capturedStones;
            BoardData newState = new BoardData(stones, lastMove, color, color.equals(Stone.WHITE), zobrist, moveNumber, moveNumberList, bc, wc, nextWinrate, 0, nextScoreMean);
            newState.moveMNNumber = moveMNNumber;
            // don't make this coordinate if it is suicidal or violates superko
            if (isSuicidal > 0 || this.violatesKoRule(newState))
                return;
            // update history with this coordinate
            this.addOrGoto(newState, newBranch, changeMove);
        }
    }

    public void addNodeProperty(String key, String value) {
        synchronized (this) {
            this.getData().addProperty(key, value);
            if ("MN".equals(key)) {
                moveNumber(Integer.parseInt(value));
            }
        }
    }

    public void moveNumber(int moveNumber) {
        synchronized (this) {
            BoardData data = this.getData();
            if (data.lastMove.isPresent()) {
                int[] moveNumberList = this.getMoveNumberList();
                moveNumberList[Board.getIndex(data.lastMove.get()[0], data.lastMove.get()[1])] = moveNumber;
                Optional<BoardHistoryNode> node = this.getCurrentHistoryNode().previous();
                while (node.isPresent() && node.get().numberOfChildren() <= 1) {
                    BoardData nodeData = node.get().getData();
                    if (nodeData.lastMove.isPresent() && nodeData.moveNumber >= moveNumber) {
                        moveNumber = (moveNumber > 1) ? moveNumber - 1 : 0;
                        moveNumberList[Board.getIndex(nodeData.lastMove.get()[0], nodeData.lastMove.get()[1])] = moveNumber;
                    }
                    node = node.get().previous();
                }
            }
        }
    }

    public void addStone(int x, int y, Stone color) {
        synchronized (this) {
            if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY)
                return;
            Stone[] stones = this.getData().stones;
            Zobrist zobrist = this.getData().zobrist;
            // set the stone at (x, y) to color
            stones[Board.getIndex(x, y)] = color;
            zobrist.toggleStone(x, y, color);
        }
    }

    public void removeStone(int x, int y, Stone color) {
        synchronized (this) {
            if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] == Stone.EMPTY)
                return;
            BoardData data = this.getData();
            Stone[] stones = data.stones;
            Zobrist zobrist = data.zobrist;
            // set the stone at (x, y) to empty
            Stone oriColor = stones[Board.getIndex(x, y)];
            stones[Board.getIndex(x, y)] = Stone.EMPTY;
            zobrist.toggleStone(x, y, oriColor);
            data.moveNumberList[Board.getIndex(x, y)] = 0;
        }
    }

    public void flatten() {
        Stone[] stones = this.getStones();
        boolean blackToPlay = this.isBlacksTurn();
        Zobrist zobrist = this.getZobrist().clone();
        BoardHistoryList oldHistory = this;
        head = new BoardHistoryNode(new BoardData(stones, Optional.empty(), Stone.EMPTY, blackToPlay, zobrist, 0, new int[Board.boardWidth * Board.boardHeight], 0, 0, 0.0, 0, 0.0));
        this.setGameInfo(oldHistory.getGameInfo());
    }

    public boolean goToMoveNumber(int moveNumber, boolean withinBranch) {
        int delta = moveNumber - this.getMoveNumber();
        boolean moved = false;
        for (int i = 0; i < Math.abs(delta); i++) {
            if (withinBranch && delta < 0) {
                BoardHistoryNode currentNode = this.getCurrentHistoryNode();
                if (!currentNode.isFirstChild()) {
                    break;
                }
            }
            if (!(delta > 0 ? next().isPresent() : previous().isPresent())) {
                break;
            }
            moved = true;
        }
        return moved;
    }

    public int sync(BoardHistoryList newList) {
        int diffMoveNo = -1;
        BoardHistoryNode node = this.getCurrentHistoryNode();
        BoardHistoryNode prev = node.previous().map(p -> p).orElse(null);
        // From begin
        while (prev != null) {
            node = prev;
            prev = node.previous().map(p -> p).orElse(null);
        }
        // Compare
        BoardHistoryNode newNode = newList.getCurrentHistoryNode();
        while (newNode != null) {
            if (node == null) {
                // Add
                prev.addOrGoto(newNode.getData().clone());
                node = prev.next().map(n -> n).orElse(null);
                if (diffMoveNo < 0) {
                    diffMoveNo = newNode.getData().moveNumber;
                }
                node.sync(newNode);
                break;
            } else {
                if (!node.compare(newNode)) {
                    if (diffMoveNo < 0) {
                        diffMoveNo = newNode.getData().moveNumber;
                    }
                    node.sync(newNode);
                    break;
                }
            }
            prev = node;
            node = node.next(true).map(n -> n).orElse(null);
            newNode = newNode.next(true).map(n -> n).orElse(null);
        }
        return diffMoveNo;
    }
}


for (int i = 0; i < Math.abs(delta); i++) {
            if (withinBranch && delta < 0) {
                BoardHistoryNode currentNode = this.getCurrentHistoryNode();
                if (!currentNode.isFirstChild()) {
                    break;
                }
            }
            if (!(delta > 0 ? next().isPresent() : previous().isPresent())) {
                break;
            }
            moved = true;
        }

package featurecat.lizzie.rules;

import featurecat.lizzie.analysis.GameInfo;
import java.util.List;
import java.util.Optional;

/**
 * Linked list data structure to store board history
 */
public class BoardHistoryList {

    private GameInfo gameInfo;

    private BoardHistoryNode head;

    /**
     * Initialize a new board history list, whose first node is data
     *
     * @param data the data to be stored for the first entry
     */
    public BoardHistoryList(BoardData data) {
        head = new BoardHistoryNode(data);
        gameInfo = new GameInfo();
    }

    public GameInfo getGameInfo() {
        return gameInfo;
    }

    public void setGameInfo(GameInfo gameInfo) {
        this.gameInfo = gameInfo;
    }

    public BoardHistoryList shallowCopy() {
        BoardHistoryList copy = new BoardHistoryList(null);
        copy.head = head;
        copy.gameInfo = gameInfo;
        return copy;
    }

    /**
     * Clear history.
     */
    public void clear() {
        head.clear();
    }

    /**
     * Add new data after head. Overwrites any data that may have been stored after head.
     *
     * @param data the data to add
     */
    public void add(BoardData data) {
        head = head.add(new BoardHistoryNode(data));
    }

    public void addOrGoto(BoardData data) {
        addOrGoto(data, false);
    }

    public void addOrGoto(BoardData data, boolean newBranch) {
        head = head.addOrGoto(data, newBranch);
    }

    public void addOrGoto(BoardData data, boolean newBranch, boolean changeMove) {
        head = head.addOrGoto(data, newBranch, changeMove);
    }

    /**
     * moves the pointer to the left, returns the data stored there
     *
     * @return data of previous node, Optional.empty if there is no previous node
     */
    public Optional<BoardData> previous() {
        if (!head.previous().isPresent())
            return Optional.empty();
        else
            head = head.previous().get();
        return Optional.of(head.getData());
    }

    public void toStart() {
        while (previous().isPresent()) ;
    }

    public void toBranchTop() {
        BoardHistoryNode start = head;
        while (start.previous().isPresent()) {
            BoardHistoryNode pre = start.previous().get();
            if (pre.next(true).isPresent() && pre.next(true).get() != start) {
                previous();
                break;
            }
            previous();
            start = pre;
        }
    }

    /**
     * moves the pointer to the right, returns the data stored there
     *
     * @return the data of next node, Optional.empty if there is no next node
     */
    public Optional<BoardData> next() {
        return next(false);
    }

    public Optional<BoardData> next(boolean includeDummay) {
        Optional<BoardHistoryNode> n = head.next(includeDummay);
        n.ifPresent(x -> head = x);
        return n.map(x -> x.getData());
    }

    /**
     * Moves the pointer to the variation number idx, returns the data stored there.
     *
     * @return the data of next node, Optional.empty if there is no variation with index.
     */
    public Optional<BoardData> nextVariation(int idx) {
        Optional<BoardHistoryNode> n = head.getVariation(idx);
        n.ifPresent(x -> head = x);
        return n.map(x -> x.getData());
    }

    /**
     * Does not change the pointer position
     *
     * @return the data stored at the next index, if any, Optional.empty otherwise.
     */
    public Optional<BoardData> getNext() {
        return getNext(false);
    }

    public Optional<BoardData> getNext(boolean includeDummy) {
        return head.next(includeDummy).map(x -> x.getData());
    }

    /**
     * @return nexts for display
     */
    public List<BoardHistoryNode> getNexts() {
        return head.getVariations();
    }

    /**
     * Does not change the pointer position.
     *
     * @return the data stored at the previous index, if any, Optional.empty otherwise.
     */
    public Optional<BoardData> getPrevious() {
        return head.previous().map(p -> p.getData());
    }

    /**
     * @return the data of the current node
     */
    public BoardData getData() {
        return head.getData();
    }

    public void setStone(int[] coordinates, Stone stone) {
        if (!Board.isValid(coordinates[0], coordinates[1]))
            return;
        int index = Board.getIndex(coordinates[0], coordinates[1]);
        head.getData().stones[index] = stone;
        head.getData().zobrist.toggleStone(coordinates[0], coordinates[1], stone);
    }

    public Stone[] getStones() {
        return head.getData().stones;
    }

    public Optional<int[]> getLastMove() {
        return head.getData().lastMove;
    }

    public Optional<int[]> getNextMove() {
        return getNext().flatMap(n -> n.lastMove);
    }

    public Stone getLastMoveColor() {
        return head.getData().lastMoveColor;
    }

    public boolean isBlacksTurn() {
        return head.getData().blackToPlay;
    }

    public Zobrist getZobrist() {
        return head.getData().zobrist.clone();
    }

    public int getMoveNumber() {
        return head.getData().moveNumber;
    }

    public int getMoveMNNumber() {
        return head.getData().moveMNNumber;
    }

    public int[] getMoveNumberList() {
        return head.getData().moveNumberList;
    }

    public BoardHistoryNode getCurrentHistoryNode() {
        return head;
    }

    /**
     * @param data the board position to check against superko
     * @return whether or not the given position violates the superko rule at the head's state
     */
    public boolean violatesSuperko(BoardData data) {
        BoardHistoryNode head = this.head;
        // check to see if this position has occurred before
        while (head.previous().isPresent()) {
            // if two zobrist hashes are equal, and it's the same player to coordinate, they are the same
            // position
            if (data.zobrist.equals(head.getData().zobrist) && data.blackToPlay == head.getData().blackToPlay)
                return true;
            head = head.previous().get();
        }
        // no position matched this position, so it's valid
        return false;
    }

    public boolean violatesKoRule(BoardData data) {
        // check if the current position is identical to the position two moves ago
        return this.head.previous().map(p -> p != null && data.zobrist.equals(p.getData().zobrist)).orElse(false);
    }

    /**
     * Returns the root node
     *
     * @return root node
     */
    public BoardHistoryNode root() {
        BoardHistoryNode top = head;
        while (top.previous().isPresent()) {
            top = top.previous().get();
        }
        return top;
    }

    /**
     * Returns the length of current branch
     *
     * @return length of current branch
     */
    public int currentBranchLength() {
        return getMoveNumber() + head.getDepth();
    }

    /**
     * Returns the length of main trunk
     *
     * @return length of main trunk
     */
    public int mainTrunkLength() {
        return root().getDepth();
    }

    public BoardHistoryNode getEnd() {
        BoardHistoryNode e = head;
        while (e.next().isPresent()) {
            e = e.next().get();
        }
        return e;
    }

    public void pass(Stone color) {
        pass(color, false, false, false);
    }

    public void pass(Stone color, boolean newBranch) {
        pass(color, newBranch, false, false);
    }

    public void pass(Stone color, boolean newBranch, boolean dummy) {
        pass(color, newBranch, dummy, false);
    }

    public void pass(Stone color, boolean newBranch, boolean dummy, boolean changeMove) {
        synchronized (this) {
            // check to see if this move is being replayed in history
            if (this.getNext().map(n -> !n.lastMove.isPresent()).orElse(false) && !newBranch) {
                // this is the next move in history. Just increment history so that we don't erase the
                // redo's
                this.next();
                return;
            }
            Stone[] stones = this.getStones().clone();
            Zobrist zobrist = this.getZobrist();
            int moveNumber = this.getMoveNumber() + 1;
            int[] moveNumberList = newBranch && this.getNext(true).isPresent() ? new int[Board.boardWidth * Board.boardHeight] : this.getMoveNumberList().clone();
            // build the new game state
            BoardData newState = new BoardData(stones, Optional.empty(), color, color.equals(Stone.WHITE), zobrist, moveNumber, moveNumberList, this.getData().blackCaptures, this.getData().whiteCaptures, 0, 0, 0);
            newState.dummy = dummy;
            // update history with pass
            this.addOrGoto(newState, newBranch, changeMove);
        }
    }

    public void place(int x, int y, Stone color) {
        place(x, y, color, false);
    }

    public void place(int x, int y, Stone color, boolean newBranch) {
        place(x, y, color, newBranch, false);
    }

    public void place(int x, int y, Stone color, boolean newBranch, boolean changeMove) {
        synchronized (this) {
            if (!Board.isValid(x, y) || (this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY && !newBranch))
                return;
            double nextWinrate = -100;
            if (this.getData().winrate >= 0)
                nextWinrate = 100 - this.getData().winrate;
            double nextScoreMean = -this.getData().scoreMean;
            // check to see if this coordinate is being replayed in history
            Optional<int[]> nextLast = this.getNext().flatMap(n -> n.lastMove);
            if (nextLast.isPresent() && nextLast.get()[0] == x && nextLast.get()[1] == y && !newBranch && !changeMove) {
                // this is the next coordinate in history. Just increment history so that we don't erase the
                // redo's
                this.next();
                return;
            }
            // load a copy of the data at the current node of history
            Stone[] stones = this.getStones().clone();
            Zobrist zobrist = this.getZobrist();
            Optional<int[]> lastMove = Optional.of(new int[] { x, y });
            int moveNumber = this.getMoveNumber() + 1;
            int moveMNNumber = this.getMoveMNNumber() > -1 && !newBranch ? this.getMoveMNNumber() + 1 : -1;
            int[] moveNumberList = newBranch && this.getNext(true).isPresent() ? new int[Board.boardWidth * Board.boardHeight] : this.getMoveNumberList().clone();
            moveNumberList[Board.getIndex(x, y)] = moveMNNumber > -1 ? moveMNNumber : moveNumber;
            // set the stone at (x, y) to color
            stones[Board.getIndex(x, y)] = color;
            zobrist.toggleStone(x, y, color);
            // remove enemy stones
            int capturedStones = 0;
            capturedStones += Board.removeDeadChain(x + 1, y, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x, y + 1, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x - 1, y, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x, y - 1, color.opposite(), stones, zobrist);
            // check to see if the player made a suicidal coordinate
            int isSuicidal = Board.removeDeadChain(x, y, color, stones, zobrist);
            for (int i = 0; i < Board.boardWidth * Board.boardHeight; i++) {
                if (stones[i].equals(Stone.EMPTY)) {
                    moveNumberList[i] = 0;
                }
            }
            int bc = this.getData().blackCaptures;
            int wc = this.getData().whiteCaptures;
            if (color.isBlack())
                bc += capturedStones;
            else
                wc += capturedStones;
            BoardData newState = new BoardData(stones, lastMove, color, color.equals(Stone.WHITE), zobrist, moveNumber, moveNumberList, bc, wc, nextWinrate, 0, nextScoreMean);
            newState.moveMNNumber = moveMNNumber;
            // don't make this coordinate if it is suicidal or violates superko
            if (isSuicidal > 0 || this.violatesKoRule(newState))
                return;
            // update history with this coordinate
            this.addOrGoto(newState, newBranch, changeMove);
        }
    }

    public void addNodeProperty(String key, String value) {
        synchronized (this) {
            this.getData().addProperty(key, value);
            if ("MN".equals(key)) {
                moveNumber(Integer.parseInt(value));
            }
        }
    }

    public void moveNumber(int moveNumber) {
        synchronized (this) {
            BoardData data = this.getData();
            if (data.lastMove.isPresent()) {
                int[] moveNumberList = this.getMoveNumberList();
                moveNumberList[Board.getIndex(data.lastMove.get()[0], data.lastMove.get()[1])] = moveNumber;
                Optional<BoardHistoryNode> node = this.getCurrentHistoryNode().previous();
                while (node.isPresent() && node.get().numberOfChildren() <= 1) {
                    BoardData nodeData = node.get().getData();
                    if (nodeData.lastMove.isPresent() && nodeData.moveNumber >= moveNumber) {
                        moveNumber = (moveNumber > 1) ? moveNumber - 1 : 0;
                        moveNumberList[Board.getIndex(nodeData.lastMove.get()[0], nodeData.lastMove.get()[1])] = moveNumber;
                    }
                    node = node.get().previous();
                }
            }
        }
    }

    public void addStone(int x, int y, Stone color) {
        synchronized (this) {
            if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY)
                return;
            Stone[] stones = this.getData().stones;
            Zobrist zobrist = this.getData().zobrist;
            // set the stone at (x, y) to color
            stones[Board.getIndex(x, y)] = color;
            zobrist.toggleStone(x, y, color);
        }
    }

    public void removeStone(int x, int y, Stone color) {
        synchronized (this) {
            if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] == Stone.EMPTY)
                return;
            BoardData data = this.getData();
            Stone[] stones = data.stones;
            Zobrist zobrist = data.zobrist;
            // set the stone at (x, y) to empty
            Stone oriColor = stones[Board.getIndex(x, y)];
            stones[Board.getIndex(x, y)] = Stone.EMPTY;
            zobrist.toggleStone(x, y, oriColor);
            data.moveNumberList[Board.getIndex(x, y)] = 0;
        }
    }

    public void flatten() {
        Stone[] stones = this.getStones();
        boolean blackToPlay = this.isBlacksTurn();
        Zobrist zobrist = this.getZobrist().clone();
        BoardHistoryList oldHistory = this;
        head = new BoardHistoryNode(new BoardData(stones, Optional.empty(), Stone.EMPTY, blackToPlay, zobrist, 0, new int[Board.boardWidth * Board.boardHeight], 0, 0, 0.0, 0, 0.0));
        this.setGameInfo(oldHistory.getGameInfo());
    }

    public boolean goToMoveNumber(int moveNumber, boolean withinBranch) {
        int delta = moveNumber - this.getMoveNumber();
        boolean moved = false;

        return moved;
    }

    public int sync(BoardHistoryList newList) {
        int diffMoveNo = -1;
        BoardHistoryNode node = this.getCurrentHistoryNode();
        BoardHistoryNode prev = node.previous().map(p -> p).orElse(null);
        // From begin
        while (prev != null) {
            node = prev;
            prev = node.previous().map(p -> p).orElse(null);
        }
        // Compare
        BoardHistoryNode newNode = newList.getCurrentHistoryNode();
        while (newNode != null) {
            if (node == null) {
                // Add
                prev.addOrGoto(newNode.getData().clone());
                node = prev.next().map(n -> n).orElse(null);
                if (diffMoveNo < 0) {
                    diffMoveNo = newNode.getData().moveNumber;
                }
                node.sync(newNode);
                break;
            } else {
                if (!node.compare(newNode)) {
                    if (diffMoveNo < 0) {
                        diffMoveNo = newNode.getData().moveNumber;
                    }
                    node.sync(newNode);
                    break;
                }
            }
            prev = node;
            node = node.next(true).map(n -> n).orElse(null);
            newNode = newNode.next(true).map(n -> n).orElse(null);
        }
        return diffMoveNo;
    }
}


while (start.previous().isPresent()) {
            BoardHistoryNode pre = start.previous().get();
            if (pre.next(true).isPresent() && pre.next(true).get() != start) {
                previous();
                break;
            }
            previous();
            start = pre;
        }

package featurecat.lizzie.rules;

import featurecat.lizzie.analysis.GameInfo;
import java.util.List;
import java.util.Optional;

/**
 * Linked list data structure to store board history
 */
public class BoardHistoryList {

    private GameInfo gameInfo;

    private BoardHistoryNode head;

    /**
     * Initialize a new board history list, whose first node is data
     *
     * @param data the data to be stored for the first entry
     */
    public BoardHistoryList(BoardData data) {
        head = new BoardHistoryNode(data);
        gameInfo = new GameInfo();
    }

    public GameInfo getGameInfo() {
        return gameInfo;
    }

    public void setGameInfo(GameInfo gameInfo) {
        this.gameInfo = gameInfo;
    }

    public BoardHistoryList shallowCopy() {
        BoardHistoryList copy = new BoardHistoryList(null);
        copy.head = head;
        copy.gameInfo = gameInfo;
        return copy;
    }

    /**
     * Clear history.
     */
    public void clear() {
        head.clear();
    }

    /**
     * Add new data after head. Overwrites any data that may have been stored after head.
     *
     * @param data the data to add
     */
    public void add(BoardData data) {
        head = head.add(new BoardHistoryNode(data));
    }

    public void addOrGoto(BoardData data) {
        addOrGoto(data, false);
    }

    public void addOrGoto(BoardData data, boolean newBranch) {
        head = head.addOrGoto(data, newBranch);
    }

    public void addOrGoto(BoardData data, boolean newBranch, boolean changeMove) {
        head = head.addOrGoto(data, newBranch, changeMove);
    }

    /**
     * moves the pointer to the left, returns the data stored there
     *
     * @return data of previous node, Optional.empty if there is no previous node
     */
    public Optional<BoardData> previous() {
        if (!head.previous().isPresent())
            return Optional.empty();
        else
            head = head.previous().get();
        return Optional.of(head.getData());
    }

    public void toStart() {
        while (previous().isPresent()) ;
    }

    public void toBranchTop() {
        BoardHistoryNode start = head;

    }

    /**
     * moves the pointer to the right, returns the data stored there
     *
     * @return the data of next node, Optional.empty if there is no next node
     */
    public Optional<BoardData> next() {
        return next(false);
    }

    public Optional<BoardData> next(boolean includeDummay) {
        Optional<BoardHistoryNode> n = head.next(includeDummay);
        n.ifPresent(x -> head = x);
        return n.map(x -> x.getData());
    }

    /**
     * Moves the pointer to the variation number idx, returns the data stored there.
     *
     * @return the data of next node, Optional.empty if there is no variation with index.
     */
    public Optional<BoardData> nextVariation(int idx) {
        Optional<BoardHistoryNode> n = head.getVariation(idx);
        n.ifPresent(x -> head = x);
        return n.map(x -> x.getData());
    }

    /**
     * Does not change the pointer position
     *
     * @return the data stored at the next index, if any, Optional.empty otherwise.
     */
    public Optional<BoardData> getNext() {
        return getNext(false);
    }

    public Optional<BoardData> getNext(boolean includeDummy) {
        return head.next(includeDummy).map(x -> x.getData());
    }

    /**
     * @return nexts for display
     */
    public List<BoardHistoryNode> getNexts() {
        return head.getVariations();
    }

    /**
     * Does not change the pointer position.
     *
     * @return the data stored at the previous index, if any, Optional.empty otherwise.
     */
    public Optional<BoardData> getPrevious() {
        return head.previous().map(p -> p.getData());
    }

    /**
     * @return the data of the current node
     */
    public BoardData getData() {
        return head.getData();
    }

    public void setStone(int[] coordinates, Stone stone) {
        if (!Board.isValid(coordinates[0], coordinates[1]))
            return;
        int index = Board.getIndex(coordinates[0], coordinates[1]);
        head.getData().stones[index] = stone;
        head.getData().zobrist.toggleStone(coordinates[0], coordinates[1], stone);
    }

    public Stone[] getStones() {
        return head.getData().stones;
    }

    public Optional<int[]> getLastMove() {
        return head.getData().lastMove;
    }

    public Optional<int[]> getNextMove() {
        return getNext().flatMap(n -> n.lastMove);
    }

    public Stone getLastMoveColor() {
        return head.getData().lastMoveColor;
    }

    public boolean isBlacksTurn() {
        return head.getData().blackToPlay;
    }

    public Zobrist getZobrist() {
        return head.getData().zobrist.clone();
    }

    public int getMoveNumber() {
        return head.getData().moveNumber;
    }

    public int getMoveMNNumber() {
        return head.getData().moveMNNumber;
    }

    public int[] getMoveNumberList() {
        return head.getData().moveNumberList;
    }

    public BoardHistoryNode getCurrentHistoryNode() {
        return head;
    }

    /**
     * @param data the board position to check against superko
     * @return whether or not the given position violates the superko rule at the head's state
     */
    public boolean violatesSuperko(BoardData data) {
        BoardHistoryNode head = this.head;
        // check to see if this position has occurred before
        while (head.previous().isPresent()) {
            // if two zobrist hashes are equal, and it's the same player to coordinate, they are the same
            // position
            if (data.zobrist.equals(head.getData().zobrist) && data.blackToPlay == head.getData().blackToPlay)
                return true;
            head = head.previous().get();
        }
        // no position matched this position, so it's valid
        return false;
    }

    public boolean violatesKoRule(BoardData data) {
        // check if the current position is identical to the position two moves ago
        return this.head.previous().map(p -> p != null && data.zobrist.equals(p.getData().zobrist)).orElse(false);
    }

    /**
     * Returns the root node
     *
     * @return root node
     */
    public BoardHistoryNode root() {
        BoardHistoryNode top = head;
        while (top.previous().isPresent()) {
            top = top.previous().get();
        }
        return top;
    }

    /**
     * Returns the length of current branch
     *
     * @return length of current branch
     */
    public int currentBranchLength() {
        return getMoveNumber() + head.getDepth();
    }

    /**
     * Returns the length of main trunk
     *
     * @return length of main trunk
     */
    public int mainTrunkLength() {
        return root().getDepth();
    }

    public BoardHistoryNode getEnd() {
        BoardHistoryNode e = head;
        while (e.next().isPresent()) {
            e = e.next().get();
        }
        return e;
    }

    public void pass(Stone color) {
        pass(color, false, false, false);
    }

    public void pass(Stone color, boolean newBranch) {
        pass(color, newBranch, false, false);
    }

    public void pass(Stone color, boolean newBranch, boolean dummy) {
        pass(color, newBranch, dummy, false);
    }

    public void pass(Stone color, boolean newBranch, boolean dummy, boolean changeMove) {
        synchronized (this) {
            // check to see if this move is being replayed in history
            if (this.getNext().map(n -> !n.lastMove.isPresent()).orElse(false) && !newBranch) {
                // this is the next move in history. Just increment history so that we don't erase the
                // redo's
                this.next();
                return;
            }
            Stone[] stones = this.getStones().clone();
            Zobrist zobrist = this.getZobrist();
            int moveNumber = this.getMoveNumber() + 1;
            int[] moveNumberList = newBranch && this.getNext(true).isPresent() ? new int[Board.boardWidth * Board.boardHeight] : this.getMoveNumberList().clone();
            // build the new game state
            BoardData newState = new BoardData(stones, Optional.empty(), color, color.equals(Stone.WHITE), zobrist, moveNumber, moveNumberList, this.getData().blackCaptures, this.getData().whiteCaptures, 0, 0, 0);
            newState.dummy = dummy;
            // update history with pass
            this.addOrGoto(newState, newBranch, changeMove);
        }
    }

    public void place(int x, int y, Stone color) {
        place(x, y, color, false);
    }

    public void place(int x, int y, Stone color, boolean newBranch) {
        place(x, y, color, newBranch, false);
    }

    public void place(int x, int y, Stone color, boolean newBranch, boolean changeMove) {
        synchronized (this) {
            if (!Board.isValid(x, y) || (this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY && !newBranch))
                return;
            double nextWinrate = -100;
            if (this.getData().winrate >= 0)
                nextWinrate = 100 - this.getData().winrate;
            double nextScoreMean = -this.getData().scoreMean;
            // check to see if this coordinate is being replayed in history
            Optional<int[]> nextLast = this.getNext().flatMap(n -> n.lastMove);
            if (nextLast.isPresent() && nextLast.get()[0] == x && nextLast.get()[1] == y && !newBranch && !changeMove) {
                // this is the next coordinate in history. Just increment history so that we don't erase the
                // redo's
                this.next();
                return;
            }
            // load a copy of the data at the current node of history
            Stone[] stones = this.getStones().clone();
            Zobrist zobrist = this.getZobrist();
            Optional<int[]> lastMove = Optional.of(new int[] { x, y });
            int moveNumber = this.getMoveNumber() + 1;
            int moveMNNumber = this.getMoveMNNumber() > -1 && !newBranch ? this.getMoveMNNumber() + 1 : -1;
            int[] moveNumberList = newBranch && this.getNext(true).isPresent() ? new int[Board.boardWidth * Board.boardHeight] : this.getMoveNumberList().clone();
            moveNumberList[Board.getIndex(x, y)] = moveMNNumber > -1 ? moveMNNumber : moveNumber;
            // set the stone at (x, y) to color
            stones[Board.getIndex(x, y)] = color;
            zobrist.toggleStone(x, y, color);
            // remove enemy stones
            int capturedStones = 0;
            capturedStones += Board.removeDeadChain(x + 1, y, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x, y + 1, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x - 1, y, color.opposite(), stones, zobrist);
            capturedStones += Board.removeDeadChain(x, y - 1, color.opposite(), stones, zobrist);
            // check to see if the player made a suicidal coordinate
            int isSuicidal = Board.removeDeadChain(x, y, color, stones, zobrist);
            for (int i = 0; i < Board.boardWidth * Board.boardHeight; i++) {
                if (stones[i].equals(Stone.EMPTY)) {
                    moveNumberList[i] = 0;
                }
            }
            int bc = this.getData().blackCaptures;
            int wc = this.getData().whiteCaptures;
            if (color.isBlack())
                bc += capturedStones;
            else
                wc += capturedStones;
            BoardData newState = new BoardData(stones, lastMove, color, color.equals(Stone.WHITE), zobrist, moveNumber, moveNumberList, bc, wc, nextWinrate, 0, nextScoreMean);
            newState.moveMNNumber = moveMNNumber;
            // don't make this coordinate if it is suicidal or violates superko
            if (isSuicidal > 0 || this.violatesKoRule(newState))
                return;
            // update history with this coordinate
            this.addOrGoto(newState, newBranch, changeMove);
        }
    }

    public void addNodeProperty(String key, String value) {
        synchronized (this) {
            this.getData().addProperty(key, value);
            if ("MN".equals(key)) {
                moveNumber(Integer.parseInt(value));
            }
        }
    }

    public void moveNumber(int moveNumber) {
        synchronized (this) {
            BoardData data = this.getData();
            if (data.lastMove.isPresent()) {
                int[] moveNumberList = this.getMoveNumberList();
                moveNumberList[Board.getIndex(data.lastMove.get()[0], data.lastMove.get()[1])] = moveNumber;
                Optional<BoardHistoryNode> node = this.getCurrentHistoryNode().previous();
                while (node.isPresent() && node.get().numberOfChildren() <= 1) {
                    BoardData nodeData = node.get().getData();
                    if (nodeData.lastMove.isPresent() && nodeData.moveNumber >= moveNumber) {
                        moveNumber = (moveNumber > 1) ? moveNumber - 1 : 0;
                        moveNumberList[Board.getIndex(nodeData.lastMove.get()[0], nodeData.lastMove.get()[1])] = moveNumber;
                    }
                    node = node.get().previous();
                }
            }
        }
    }

    public void addStone(int x, int y, Stone color) {
        synchronized (this) {
            if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY)
                return;
            Stone[] stones = this.getData().stones;
            Zobrist zobrist = this.getData().zobrist;
            // set the stone at (x, y) to color
            stones[Board.getIndex(x, y)] = color;
            zobrist.toggleStone(x, y, color);
        }
    }

    public void removeStone(int x, int y, Stone color) {
        synchronized (this) {
            if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] == Stone.EMPTY)
                return;
            BoardData data = this.getData();
            Stone[] stones = data.stones;
            Zobrist zobrist = data.zobrist;
            // set the stone at (x, y) to empty
            Stone oriColor = stones[Board.getIndex(x, y)];
            stones[Board.getIndex(x, y)] = Stone.EMPTY;
            zobrist.toggleStone(x, y, oriColor);
            data.moveNumberList[Board.getIndex(x, y)] = 0;
        }
    }

    public void flatten() {
        Stone[] stones = this.getStones();
        boolean blackToPlay = this.isBlacksTurn();
        Zobrist zobrist = this.getZobrist().clone();
        BoardHistoryList oldHistory = this;
        head = new BoardHistoryNode(new BoardData(stones, Optional.empty(), Stone.EMPTY, blackToPlay, zobrist, 0, new int[Board.boardWidth * Board.boardHeight], 0, 0, 0.0, 0, 0.0));
        this.setGameInfo(oldHistory.getGameInfo());
    }

    public boolean goToMoveNumber(int moveNumber, boolean withinBranch) {
        int delta = moveNumber - this.getMoveNumber();
        boolean moved = false;
        for (int i = 0; i < Math.abs(delta); i++) {
            if (withinBranch && delta < 0) {
                BoardHistoryNode currentNode = this.getCurrentHistoryNode();
                if (!currentNode.isFirstChild()) {
                    break;
                }
            }
            if (!(delta > 0 ? next().isPresent() : previous().isPresent())) {
                break;
            }
            moved = true;
        }
        return moved;
    }

    public int sync(BoardHistoryList newList) {
        int diffMoveNo = -1;
        BoardHistoryNode node = this.getCurrentHistoryNode();
        BoardHistoryNode prev = node.previous().map(p -> p).orElse(null);
        // From begin
        while (prev != null) {
            node = prev;
            prev = node.previous().map(p -> p).orElse(null);
        }
        // Compare
        BoardHistoryNode newNode = newList.getCurrentHistoryNode();
        while (newNode != null) {
            if (node == null) {
                // Add
                prev.addOrGoto(newNode.getData().clone());
                node = prev.next().map(n -> n).orElse(null);
                if (diffMoveNo < 0) {
                    diffMoveNo = newNode.getData().moveNumber;
                }
                node.sync(newNode);
                break;
            } else {
                if (!node.compare(newNode)) {
                    if (diffMoveNo < 0) {
                        diffMoveNo = newNode.getData().moveNumber;
                    }
                    node.sync(newNode);
                    break;
                }
            }
            prev = node;
            node = node.next(true).map(n -> n).orElse(null);
            newNode = newNode.next(true).map(n -> n).orElse(null);
        }
        return diffMoveNo;
    }
}


public int getNodeKind() {
        return Type.NAMESPACE;
    }

package client.net.sf.saxon.ce.tree;

import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.event.Receiver;
import client.net.sf.saxon.ce.event.ReceiverOptions;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.pattern.AnyNodeTest;
import client.net.sf.saxon.ce.pattern.NodeTest;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.AxisIterator;
import client.net.sf.saxon.ce.tree.iter.EmptyIterator;
import client.net.sf.saxon.ce.tree.iter.NodeListIterator;
import client.net.sf.saxon.ce.tree.iter.PrependIterator;
import client.net.sf.saxon.ce.tree.util.FastStringBuffer;
import client.net.sf.saxon.ce.tree.util.NamespaceIterator;
import client.net.sf.saxon.ce.tree.util.Navigator;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.value.AtomicValue;
import client.net.sf.saxon.ce.value.StringValue;
import client.net.sf.saxon.ce.value.Value;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class NamespaceNode implements NodeInfo {

    NodeInfo element;

    NamespaceBinding nsBinding;

    int position;

    int namecode;

    /**
     * Create a namespace node
     *
     * @param element  the parent element of the namespace node
     * @param nscode   the namespace code, representing the prefix and URI of the namespace binding
     * @param position maintains document order among namespace nodes for the same element
     */
    public NamespaceNode(NodeInfo element, NamespaceBinding nscode, int position) {
        this.element = element;
        this.nsBinding = nscode;
        this.position = position;
        // evaluated lazily to avoid NamePool access
        namecode = -1;
    }

    /**
     * Get the kind of node. This will be a value such as Type.ELEMENT or Type.ATTRIBUTE
     *
     * @return an integer identifying the kind of node. These integer values are the
     *         same as those used in the DOM
     */

/**
     * Determine whether this is the same node as another node.
     * Note: a.isSameNodeInfo(b) if and only if generateId(a)==generateId(b).
     * This method has the same semantics as isSameNode() in DOM Level 3, but
     * works on Saxon NodeInfo objects rather than DOM Node objects.
     *
     * @param other the node to be compared with this node
     * @return true if this NodeInfo object and the supplied NodeInfo object represent
     *         the same node in the tree.
     */
    public boolean isSameNodeInfo(NodeInfo other) {
        return other instanceof NamespaceNode && element.isSameNodeInfo(((NamespaceNode) other).element) && nsBinding == ((NamespaceNode) other).nsBinding;
    }

    /**
     * The equals() method compares nodes for identity. It is defined to give the same result
     * as isSameNodeInfo().
     *
     * @param other the node to be compared with this node
     * @return true if this NodeInfo object and the supplied NodeInfo object represent
     *         the same node in the tree.
     * @since 8.7 Previously, the effect of the equals() method was not defined. Callers
     *        should therefore be aware that third party implementations of the NodeInfo interface may
     *        not implement the correct semantics. It is safer to use isSameNodeInfo() for this reason.
     *        The equals() method has been defined because it is useful in contexts such as a Java Set or HashMap.
     */
    public boolean equals(Object other) {
        return other instanceof NodeInfo && isSameNodeInfo((NodeInfo) other);
    }

    /**
     * The hashCode() method obeys the contract for hashCode(): that is, if two objects are equal
     * (represent the same node) then they must have the same hashCode()
     *
     * @since 8.7 Previously, the effect of the equals() and hashCode() methods was not defined. Callers
     *        should therefore be aware that third party implementations of the NodeInfo interface may
     *        not implement the correct semantics.
     */
    public int hashCode() {
        return element.hashCode() ^ (position << 13);
    }

    /**
     * Get the System ID for the node.
     *
     * @return the System Identifier of the entity in the source document
     *         containing the node, or null if not known. Note this is not the
     *         same as the base URI: the base URI can be modified by xml:base, but
     *         the system ID cannot.
     */
    public String getSystemId() {
        return element.getSystemId();
    }

    /**
     * Get the Base URI for the node, that is, the URI used for resolving a relative URI contained
     * in the node. This will be the same as the System ID unless xml:base has been used.
     *
     * @return the base URI of the node
     */
    public String getBaseURI() {
        // the base URI of a namespace node is the empty sequence
        return null;
    }

    /**
     * Get line number
     *
     * @return the line number of the node in its original source document; or
     *         -1 if not available
     */
    public int getLineNumber() {
        return element.getLineNumber();
    }

    /**
     * Determine the relative position of this node and another node, in document order.
     * The other node will always be in the same document.
     *
     * @param other The other node, whose position is to be compared with this
     *              node
     * @return -1 if this node precedes the other node, +1 if it follows the
     *         other node, or 0 if they are the same node. (In this case,
     *         isSameNode() will always return true, and the two nodes will
     *         produce the same result for generateId())
     */
    public int compareOrder(NodeInfo other) {
        if (other instanceof NamespaceNode && element.isSameNodeInfo(((NamespaceNode) other).element)) {
            int c = position - ((NamespaceNode) other).position;
            if (c == 0) {
                return 0;
            }
            if (c < 0) {
                return -1;
            }
            return +1;
        } else if (element.isSameNodeInfo(other)) {
            return +1;
        } else {
            return element.compareOrder(other);
        }
    }

    /**
     * Return the string value of the node. The interpretation of this depends on the type
     * of node. For a namespace node, it is the namespace URI.
     *
     * @return the string value of the node
     */
    public String getStringValue() {
        return nsBinding.getURI();
    }

    /**
     * Get the value of the item as a CharSequence. This is in some cases more efficient than
     * the version of the method that returns a String.
     */
    public CharSequence getStringValueCS() {
        return getStringValue();
    }

    /**
     * Get name code. The name code is a coded form of the node name: two nodes
     * with the same name code have the same namespace URI, the same local name,
     * and the same prefix. By masking the name code with &0xfffff, you get a
     * fingerprint: two nodes with the same fingerprint have the same local name
     * and namespace URI.
     *
     * @return an integer name code, which may be used to obtain the actual node
     *         name from the name pool
     * @see client.net.sf.saxon.ce.om.NamePool#allocate allocate
     * @see client.net.sf.saxon.ce.om.NamePool#getFingerprint getFingerprint
     */
    public int getNameCode() {
        if (namecode == -1) {
            if (nsBinding.getPrefix().isEmpty()) {
                return -1;
            } else {
                namecode = element.getNamePool().allocate("", "", nsBinding.getPrefix());
            }
        }
        return namecode;
    }

    /**
     * Get fingerprint. The fingerprint is a coded form of the expanded name
     * of the node: two nodes
     * with the same name code have the same namespace URI and the same local name.
     * A fingerprint of -1 should be returned for a node with no name.
     *
     * @return an integer fingerprint; two nodes with the same fingerprint have
     *         the same expanded QName
     */
    public int getFingerprint() {
        if (nsBinding.getPrefix().isEmpty()) {
            return -1;
        }
        return getNameCode() & NamePool.FP_MASK;
    }

    /**
     * Get the local part of the name of this node. This is the name after the ":" if any.
     *
     * @return the local part of the name. For an unnamed node, returns "". Unlike the DOM
     *         interface, this returns the full name in the case of a non-namespaced name.
     */
    public String getLocalPart() {
        return nsBinding.getPrefix();
    }

    /**
     * Get the URI part of the name of this node. This is the URI corresponding to the
     * prefix, or the URI of the default namespace if appropriate.
     *
     * @return The URI of the namespace of this node. Since the name of a namespace
     *         node is always an NCName (the namespace prefix), this method always returns "".
     */
    public String getURI() {
        return "";
    }

    /**
     * Get the display name of this node. For elements and attributes this is [prefix:]localname.
     * For unnamed nodes, it is an empty string.
     *
     * @return The display name of this node. For a node with no name, return
     *         an empty string.
     */
    public String getDisplayName() {
        return getLocalPart();
    }

    /**
     * Get the prefix of the name of the node. This is defined only for elements and attributes.
     * If the node has no prefix, or for other kinds of node, return a zero-length string.
     *
     * @return The prefix of the name of the node.
     */
    public String getPrefix() {
        return "";
    }

    /**
     * Get the configuration
     */
    public Configuration getConfiguration() {
        return element.getConfiguration();
    }

    /**
     * Get the NamePool that holds the namecode for this node
     *
     * @return the namepool
     */
    public NamePool getNamePool() {
        return element.getNamePool();
    }

    /**
     * Get the type annotation of this node, if any.
     * Returns -1 for kinds of nodes that have no annotation, and for elements annotated as
     * untyped, and attributes annotated as untypedAtomic.
     *
     * @return the type annotation of the node.
     */
    public int getTypeAnnotation() {
        return StandardNames.XS_STRING;
    }

    /**
     * Get the NodeInfo object representing the parent of this node
     *
     * @return the parent of this node; null if this node has no parent
     */
    public NodeInfo getParent() {
        return element;
    }

    /**
     * Return an iteration over all the nodes reached by the given axis from this node
     *
     * @param axisNumber an integer identifying the axis; one of the constants
     *                   defined in class net.sf.saxon.om.Axis
     * @return an AxisIterator that scans the nodes reached by the axis in
     *         turn.
     * @throws UnsupportedOperationException if the namespace axis is
     *                                       requested and this axis is not supported for this implementation.
     * @see client.net.sf.saxon.ce.om.Axis
     */
    public AxisIterator iterateAxis(byte axisNumber) {
        return iterateAxis(axisNumber, AnyNodeTest.getInstance());
    }

    /**
     * Return an iteration over all the nodes reached by the given axis from this node
     * that match a given NodeTest
     *
     * @param axisNumber an integer identifying the axis; one of the constants
     *                   defined in class net.sf.saxon.om.Axis
     * @param nodeTest   A pattern to be matched by the returned nodes; nodes
     *                   that do not match this pattern are not included in the result
     * @return a NodeEnumeration that scans the nodes reached by the axis in
     *         turn.
     * @throws UnsupportedOperationException if the namespace axis is
     *                                       requested and this axis is not supported for this implementation.
     * @see client.net.sf.saxon.ce.om.Axis
     */
    public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) {
        switch(axisNumber) {
            case Axis.ANCESTOR:
                return element.iterateAxis(Axis.ANCESTOR_OR_SELF, nodeTest);
            case Axis.ANCESTOR_OR_SELF:
                if (nodeTest.matches(this)) {
                    return new PrependIterator(this, element.iterateAxis(Axis.ANCESTOR_OR_SELF, nodeTest));
                } else {
                    return element.iterateAxis(Axis.ANCESTOR_OR_SELF, nodeTest);
                }
            case Axis.ATTRIBUTE:
            case Axis.CHILD:
            case Axis.DESCENDANT:
            case Axis.DESCENDANT_OR_SELF:
            case Axis.FOLLOWING_SIBLING:
            case Axis.NAMESPACE:
            case Axis.PRECEDING_SIBLING:
                return EmptyIterator.getInstance();
            case Axis.FOLLOWING:
                return new Navigator.AxisFilter(new Navigator.FollowingEnumeration(this), nodeTest);
            case Axis.PARENT:
                return Navigator.filteredSingleton(element, nodeTest);
            case Axis.PRECEDING:
                return new Navigator.AxisFilter(new Navigator.PrecedingEnumeration(this, false), nodeTest);
            case Axis.SELF:
                return Navigator.filteredSingleton(this, nodeTest);
            case Axis.PRECEDING_OR_ANCESTOR:
                return new Navigator.AxisFilter(new Navigator.PrecedingEnumeration(this, true), nodeTest);
            default:
                throw new IllegalArgumentException("Unknown axis number " + axisNumber);
        }
    }

    /**
     * Get the root node of the tree containing this node
     *
     * @return the NodeInfo representing the top-level ancestor of this node.
     *         This will not necessarily be a document node
     */
    public NodeInfo getRoot() {
        return element.getRoot();
    }

    /**
     * Get the root node, if it is a document node.
     *
     * @return the DocumentInfo representing the containing document. If this
     *         node is part of a tree that does not have a document node as its
     *         root, return null.
     */
    public DocumentInfo getDocumentRoot() {
        return element.getDocumentRoot();
    }

    /**
     * Determine whether the node has any children.

     * Note: the result is equivalent to

     * getEnumeration(Axis.CHILD, AnyNodeTest.getInstance()).hasNext()
     *
     * @return True if the node has one or more children
     */
    public boolean hasChildNodes() {
        return false;
    }

    /**
     * Get a character string that uniquely identifies this node.
     * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
     *
     * @param buffer buffer to hold a string that uniquely identifies this node, across all
     *               documents.
     */
    public void generateId(FastStringBuffer buffer) {
        element.generateId(buffer);
        buffer.append("n");
        buffer.append(Integer.toString(position));
    }

    /**
     * Get the document number of the document containing this node. For a free-standing
     * orphan node, just return the hashcode.
     */
    public int getDocumentNumber() {
        return element.getDocumentNumber();
    }

    /**
     * Copy this node to a given outputter
     *
     * @param out         the Receiver to which the node should be copied
     * @param copyOptions a selection of the options defined
     */
    public void copy(Receiver out, int copyOptions) throws XPathException {
        out.namespace(nsBinding, ReceiverOptions.REJECT_DUPLICATES);
    }

    /**
     * Get all namespace undeclarations and undeclarations defined on this element.
     *
     * @param buffer If this is non-null, and the result array fits in this buffer, then the result
     *               may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
     * @return An array of integers representing the namespace declarations and undeclarations present on
     *         this element. For a node other than an element, return null. Otherwise, the returned array is a
     *         sequence of namespace codes, whose meaning may be interpreted by reference to the name pool. The
     *         top half word of each namespace code represents the prefix, the bottom half represents the URI.
     *         If the bottom half is zero, then this is a namespace undeclaration rather than a declaration.
     *         The XML namespace is never included in the list. If the supplied array is larger than required,
     *         then the first unused entry will be set to -1.
     *

     *
For a node other than an element, the method returns null.

     */
    public NamespaceBinding[] getDeclaredNamespaces(NamespaceBinding[] buffer) {
        return null;
    }

    /**
     * Get the typed value of the item
     *
     * @return the typed value of the item. In general this will be a sequence
     */
    public AtomicValue getTypedValue() {
        return new StringValue(getStringValueCS());
    }

    /**
     * Get the typed value. The result of this method will always be consistent with the method
     * {@link client.net.sf.saxon.ce.om.Item#getTypedValue()}. However, this method is often more convenient and may be
     * more efficient, especially in the common case where the value is expected to be a singleton.
     *
     * @return the typed value. If requireSingleton is set to true, the result will always be an
     *         AtomicValue. In other cases it may be a Value representing a sequence whose items are atomic
     *         values.
     * @since 8.5
     */
    public Value atomize() throws XPathException {
        return new StringValue(getStringValueCS());
    }

    /**
     * Factory method to create an iterator over the in-scope namespace nodes of an element
     * @param element the node whose namespaces are required
     * @param test used to filter the returned nodes
     * @return an iterator over the namespace nodes that satisfy the test
     */
    public static AxisIterator makeIterator(final NodeInfo element, NodeTest test) {
        List<NamespaceNode> nodes = new ArrayList();
        Iterator<NamespaceBinding> bindings = NamespaceIterator.iterateNamespaces(element);
        int position = 0;
        while (bindings.hasNext()) {
            NamespaceNode node = new NamespaceNode(element, bindings.next(), position++);
            if (test.matches(node)) {
                nodes.add(node);
            }
        }
        NamespaceNode node = new NamespaceNode(element, NamespaceBinding.XML, position);
        if (test.matches(node)) {
            nodes.add(node);
        }
        return new NodeListIterator(nodes);
    }
}


while (bindings.hasNext()) {
            NamespaceNode node = new NamespaceNode(element, bindings.next(), position++);
            if (test.matches(node)) {
                nodes.add(node);
            }
        }

package client.net.sf.saxon.ce.tree;

import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.event.Receiver;
import client.net.sf.saxon.ce.event.ReceiverOptions;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.pattern.AnyNodeTest;
import client.net.sf.saxon.ce.pattern.NodeTest;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.AxisIterator;
import client.net.sf.saxon.ce.tree.iter.EmptyIterator;
import client.net.sf.saxon.ce.tree.iter.NodeListIterator;
import client.net.sf.saxon.ce.tree.iter.PrependIterator;
import client.net.sf.saxon.ce.tree.util.FastStringBuffer;
import client.net.sf.saxon.ce.tree.util.NamespaceIterator;
import client.net.sf.saxon.ce.tree.util.Navigator;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.value.AtomicValue;
import client.net.sf.saxon.ce.value.StringValue;
import client.net.sf.saxon.ce.value.Value;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class NamespaceNode implements NodeInfo {

    NodeInfo element;

    NamespaceBinding nsBinding;

    int position;

    int namecode;

    /**
     * Create a namespace node
     *
     * @param element  the parent element of the namespace node
     * @param nscode   the namespace code, representing the prefix and URI of the namespace binding
     * @param position maintains document order among namespace nodes for the same element
     */
    public NamespaceNode(NodeInfo element, NamespaceBinding nscode, int position) {
        this.element = element;
        this.nsBinding = nscode;
        this.position = position;
        // evaluated lazily to avoid NamePool access
        namecode = -1;
    }

    /**
     * Get the kind of node. This will be a value such as Type.ELEMENT or Type.ATTRIBUTE
     *
     * @return an integer identifying the kind of node. These integer values are the
     *         same as those used in the DOM
     */
    public int getNodeKind() {
        return Type.NAMESPACE;
    }

    /**
     * Determine whether this is the same node as another node.
     * Note: a.isSameNodeInfo(b) if and only if generateId(a)==generateId(b).
     * This method has the same semantics as isSameNode() in DOM Level 3, but
     * works on Saxon NodeInfo objects rather than DOM Node objects.
     *
     * @param other the node to be compared with this node
     * @return true if this NodeInfo object and the supplied NodeInfo object represent
     *         the same node in the tree.
     */
    public boolean isSameNodeInfo(NodeInfo other) {
        return other instanceof NamespaceNode && element.isSameNodeInfo(((NamespaceNode) other).element) && nsBinding == ((NamespaceNode) other).nsBinding;
    }

    /**
     * The equals() method compares nodes for identity. It is defined to give the same result
     * as isSameNodeInfo().
     *
     * @param other the node to be compared with this node
     * @return true if this NodeInfo object and the supplied NodeInfo object represent
     *         the same node in the tree.
     * @since 8.7 Previously, the effect of the equals() method was not defined. Callers
     *        should therefore be aware that third party implementations of the NodeInfo interface may
     *        not implement the correct semantics. It is safer to use isSameNodeInfo() for this reason.
     *        The equals() method has been defined because it is useful in contexts such as a Java Set or HashMap.
     */
    public boolean equals(Object other) {
        return other instanceof NodeInfo && isSameNodeInfo((NodeInfo) other);
    }

    /**
     * The hashCode() method obeys the contract for hashCode(): that is, if two objects are equal
     * (represent the same node) then they must have the same hashCode()
     *
     * @since 8.7 Previously, the effect of the equals() and hashCode() methods was not defined. Callers
     *        should therefore be aware that third party implementations of the NodeInfo interface may
     *        not implement the correct semantics.
     */
    public int hashCode() {
        return element.hashCode() ^ (position << 13);
    }

    /**
     * Get the System ID for the node.
     *
     * @return the System Identifier of the entity in the source document
     *         containing the node, or null if not known. Note this is not the
     *         same as the base URI: the base URI can be modified by xml:base, but
     *         the system ID cannot.
     */
    public String getSystemId() {
        return element.getSystemId();
    }

    /**
     * Get the Base URI for the node, that is, the URI used for resolving a relative URI contained
     * in the node. This will be the same as the System ID unless xml:base has been used.
     *
     * @return the base URI of the node
     */
    public String getBaseURI() {
        // the base URI of a namespace node is the empty sequence
        return null;
    }

    /**
     * Get line number
     *
     * @return the line number of the node in its original source document; or
     *         -1 if not available
     */
    public int getLineNumber() {
        return element.getLineNumber();
    }

    /**
     * Determine the relative position of this node and another node, in document order.
     * The other node will always be in the same document.
     *
     * @param other The other node, whose position is to be compared with this
     *              node
     * @return -1 if this node precedes the other node, +1 if it follows the
     *         other node, or 0 if they are the same node. (In this case,
     *         isSameNode() will always return true, and the two nodes will
     *         produce the same result for generateId())
     */
    public int compareOrder(NodeInfo other) {
        if (other instanceof NamespaceNode && element.isSameNodeInfo(((NamespaceNode) other).element)) {
            int c = position - ((NamespaceNode) other).position;
            if (c == 0) {
                return 0;
            }
            if (c < 0) {
                return -1;
            }
            return +1;
        } else if (element.isSameNodeInfo(other)) {
            return +1;
        } else {
            return element.compareOrder(other);
        }
    }

    /**
     * Return the string value of the node. The interpretation of this depends on the type
     * of node. For a namespace node, it is the namespace URI.
     *
     * @return the string value of the node
     */
    public String getStringValue() {
        return nsBinding.getURI();
    }

    /**
     * Get the value of the item as a CharSequence. This is in some cases more efficient than
     * the version of the method that returns a String.
     */
    public CharSequence getStringValueCS() {
        return getStringValue();
    }

    /**
     * Get name code. The name code is a coded form of the node name: two nodes
     * with the same name code have the same namespace URI, the same local name,
     * and the same prefix. By masking the name code with &0xfffff, you get a
     * fingerprint: two nodes with the same fingerprint have the same local name
     * and namespace URI.
     *
     * @return an integer name code, which may be used to obtain the actual node
     *         name from the name pool
     * @see client.net.sf.saxon.ce.om.NamePool#allocate allocate
     * @see client.net.sf.saxon.ce.om.NamePool#getFingerprint getFingerprint
     */
    public int getNameCode() {
        if (namecode == -1) {
            if (nsBinding.getPrefix().isEmpty()) {
                return -1;
            } else {
                namecode = element.getNamePool().allocate("", "", nsBinding.getPrefix());
            }
        }
        return namecode;
    }

    /**
     * Get fingerprint. The fingerprint is a coded form of the expanded name
     * of the node: two nodes
     * with the same name code have the same namespace URI and the same local name.
     * A fingerprint of -1 should be returned for a node with no name.
     *
     * @return an integer fingerprint; two nodes with the same fingerprint have
     *         the same expanded QName
     */
    public int getFingerprint() {
        if (nsBinding.getPrefix().isEmpty()) {
            return -1;
        }
        return getNameCode() & NamePool.FP_MASK;
    }

    /**
     * Get the local part of the name of this node. This is the name after the ":" if any.
     *
     * @return the local part of the name. For an unnamed node, returns "". Unlike the DOM
     *         interface, this returns the full name in the case of a non-namespaced name.
     */
    public String getLocalPart() {
        return nsBinding.getPrefix();
    }

    /**
     * Get the URI part of the name of this node. This is the URI corresponding to the
     * prefix, or the URI of the default namespace if appropriate.
     *
     * @return The URI of the namespace of this node. Since the name of a namespace
     *         node is always an NCName (the namespace prefix), this method always returns "".
     */
    public String getURI() {
        return "";
    }

    /**
     * Get the display name of this node. For elements and attributes this is [prefix:]localname.
     * For unnamed nodes, it is an empty string.
     *
     * @return The display name of this node. For a node with no name, return
     *         an empty string.
     */
    public String getDisplayName() {
        return getLocalPart();
    }

    /**
     * Get the prefix of the name of the node. This is defined only for elements and attributes.
     * If the node has no prefix, or for other kinds of node, return a zero-length string.
     *
     * @return The prefix of the name of the node.
     */
    public String getPrefix() {
        return "";
    }

    /**
     * Get the configuration
     */
    public Configuration getConfiguration() {
        return element.getConfiguration();
    }

    /**
     * Get the NamePool that holds the namecode for this node
     *
     * @return the namepool
     */
    public NamePool getNamePool() {
        return element.getNamePool();
    }

    /**
     * Get the type annotation of this node, if any.
     * Returns -1 for kinds of nodes that have no annotation, and for elements annotated as
     * untyped, and attributes annotated as untypedAtomic.
     *
     * @return the type annotation of the node.
     */
    public int getTypeAnnotation() {
        return StandardNames.XS_STRING;
    }

    /**
     * Get the NodeInfo object representing the parent of this node
     *
     * @return the parent of this node; null if this node has no parent
     */
    public NodeInfo getParent() {
        return element;
    }

    /**
     * Return an iteration over all the nodes reached by the given axis from this node
     *
     * @param axisNumber an integer identifying the axis; one of the constants
     *                   defined in class net.sf.saxon.om.Axis
     * @return an AxisIterator that scans the nodes reached by the axis in
     *         turn.
     * @throws UnsupportedOperationException if the namespace axis is
     *                                       requested and this axis is not supported for this implementation.
     * @see client.net.sf.saxon.ce.om.Axis
     */
    public AxisIterator iterateAxis(byte axisNumber) {
        return iterateAxis(axisNumber, AnyNodeTest.getInstance());
    }

    /**
     * Return an iteration over all the nodes reached by the given axis from this node
     * that match a given NodeTest
     *
     * @param axisNumber an integer identifying the axis; one of the constants
     *                   defined in class net.sf.saxon.om.Axis
     * @param nodeTest   A pattern to be matched by the returned nodes; nodes
     *                   that do not match this pattern are not included in the result
     * @return a NodeEnumeration that scans the nodes reached by the axis in
     *         turn.
     * @throws UnsupportedOperationException if the namespace axis is
     *                                       requested and this axis is not supported for this implementation.
     * @see client.net.sf.saxon.ce.om.Axis
     */
    public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) {
        switch(axisNumber) {
            case Axis.ANCESTOR:
                return element.iterateAxis(Axis.ANCESTOR_OR_SELF, nodeTest);
            case Axis.ANCESTOR_OR_SELF:
                if (nodeTest.matches(this)) {
                    return new PrependIterator(this, element.iterateAxis(Axis.ANCESTOR_OR_SELF, nodeTest));
                } else {
                    return element.iterateAxis(Axis.ANCESTOR_OR_SELF, nodeTest);
                }
            case Axis.ATTRIBUTE:
            case Axis.CHILD:
            case Axis.DESCENDANT:
            case Axis.DESCENDANT_OR_SELF:
            case Axis.FOLLOWING_SIBLING:
            case Axis.NAMESPACE:
            case Axis.PRECEDING_SIBLING:
                return EmptyIterator.getInstance();
            case Axis.FOLLOWING:
                return new Navigator.AxisFilter(new Navigator.FollowingEnumeration(this), nodeTest);
            case Axis.PARENT:
                return Navigator.filteredSingleton(element, nodeTest);
            case Axis.PRECEDING:
                return new Navigator.AxisFilter(new Navigator.PrecedingEnumeration(this, false), nodeTest);
            case Axis.SELF:
                return Navigator.filteredSingleton(this, nodeTest);
            case Axis.PRECEDING_OR_ANCESTOR:
                return new Navigator.AxisFilter(new Navigator.PrecedingEnumeration(this, true), nodeTest);
            default:
                throw new IllegalArgumentException("Unknown axis number " + axisNumber);
        }
    }

    /**
     * Get the root node of the tree containing this node
     *
     * @return the NodeInfo representing the top-level ancestor of this node.
     *         This will not necessarily be a document node
     */
    public NodeInfo getRoot() {
        return element.getRoot();
    }

    /**
     * Get the root node, if it is a document node.
     *
     * @return the DocumentInfo representing the containing document. If this
     *         node is part of a tree that does not have a document node as its
     *         root, return null.
     */
    public DocumentInfo getDocumentRoot() {
        return element.getDocumentRoot();
    }

    /**
     * Determine whether the node has any children.

     * Note: the result is equivalent to

     * getEnumeration(Axis.CHILD, AnyNodeTest.getInstance()).hasNext()
     *
     * @return True if the node has one or more children
     */
    public boolean hasChildNodes() {
        return false;
    }

    /**
     * Get a character string that uniquely identifies this node.
     * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
     *
     * @param buffer buffer to hold a string that uniquely identifies this node, across all
     *               documents.
     */
    public void generateId(FastStringBuffer buffer) {
        element.generateId(buffer);
        buffer.append("n");
        buffer.append(Integer.toString(position));
    }

    /**
     * Get the document number of the document containing this node. For a free-standing
     * orphan node, just return the hashcode.
     */
    public int getDocumentNumber() {
        return element.getDocumentNumber();
    }

    /**
     * Copy this node to a given outputter
     *
     * @param out         the Receiver to which the node should be copied
     * @param copyOptions a selection of the options defined
     */
    public void copy(Receiver out, int copyOptions) throws XPathException {
        out.namespace(nsBinding, ReceiverOptions.REJECT_DUPLICATES);
    }

    /**
     * Get all namespace undeclarations and undeclarations defined on this element.
     *
     * @param buffer If this is non-null, and the result array fits in this buffer, then the result
     *               may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
     * @return An array of integers representing the namespace declarations and undeclarations present on
     *         this element. For a node other than an element, return null. Otherwise, the returned array is a
     *         sequence of namespace codes, whose meaning may be interpreted by reference to the name pool. The
     *         top half word of each namespace code represents the prefix, the bottom half represents the URI.
     *         If the bottom half is zero, then this is a namespace undeclaration rather than a declaration.
     *         The XML namespace is never included in the list. If the supplied array is larger than required,
     *         then the first unused entry will be set to -1.
     *

     *
For a node other than an element, the method returns null.

     */
    public NamespaceBinding[] getDeclaredNamespaces(NamespaceBinding[] buffer) {
        return null;
    }

    /**
     * Get the typed value of the item
     *
     * @return the typed value of the item. In general this will be a sequence
     */
    public AtomicValue getTypedValue() {
        return new StringValue(getStringValueCS());
    }

    /**
     * Get the typed value. The result of this method will always be consistent with the method
     * {@link client.net.sf.saxon.ce.om.Item#getTypedValue()}. However, this method is often more convenient and may be
     * more efficient, especially in the common case where the value is expected to be a singleton.
     *
     * @return the typed value. If requireSingleton is set to true, the result will always be an
     *         AtomicValue. In other cases it may be a Value representing a sequence whose items are atomic
     *         values.
     * @since 8.5
     */
    public Value atomize() throws XPathException {
        return new StringValue(getStringValueCS());
    }

    /**
     * Factory method to create an iterator over the in-scope namespace nodes of an element
     * @param element the node whose namespaces are required
     * @param test used to filter the returned nodes
     * @return an iterator over the namespace nodes that satisfy the test
     */
    public static AxisIterator makeIterator(final NodeInfo element, NodeTest test) {
        List<NamespaceNode> nodes = new ArrayList();
        Iterator<NamespaceBinding> bindings = NamespaceIterator.iterateNamespaces(element);
        int position = 0;

        NamespaceNode node = new NamespaceNode(element, NamespaceBinding.XML, position);
        if (test.matches(node)) {
            nodes.add(node);
        }
        return new NodeListIterator(nodes);
    }
}


public SegmentWriter newSegment() {
        return new SegmentWriter();
    }

package org.orekit.files.ccsds.ndm.odm.oem;

import java.io.IOException;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.FrameFacade;
import org.orekit.files.ccsds.section.Header;
import org.orekit.files.ccsds.utils.generation.Generator;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.sampling.OrekitFixedStepHandler;
import org.orekit.time.AbsoluteDate;

/**
 * A writer for OEM files.
 *
 *
 Each instance corresponds to a single OEM file. A new OEM ephemeris segment is
 * started by calling {@link #newSegment()}.
 *
 *
 This class can be used as a step handler for a {@link Propagator}.
 *
 *
{@code
 * Propagator propagator = ...; // pre-configured propagator
 * OEMWriter  aemWriter  = ...; // pre-configured writer
 *   try (Generator out = ...;  // set-up output stream
 *        StreamingOemWriter sw = new StreamingOemWriter(out, oemWriter)) { // set-up streaming writer
 *
 *     // write segment 1
 *     propagator.getMultiplexer().add(step, sw.newSegment());
 *     propagator.propagate(startDate1, stopDate1);
 *
 *     ...
 *
 *     // write segment n
 *     propagator.getMultiplexer().clear();
 *     propagator.getMultiplexer().add(step, sw.newSegment());
 *     propagator.propagate(startDateN, stopDateN);
 *
 *   }
 * }

 *
 * @author Evan Ward
 * @see CCSDS 502.0-B-2 Orbit Data
 *      Messages
 * @see CCSDS 500.0-G-4 Navigation
 *      Data Definitions and Conventions
 * @see OemWriter
 */
public class StreamingOemWriter implements AutoCloseable {

    /**
     * Generator for OEM output.
     */
    private final Generator generator;

    /**
     * Writer for the OEM message format.
     */
    private final OemWriter writer;

    private final Header header;

    /**
     * Current metadata.
     */
    private final OemMetadata metadata;

    /**
     * Indicator for writing header.
     */
    private boolean headerWritePending;

    /**
     * Simple constructor.
     * @param generator generator for OEM output
     * @param writer writer for the AEM message format
     * @param header file header (may be null)
     * @param template template for metadata
     * @since 11.0
     */
    public StreamingOemWriter(final Generator generator, final OemWriter writer, final Header header, final OemMetadata template) {
        this.generator = generator;
        this.writer = writer;
        this.header = header;
        this.metadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
        this.headerWritePending = true;
    }

    /**
     * Create a writer for a new OEM ephemeris segment.
     *
 The returned writer can only write a single ephemeris segment in an OEM.
     * This method must be called to create a writer for each ephemeris segment.
     * @return a new OEM segment writer, ready for use.
     */

    @Override
    public void close() throws IOException {
        writer.writeFooter(generator);
    }

    /**
     * A writer for a segment of an OEM.
     */
    public class SegmentWriter implements OrekitFixedStepHandler {

        /**
         * {@inheritDoc}
         *
         * <p> Sets the {@link OemMetadataKey#START_TIME} and {@link OemMetadataKey#STOP_TIME} in this
         * segment's metadata if not already set by the user. Then calls {@link OemWriter#writeHeader(Generator, Header)
         * writeHeader} if it is the first segment) and {@link OemWriter#writeMetadata(Generator, OemMetadata)}
         * to start the segment.
         */
        @Override
        public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
            try {
                final AbsoluteDate date = s0.getDate();
                if (t.isBefore(date)) {
                    throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES, date, t, date.durationFrom(t));
                }
                if (headerWritePending) {
                    // we write the header only for the first segment
                    writer.writeHeader(generator, header);
                    headerWritePending = false;
                }
                metadata.setStartTime(date);
                metadata.setUseableStartTime(null);
                metadata.setUseableStopTime(null);
                metadata.setStopTime(t);
                metadata.setReferenceFrame(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
                writer.writeMetadata(generator, metadata);
                writer.startData(generator);
            } catch (IOException e) {
                throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
            }
        }

        @Override
        public void handleStep(final SpacecraftState currentState) {
            try {
                writer.writeOrbitEphemerisLine(generator, metadata, currentState.getPVCoordinates(), true);
            } catch (IOException e) {
                throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
            }
        }

        @Override
        public void finish(final SpacecraftState finalState) {
            try {
                writer.endData(generator);
            } catch (IOException e) {
                throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
            }
        }
    }
}