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.metrics;
  
  import java.util.ArrayDeque;
  import java.util.Deque;
  
  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  import com.puppycrawl.tools.checkstyle.api.DetailAST;
  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
  
  /**
   * <p>
   * Determines complexity of methods, classes and files by counting
   * the Non Commenting Source Statements (NCSS). This check adheres to the
   * <a href="http://www.kclee.de/clemens/java/javancss/#specification">specification</a>
   * for the <a href="http://www.kclee.de/clemens/java/javancss/">JavaNCSS-Tool</a>
   * written by <b>Chr. Clemens Lee</b>.
   * </p>
   * <p>
   * Roughly said the NCSS metric is calculated by counting the source lines which are
   * not comments, (nearly) equivalent to counting the semicolons and opening curly braces.
   * </p>
   * <p>
   * The NCSS for a class is summarized from the NCSS of all its methods, the NCSS
   * of its nested classes and the number of member variable declarations.
   * </p>
   * <p>
   * The NCSS for a file is summarized from the ncss of all its top level classes,
   * the number of imports and the package declaration.
   * </p>
   * <p>
   * Rationale: Too large methods and classes are hard to read and costly to maintain.
   * A large NCSS number often means that a method or class has too many responsibilities
   * and/or functionalities which should be decomposed into smaller units.
   * </p>
   * <ul>
   * <li>
   * Property {@code methodMaximum} - Specify the maximum allowed number of
   * non commenting lines in a method.
   * Type is {@code int}.
   * Default value is {@code 50}.
   * </li>
   * <li>
   * Property {@code classMaximum} - Specify the maximum allowed number of
   * non commenting lines in a class.
   * Type is {@code int}.
   * Default value is {@code 1500}.
   * </li>
   * <li>
   * Property {@code fileMaximum} - Specify the maximum allowed number of
   * non commenting lines in a file including all top level and nested classes.
   * Type is {@code int}.
   * Default value is {@code 2000}.
   * </li>
   * <li>
   * Property {@code recordMaximum} - Specify the maximum allowed number of
   * non commenting lines in a record.
   * Type is {@code int}.
   * Default value is {@code 150}.
   * </li>
   * </ul>
   * <p>
   * To configure the check:
   * </p>
   * <pre>
   * &lt;module name="JavaNCSS"/&gt;
   * </pre>
   * <p>Example:</p>
   * <pre>
   * public void test() {
   *   System.out.println("Line 1");
   *   // another 48 lines of code
   *   System.out.println("Line 50") // OK
   *   System.out.println("Line 51") // violation, the method crosses 50 non commented lines
   * }
   * </pre>
   * <p>
   * To configure the check with 40 allowed non commented lines for a method:
   * </p>
   * <pre>
   * &lt;module name="JavaNCSS"&gt;
   *   &lt;property name="methodMaximum" value="40"/&gt;
   * &lt;/module&gt;
   * </pre>
   * <p>Example:</p>
   * <pre>
   * public void test() {
   *   System.out.println("Line 1");
   *   // another 38 lines of code
   *   System.out.println("Line 40") // OK
   *   System.out.println("Line 41") // violation, the method crosses 40 non commented lines
   * }
   * </pre>
   * <p>
   * To configure the check to set limit of non commented lines in class to 100:
   * </p>
   * <pre>
   * &lt;module name="JavaNCSS"&gt;
   *   &lt;property name="classMaximum" value="100"/&gt;
   * &lt;/module&gt;
   * </pre>
   * <p>Example:</p>
   * <pre>
   * public class Test {
   *   public void test() {
   *       System.out.println("Line 1");
   *       // another 47 lines of code
   *       System.out.println("Line 49");
   *   }
   *
   *   public void test1() {
   *       System.out.println("Line 50"); // OK
   *       // another 47 lines of code
   *       System.out.println("Line 98"); // violation
   *   }
   * }
   * </pre>
   * <p>
   * To configure the check to set limit of non commented lines in file to 200:
   * </p>
   * <pre>
   * &lt;module name="JavaNCSS"&gt;
   *   &lt;property name="fileMaximum" value="200"/&gt;
   * &lt;/module&gt;
   * </pre>
   * <p>Example:</p>
   * <pre>
   * public class Test1 {
   *   public void test() {
   *       System.out.println("Line 1");
   *       // another 48 lines of code
   *       System.out.println("Line 49");
   *   }
   *
   *   public void test1() {
   *       System.out.println("Line 50");
   *       // another 47 lines of code
   *       System.out.println("Line 98"); // OK
   *   }
   * }
   *
   * class Test2 {
   *   public void test() {
   *       System.out.println("Line 150"); // OK
   *   }
   *
   *   public void test1() {
   *       System.out.println("Line 200"); // violation
   *   }
   * }
   * </pre>
   * <p>
   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
   * </p>
   * <p>
   * Violation Message Keys:
   * </p>
   * <ul>
   * <li>
   * {@code ncss.class}
   * </li>
   * <li>
   * {@code ncss.file}
   * </li>
   * <li>
   * {@code ncss.method}
   * </li>
   * <li>
   * {@code ncss.record}
   * </li>
   * </ul>
   *
   * @since 3.5
   */
  // -@cs[AbbreviationAsWordInName] We can not change it as,
  // check's name is a part of API (used in configurations).
  @FileStatefulCheck
  public class JavaNCSSCheck extends AbstractCheck {
  
      /**
       * A key is pointing to the warning message text in "messages.properties"
       * file.
       */
      public static final String MSG_METHOD = "ncss.method";
  
      /**
       * A key is pointing to the warning message text in "messages.properties"
       * file.
       */
      public static final String MSG_CLASS = "ncss.class";
  
      /**
       * A key is pointing to the warning message text in "messages.properties"
       * file.
       */
      public static final String MSG_RECORD = "ncss.record";
  
      /**
       * A key is pointing to the warning message text in "messages.properties"
       * file.
       */
      public static final String MSG_FILE = "ncss.file";
  
      /** Default constant for max file ncss. */
      private static final int FILE_MAX_NCSS = 2000;
  
      /** Default constant for max file ncss. */
      private static final int CLASS_MAX_NCSS = 1500;
  
      /** Default constant for max record ncss. */
      private static final int RECORD_MAX_NCSS = 150;
  
      /** Default constant for max method ncss. */
      private static final int METHOD_MAX_NCSS = 50;
  
      /**
       * Specify the maximum allowed number of non commenting lines in a file
       * including all top level and nested classes.
       */
      private int fileMaximum = FILE_MAX_NCSS;
  
      /** Specify the maximum allowed number of non commenting lines in a class. */
      private int classMaximum = CLASS_MAX_NCSS;
  
      /** Specify the maximum allowed number of non commenting lines in a record. */
      private int recordMaximum = RECORD_MAX_NCSS;
  
      /** Specify the maximum allowed number of non commenting lines in a method. */
      private int methodMaximum = METHOD_MAX_NCSS;
  
      /** List containing the stacked counters. */
      private Deque<Counter> counters;
  
      @Override
      public int[] getDefaultTokens() {
          return getRequiredTokens();
      }
  
      @Override
      public int[] getRequiredTokens() {
          return new int[] {
              TokenTypes.CLASS_DEF,
              TokenTypes.INTERFACE_DEF,
              TokenTypes.METHOD_DEF,
              TokenTypes.CTOR_DEF,
              TokenTypes.INSTANCE_INIT,
              TokenTypes.STATIC_INIT,
              TokenTypes.PACKAGE_DEF,
              TokenTypes.IMPORT,
              TokenTypes.VARIABLE_DEF,
              TokenTypes.CTOR_CALL,
              TokenTypes.SUPER_CTOR_CALL,
              TokenTypes.LITERAL_IF,
              TokenTypes.LITERAL_ELSE,
              TokenTypes.LITERAL_WHILE,
              TokenTypes.LITERAL_DO,
              TokenTypes.LITERAL_FOR,
              TokenTypes.LITERAL_SWITCH,
              TokenTypes.LITERAL_BREAK,
              TokenTypes.LITERAL_CONTINUE,
              TokenTypes.LITERAL_RETURN,
              TokenTypes.LITERAL_THROW,
              TokenTypes.LITERAL_SYNCHRONIZED,
              TokenTypes.LITERAL_CATCH,
              TokenTypes.LITERAL_FINALLY,
              TokenTypes.EXPR,
              TokenTypes.LABELED_STAT,
              TokenTypes.LITERAL_CASE,
              TokenTypes.LITERAL_DEFAULT,
              TokenTypes.RECORD_DEF,
              TokenTypes.COMPACT_CTOR_DEF,
          };
      }
  
      @Override
      public int[] getAcceptableTokens() {
          return getRequiredTokens();
      }
  
      @Override
      public void beginTree(DetailAST rootAST) {
          counters = new ArrayDeque<>();
  
          // add a counter for the file
          counters.push(new Counter());
      }
  
      @Override
      public void visitToken(DetailAST ast) {
          final int tokenType = ast.getType();
  
          if (tokenType == TokenTypes.CLASS_DEF
              || tokenType == TokenTypes.RECORD_DEF
              || isMethodOrCtorOrInitDefinition(tokenType)) {
              // add a counter for this class/method
              counters.push(new Counter());
          }
  
          // check if token is countable
          if (isCountable(ast)) {
              // increment the stacked counters
              counters.forEach(Counter::increment);
          }
      }
  
      @Override
      public void leaveToken(DetailAST ast) {
          final int tokenType = ast.getType();
  
          if (isMethodOrCtorOrInitDefinition(tokenType)) {
              // pop counter from the stack
              final Counter counter = counters.pop();
  
              final int count = counter.getCount();
              if (count > methodMaximum) {
                  log(ast, MSG_METHOD, count, methodMaximum);
              }
          }
          else if (tokenType == TokenTypes.CLASS_DEF) {
              // pop counter from the stack
              final Counter counter = counters.pop();
  
              final int count = counter.getCount();
              if (count > classMaximum) {
                  log(ast, MSG_CLASS, count, classMaximum);
              }
          }
          else if (tokenType == TokenTypes.RECORD_DEF) {
              // pop counter from the stack
              final Counter counter = counters.pop();
  
              final int count = counter.getCount();
              if (count > recordMaximum) {
                  log(ast, MSG_RECORD, count, recordMaximum);
              }
          }
      }
  
      @Override
      public void finishTree(DetailAST rootAST) {
          // pop counter from the stack
          final Counter counter = counters.pop();
  
          final int count = counter.getCount();
          if (count > fileMaximum) {
              log(rootAST, MSG_FILE, count, fileMaximum);
          }
      }
  
      /**
       * Setter to specify the maximum allowed number of non commenting lines
       * in a file including all top level and nested classes.
       *
       * @param fileMaximum
       *            the maximum ncss
       */
      public void setFileMaximum(int fileMaximum) {
          this.fileMaximum = fileMaximum;
      }
  
      /**
       * Setter to specify the maximum allowed number of non commenting lines in a class.
       *
       * @param classMaximum
       *            the maximum ncss
       */
      public void setClassMaximum(int classMaximum) {
          this.classMaximum = classMaximum;
      }
  
      /**
       * Setter to specify the maximum allowed number of non commenting lines in a record.
       *
       * @param recordMaximum
       *            the maximum ncss
       */
      public void setRecordMaximum(int recordMaximum) {
          this.recordMaximum = recordMaximum;
      }
  
      /**
       * Setter to specify the maximum allowed number of non commenting lines in a method.
       *
       * @param methodMaximum
       *            the maximum ncss
       */
      public void setMethodMaximum(int methodMaximum) {
          this.methodMaximum = methodMaximum;
      }
  
      /**
       * Checks if a token is countable for the ncss metric.
       *
       * @param ast
       *            the AST
       * @return true if the token is countable
       */
      private static boolean isCountable(DetailAST ast) {
          boolean countable = true;
  
          final int tokenType = ast.getType();
  
          // check if an expression is countable
          if (tokenType == TokenTypes.EXPR) {
              countable = isExpressionCountable(ast);
          }
          // check if a variable definition is countable
          else if (tokenType == TokenTypes.VARIABLE_DEF) {
              countable = isVariableDefCountable(ast);
          }
          return countable;
      }
  
      /**
       * Checks if a variable definition is countable.
       *
       * @param ast the AST
       * @return true if the variable definition is countable, false otherwise
       */
      private static boolean isVariableDefCountable(DetailAST ast) {
          boolean countable = false;
  
          // count variable definitions only if they are direct child to a slist or
          // object block
          final int parentType = ast.getParent().getType();
  
          if (parentType == TokenTypes.SLIST
              || parentType == TokenTypes.OBJBLOCK) {
              final DetailAST prevSibling = ast.getPreviousSibling();
  
              // is countable if no previous sibling is found or
              // the sibling is no COMMA.
              // This is done because multiple assignment on one line are counted
              // as 1
              countable = prevSibling == null
                      || prevSibling.getType() != TokenTypes.COMMA;
          }
  
          return countable;
      }
  
      /**
       * Checks if an expression is countable for the ncss metric.
       *
       * @param ast the AST
       * @return true if the expression is countable, false otherwise
       */
      private static boolean isExpressionCountable(DetailAST ast) {
          final boolean countable;
  
          // count expressions only if they are direct child to a slist (method
          // body, for loop...)
          // or direct child of label,if,else,do,while,for
          final int parentType = ast.getParent().getType();
          switch (parentType) {
              case TokenTypes.SLIST:
              case TokenTypes.LABELED_STAT:
              case TokenTypes.LITERAL_FOR:
              case TokenTypes.LITERAL_DO:
              case TokenTypes.LITERAL_WHILE:
              case TokenTypes.LITERAL_IF:
              case TokenTypes.LITERAL_ELSE:
                  // don't count if or loop conditions
                  final DetailAST prevSibling = ast.getPreviousSibling();
                  countable = prevSibling == null
                      || prevSibling.getType() != TokenTypes.LPAREN;
                  break;
              default:
                  countable = false;
                  break;
          }
          return countable;
      }
  
      /**
       * Checks if a token is a method, constructor, or compact constructor definition.
       *
       * @param tokenType the type of token we are checking
       * @return true if token type is method or ctor definition, false otherwise
       */
      private static boolean isMethodOrCtorOrInitDefinition(int tokenType) {
          return tokenType == TokenTypes.METHOD_DEF
                  || tokenType == TokenTypes.COMPACT_CTOR_DEF
                  || tokenType == TokenTypes.CTOR_DEF
                  || tokenType == TokenTypes.STATIC_INIT
                  || tokenType == TokenTypes.INSTANCE_INIT;
      }
  
      /**
       * Class representing a counter.
       *
       */
      private static final class Counter {
  
          /** The counters internal integer. */
          private int count;
  
          /**
           * Increments the counter.
           */
          public void increment() {
              count++;
          }
  
          /**
           * Gets the counters value.
           *
           * @return the counter
           */
          public int getCount() {
              return count;
          }
  
      }
  
  }
  