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.checks.javadoc;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import com.puppycrawl.tools.checkstyle.StatelessCheck;
  import com.puppycrawl.tools.checkstyle.api.DetailNode;
  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
  
  /**
   * <p>
   * Checks that one blank line before the block tag if it is present in Javadoc.
   * </p>
   * <ul>
   * <li>
   * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
   * if the Javadoc being examined by this check violates the tight html rules defined at
   * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
   * Tight-HTML Rules</a>.
   * Type is {@code boolean}.
   * Default value is {@code false}.
   * </li>
   * </ul>
   * <p>
   * To configure the check:
   * </p>
   * <pre>
   * &lt;module name=&quot;RequireEmptyLineBeforeBlockTagGroup&quot;/&gt;
   * </pre>
   * <p>
   * By default, the check will report a violation if there is no blank line before the block tag,
   * like in the example below.
   * </p>
   * <pre>
   * &#47;**
   *  * testMethod's javadoc.
   *  * &#64;return something (violation)
   *  *&#47;
   * public boolean testMethod() {
   *     return false;
   * }
   * </pre>
   * <p>
   *  Valid javadoc should have a blank line separating the parameters, return, throw, or
   *  other tags like in the example below.
   *  </p>
   *  <pre>
   *  &#47;**
   *  * testMethod's javadoc.
   *  *
   *  * &#64;param firstParam
   *  * &#64;return something
   *  *&#47;
   *  public boolean testMethod(int firstParam) {
   *      return false;
   *  }
   *  </pre>
   * <p>
   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
   * </p>
   * <p>
   * Violation Message Keys:
   * </p>
   * <ul>
   * <li>
   * {@code javadoc.missed.html.close}
   * </li>
   * <li>
   * {@code javadoc.parse.rule.error}
   * </li>
   * <li>
   * {@code javadoc.tag.line.before}
   * </li>
   * <li>
   * {@code javadoc.wrong.singleton.html.tag}
   * </li>
   * </ul>
   *
   * @since 8.36
   */
  @StatelessCheck
  public class RequireEmptyLineBeforeBlockTagGroupCheck extends AbstractJavadocCheck {
  
      /**
       * The key in "messages.properties" for the message that describes a tag in javadoc
       * requiring an empty line before it.
       */
      public static final String MSG_JAVADOC_TAG_LINE_BEFORE = "javadoc.tag.line.before";
  
      /**
       * Case when space separates the tag and the asterisk like in the below example.
       * <pre>
       *  /**
       *   * &#64;param noSpace there is no space here
       * </pre>
       */
      private static final List<Integer> ONLY_TAG_VARIATION_1 = Arrays.asList(
              JavadocTokenTypes.WS,
              JavadocTokenTypes.LEADING_ASTERISK,
              JavadocTokenTypes.NEWLINE);
  
      /**
       * Case when no space separates the tag and the asterisk like in the below example.
       * <pre>
       *  /**
       *   *&#64;param noSpace there is no space here
       * </pre>
       */
      private static final List<Integer> ONLY_TAG_VARIATION_2 = Arrays.asList(
              JavadocTokenTypes.LEADING_ASTERISK,
              JavadocTokenTypes.NEWLINE);
  
      /**
       * Returns only javadoc tags so visitJavadocToken only receives javadoc tags.
       *
       * @return only javadoc tags.
       */
      @Override
      public int[] getDefaultJavadocTokens() {
          return new int[] {
              JavadocTokenTypes.JAVADOC_TAG,
          };
      }
  
      @Override
      public int[] getRequiredJavadocTokens() {
          return getAcceptableJavadocTokens();
      }
  
      /**
       * Logs when there is no empty line before the tag.
       *
       * @param tagNode the at tag node to check for an empty space before it.
       */
      @Override
      public void visitJavadocToken(DetailNode tagNode) {
          // No need to filter token because overridden getDefaultJavadocTokens ensures that we only
          // receive JAVADOC_TAG DetailNode.
          if (!isAnotherTagBefore(tagNode)
                  && !isOnlyTagInWholeJavadoc(tagNode)
                  && hasInsufficientConsecutiveNewlines(tagNode)) {
              log(tagNode.getLineNumber(),
                      MSG_JAVADOC_TAG_LINE_BEFORE,
                      tagNode.getChildren()[0].getText());
          }
      }
  
      /**
       * Returns true when there is a javadoc tag before the provided tagNode.
       *
       * @param tagNode the javadoc tag node, to look for more tags before it.
       * @return true when there is a javadoc tag before the provided tagNode.
       */
      private static boolean isAnotherTagBefore(DetailNode tagNode) {
          boolean found = false;
          DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode);
          while (currentNode != null) {
              if (currentNode.getType() == JavadocTokenTypes.JAVADOC_TAG) {
                  found = true;
                  break;
              }
              currentNode = JavadocUtil.getPreviousSibling(currentNode);
          }
          return found;
      }
  
      /**
       * Returns true when there are is only whitespace and asterisks before the provided tagNode.
       * When javadoc has only a javadoc tag like {@literal @} in it, the JAVADOC_TAG in a JAVADOC
       * detail node will always have 2 or 3 siblings before it. The parse tree looks like:
       * <pre>
       * JAVADOC[3x0]
       * |--NEWLINE[3x0] : [\n]
       * |--LEADING_ASTERISK[4x0] : [ *]
       * |--WS[4x2] : [ ]
       * |--JAVADOC_TAG[4x3] : [@param T The bar.\n ]
       * </pre>
       * Or it can also look like:
       * <pre>
       * JAVADOC[3x0]
       * |--NEWLINE[3x0] : [\n]
       * |--LEADING_ASTERISK[4x0] : [ *]
       * |--JAVADOC_TAG[4x3] : [@param T The bar.\n ]
       * </pre>
       * We do not include the variation
       * <pre>
       *  /**&#64;param noSpace there is no space here
       * </pre>
       * which results in the tree
       * <pre>
       * JAVADOC[3x0]
       * |--JAVADOC_TAG[4x3] : [@param noSpace there is no space here\n ]
       * </pre>
       * because this one is invalid. We must recommend placing a blank line to separate &#64;param
       * from the first javadoc asterisks.
       *
       * @param tagNode the at tag node to check if there is nothing before it
       * @return true if there is no text before the tagNode
       */
      private static boolean isOnlyTagInWholeJavadoc(DetailNode tagNode) {
          final List<Integer> previousNodeTypes = new ArrayList<>();
          DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode);
          while (currentNode != null) {
              previousNodeTypes.add(currentNode.getType());
              currentNode = JavadocUtil.getPreviousSibling(currentNode);
          }
          return ONLY_TAG_VARIATION_1.equals(previousNodeTypes)
                  || ONLY_TAG_VARIATION_2.equals(previousNodeTypes);
      }
  
      /**
       * Returns true when there are not enough empty lines before the provided tagNode.
       *
       * <p>Iterates through the previous siblings of the tagNode looking for empty lines until
       * there are no more siblings or it hits something other than asterisk, whitespace or newline.
       * If it finds at least one empty line, return true. Return false otherwise.</p>
       *
       * @param tagNode the tagNode to check if there are sufficient empty lines before it.
       * @return true if there are not enough empty lines before the tagNode.
       */
      private static boolean hasInsufficientConsecutiveNewlines(DetailNode tagNode) {
          int count = 0;
          DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode);
          while (count <= 1
                  && currentNode != null
                  && (currentNode.getType() == JavadocTokenTypes.NEWLINE
                      || currentNode.getType() == JavadocTokenTypes.WS
                      || currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK)) {
              if (currentNode.getType() == JavadocTokenTypes.NEWLINE) {
                  count++;
              }
              currentNode = JavadocUtil.getPreviousSibling(currentNode);
          }
  
          return count <= 1;
      }
  }
  