code: ///////////////////////////////////////////////////////////////////////////////////////////////
  // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
  // Copyright (C) 2001-2023 the original author or authors.
  //
  // This library is free software; you can redistribute it and/or
  // modify it under the terms of the GNU Lesser General Public
  // License as published by the Free Software Foundation; either
  // version 2.1 of the License, or (at your option) any later version.
  //
  // This library is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  // Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public
  // License along with this library; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  ///////////////////////////////////////////////////////////////////////////////////////////////
  
  package com.puppycrawl.tools.checkstyle;
  
  import java.util.BitSet;
  import java.util.Collections;
  import java.util.List;
  
  import org.antlr.v4.runtime.Token;
  
  import com.puppycrawl.tools.checkstyle.api.DetailAST;
  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
  
  /**
   * The implementation of {@link DetailAST}. This should only be directly used to
   * create custom AST nodes and in 'JavaAstVisitor.java'.
   *
   * @noinspection FieldNotUsedInToString
   * @noinspectionreason FieldNotUsedInToString - We require a specific string format for
   *      printing to CLI.
   */
  public final class DetailAstImpl implements DetailAST {
  
      /** Constant to indicate if not calculated the child count. */
      private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
  
      /** The line number. **/
      private int lineNo = NOT_INITIALIZED;
      /** The column number. **/
      private int columnNo = NOT_INITIALIZED;
  
      /** Number of children. */
      private int childCount = NOT_INITIALIZED;
      /** The parent token. */
      private DetailAstImpl parent;
      /** Previous sibling. */
      private DetailAstImpl previousSibling;
  
      /** First child of this DetailAST. */
      private DetailAstImpl firstChild;
  
      /** First sibling of this DetailAST.*/
      private DetailAstImpl nextSibling;
  
      /** Text of this DetailAST. */
      private String text;
  
      /** The type of this DetailAST. */
      private int type;
  
      /**
       * All tokens on COMMENTS channel to the left of the current token up to the
       * preceding token on the DEFAULT_TOKEN_CHANNEL.
       */
      private List<Token> hiddenBefore;
  
      /**
       * All tokens on COMMENTS channel to the right of the current token up to the
       * next token on the DEFAULT_TOKEN_CHANNEL.
       */
      private List<Token> hiddenAfter;
  
      /**
       * All token types in this branch.
       * Token 'x' (where x is an int) is in this branch
       * if branchTokenTypes.get(x) is true.
       */
      private BitSet branchTokenTypes;
  
      /**
       * Initializes this DetailAstImpl.
       *
       * @param tokenType the type of this DetailAstImpl
       * @param tokenText the text of this DetailAstImpl
       */
      public void initialize(int tokenType, String tokenText) {
          type = tokenType;
          text = tokenText;
      }
  
      /**
       * Initializes this DetailAstImpl.
       *
       * @param token the token to generate this DetailAstImpl from
       */
      public void initialize(Token token) {
          text = token.getText();
          type = token.getType();
          lineNo = token.getLine();
          columnNo = token.getCharPositionInLine();
      }
  
      /**
       * Add previous sibling.
       *
       * @param ast
       *        DetailAST object.
       */
      public void addPreviousSibling(DetailAST ast) {
          clearBranchTokenTypes();
          clearChildCountCache(parent);
          if (ast != null) {
              // parent is set in setNextSibling or parent.setFirstChild
              final DetailAstImpl previousSiblingNode = previousSibling;
              final DetailAstImpl astImpl = (DetailAstImpl) ast;
  
              if (previousSiblingNode != null) {
                  astImpl.previousSibling = previousSiblingNode;
                  previousSiblingNode.setNextSibling(astImpl);
              }
              else if (parent != null) {
                  parent.setFirstChild(astImpl);
              }
  
              astImpl.setNextSibling(this);
              previousSibling = astImpl;
          }
      }
  
      /**
       * Add next sibling, pushes other siblings back.
       *
       * @param ast DetailAST object.
       */
      public void addNextSibling(DetailAST ast) {
          clearBranchTokenTypes();
          clearChildCountCache(parent);
          if (ast != null) {
              // parent is set in setNextSibling
              final DetailAstImpl sibling = nextSibling;
              final DetailAstImpl astImpl = (DetailAstImpl) ast;
  
              if (sibling != null) {
                  astImpl.setNextSibling(sibling);
                  sibling.previousSibling = astImpl;
              }
  
              astImpl.previousSibling = this;
              setNextSibling(astImpl);
          }
      }
  
      /**
       * Adds a new child to the current AST.
       *
       * @param child to DetailAST to add as child
       */
      public void addChild(DetailAST child) {
          clearBranchTokenTypes();
          clearChildCountCache(this);
          if (child != null) {
              final DetailAstImpl astImpl = (DetailAstImpl) child;
              astImpl.setParent(this);
              astImpl.previousSibling = (DetailAstImpl) getLastChild();
          }
          DetailAST temp = firstChild;
          if (temp == null) {
              firstChild = (DetailAstImpl) child;
          }
          else {
              while (temp.getNextSibling() != null) {
                  temp = temp.getNextSibling();
              }
  
              ((DetailAstImpl) temp).setNextSibling(child);
          }
      }
  
      @Override
      public int getChildCount() {
          // lazy init
          if (childCount == NOT_INITIALIZED) {
              childCount = 0;
              DetailAST child = firstChild;
  
              while (child != null) {
                  childCount += 1;
                  child = child.getNextSibling();
              }
          }
          return childCount;
      }
  
      @Override
      public int getChildCount(int tokenType) {
          int count = 0;
          for (DetailAST ast = firstChild; ast != null; ast = ast.getNextSibling()) {
              if (ast.getType() == tokenType) {
                  count++;
              }
          }
          return count;
      }
  
      /**
       * Set the parent token.
       *
       * @param parent the parent token
       */
      private void setParent(DetailAstImpl parent) {
          DetailAstImpl instance = this;
          do {
              instance.clearBranchTokenTypes();
              instance.parent = parent;
              instance = instance.nextSibling;
          } while (instance != null);
      }
  
      @Override
      public DetailAST getParent() {
          return parent;
      }
  
      @Override
      public String getText() {
          return text;
      }
  
      /**
       * Sets the text for this DetailAstImpl.
       *
       * @param text the text field of this DetailAstImpl
       */
      public void setText(String text) {
          this.text = text;
      }
  
      @Override
      public int getType() {
          return type;
      }
  
      /**
       * Sets the type of this AST.
       *
       * @param type the token type of this DetailAstImpl
       */
      public void setType(int type) {
          this.type = type;
      }
  
      @Override
      public int getLineNo() {
          int resultNo = -1;
  
          if (lineNo == NOT_INITIALIZED) {
              // an inner AST that has been initialized
              // with initialize(String text)
              resultNo = findLineNo(firstChild);
  
              if (resultNo == -1) {
                  resultNo = findLineNo(nextSibling);
              }
          }
          if (resultNo == -1) {
              resultNo = lineNo;
          }
          return resultNo;
      }
  
      /**
       * Set line number.
       *
       * @param lineNo
       *        line number.
       */
      public void setLineNo(int lineNo) {
          this.lineNo = lineNo;
      }
  
      @Override
      public int getColumnNo() {
          int resultNo = -1;
  
          if (columnNo == NOT_INITIALIZED) {
              // an inner AST that has been initialized
              // with initialize(String text)
              resultNo = findColumnNo(firstChild);
  
              if (resultNo == -1) {
                  resultNo = findColumnNo(nextSibling);
              }
          }
          if (resultNo == -1) {
              resultNo = columnNo;
          }
          return resultNo;
      }
  
      /**
       * Set column number.
       *
       * @param columnNo
       *        column number.
       */
      public void setColumnNo(int columnNo) {
          this.columnNo = columnNo;
      }
  
      @Override
      public DetailAST getLastChild() {
          DetailAstImpl ast = firstChild;
          while (ast != null && ast.nextSibling != null) {
              ast = ast.nextSibling;
          }
          return ast;
      }
  
      /**
       * Finds column number in the first non-comment node.
       *
       * @param ast DetailAST node.
       * @return Column number if non-comment node exists, -1 otherwise.
       */
      private static int findColumnNo(DetailAST ast) {
          int resultNo = -1;
          DetailAST node = ast;
          while (node != null) {
              // comment node can't be start of any java statement/definition
              if (TokenUtil.isCommentType(node.getType())) {
                  node = node.getNextSibling();
              }
              else {
                  resultNo = node.getColumnNo();
                  break;
              }
          }
          return resultNo;
      }
  
      /**
       * Finds line number in the first non-comment node.
       *
       * @param ast DetailAST node.
       * @return Line number if non-comment node exists, -1 otherwise.
       */
      private static int findLineNo(DetailAST ast) {
          int resultNo = -1;
          DetailAST node = ast;
          while (node != null) {
              // comment node can't be start of any java statement/definition
              if (TokenUtil.isCommentType(node.getType())) {
                  node = node.getNextSibling();
              }
              else {
                  resultNo = node.getLineNo();
                  break;
              }
          }
          return resultNo;
      }
  
      /**
       * Returns token type with branch.
       *
       * @return the token types that occur in the branch as a sorted set.
       */
      private BitSet getBranchTokenTypes() {
          // lazy init
          if (branchTokenTypes == null) {
              branchTokenTypes = new BitSet();
              branchTokenTypes.set(type);
  
              // add union of all children
              DetailAstImpl child = firstChild;
              while (child != null) {
                  final BitSet childTypes = child.getBranchTokenTypes();
                  branchTokenTypes.or(childTypes);
  
                  child = child.nextSibling;
              }
          }
          return branchTokenTypes;
      }
  
      @Override
      public boolean branchContains(int tokenType) {
          return getBranchTokenTypes().get(tokenType);
      }
  
      @Override
      public DetailAST getPreviousSibling() {
          return previousSibling;
      }
  
      @Override
      public DetailAST findFirstToken(int tokenType) {
          DetailAST returnValue = null;
          for (DetailAST ast = firstChild; ast != null; ast = ast.getNextSibling()) {
              if (ast.getType() == tokenType) {
                  returnValue = ast;
                  break;
              }
          }
          return returnValue;
      }
  
      @Override
      public String toString() {
          return text + "[" + getLineNo() + "x" + getColumnNo() + "]";
      }
  
      @Override
      public DetailAstImpl getNextSibling() {
          return nextSibling;
      }
  
      @Override
      public DetailAstImpl getFirstChild() {
          return firstChild;
      }
  
      @Override
      public int getNumberOfChildren() {
          return getChildCount();
      }
  
      @Override
      public boolean hasChildren() {
          return firstChild != null;
      }
  
      /**
       * Clears the child count for the ast instance.
       *
       * @param ast The ast to clear.
       */
      private static void clearChildCountCache(DetailAstImpl ast) {
          if (ast != null) {
              ast.childCount = NOT_INITIALIZED;
          }
      }
  
      /**
       * Clears branchTokenTypes cache for all parents of the current DetailAST instance, and the
       * child count for the current DetailAST instance.
       */
      private void clearBranchTokenTypes() {
          DetailAstImpl prevParent = parent;
          while (prevParent != null) {
              prevParent.branchTokenTypes = null;
              prevParent = prevParent.parent;
          }
      }
  
      /**
       * Sets the next sibling of this AST.
       *
       * @param nextSibling the DetailAST to set as sibling
       */
      public void setNextSibling(DetailAST nextSibling) {
          clearBranchTokenTypes();
          clearChildCountCache(parent);
          this.nextSibling = (DetailAstImpl) nextSibling;
          if (nextSibling != null && parent != null) {
              ((DetailAstImpl) nextSibling).setParent(parent);
          }
          if (nextSibling != null) {
              ((DetailAstImpl) nextSibling).previousSibling = this;
          }
      }
  
      /**
       * Sets the first child of this AST.
       *
       * @param firstChild the DetailAST to set as first child
       */
      public void setFirstChild(DetailAST firstChild) {
          clearBranchTokenTypes();
          clearChildCountCache(this);
          this.firstChild = (DetailAstImpl) firstChild;
          if (firstChild != null) {
              ((DetailAstImpl) firstChild).setParent(this);
          }
      }
  
      /**
       * Removes all children of this AST.
       */
      public void removeChildren() {
          firstChild = null;
      }
  
      /**
       * Get list of tokens on COMMENTS channel to the left of the
       * current token up to the preceding token on the DEFAULT_TOKEN_CHANNEL.
       *
       * @return list of comment tokens
       */
      public List<Token> getHiddenBefore() {
          List<Token> returnList = null;
          if (hiddenBefore != null) {
              returnList = Collections.unmodifiableList(hiddenBefore);
          }
          return returnList;
      }
  
      /**
       * Get list tokens on COMMENTS channel to the right of the current
       * token up to the next token on the DEFAULT_TOKEN_CHANNEL.
       *
       * @return list of comment tokens
       */
      public List<Token> getHiddenAfter() {
          List<Token> returnList = null;
          if (hiddenAfter != null) {
              returnList = Collections.unmodifiableList(hiddenAfter);
          }
          return returnList;
      }
  
      /**
       * Sets the hiddenBefore token field.
       *
       * @param hiddenBefore comment token preceding this DetailAstImpl
       */
      public void setHiddenBefore(List<Token> hiddenBefore) {
          this.hiddenBefore = Collections.unmodifiableList(hiddenBefore);
      }
  
      /**
       * Sets the hiddenAfter token field.
       *
       * @param hiddenAfter comment token following this DetailAstImpl
       */
      public void setHiddenAfter(List<Token> hiddenAfter) {
          this.hiddenAfter = Collections.unmodifiableList(hiddenAfter);
      }
  }
  