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.internal.utils;
  
  import java.io.File;
  import java.io.IOException;
  import java.lang.reflect.Field;
  import java.nio.charset.Charset;
  import java.text.MessageFormat;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.Locale;
  import java.util.Properties;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  import com.google.common.reflect.ClassPath;
  import com.puppycrawl.tools.checkstyle.api.FileText;
  import com.puppycrawl.tools.checkstyle.checks.coding.AbstractSuperCheck;
  import com.puppycrawl.tools.checkstyle.checks.naming.AbstractAccessControlNameCheck;
  import com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck;
  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck;
  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck;
  import com.puppycrawl.tools.checkstyle.checks.whitespace.AbstractParenPadCheck;
  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
  
  public final class CheckUtil {
  
      public static final String CRLF = "\r\n";
  
      private CheckUtil() {
      }
  
      public static Set<String> getConfigCheckStyleModules() {
          return getCheckStyleModulesReferencedInConfig("config/checkstyle-checks.xml");
      }
  
      public static Set<String> getConfigSunStyleModules() {
          return getCheckStyleModulesReferencedInConfig("src/main/resources/sun_checks.xml");
      }
  
      public static Set<String> getConfigGoogleStyleModules() {
          return getCheckStyleModulesReferencedInConfig("src/main/resources/google_checks.xml");
      }
  
      /**
       * Retrieves a set of class names, removing 'Check' from the end if the class is
       * a checkstyle check.
       *
       * @param checks class instances.
       * @return a set of simple names.
       */
      public static Set<String> getSimpleNames(Set<Class<?>> checks) {
          return checks.stream().map(check -> {
              String name = check.getSimpleName();
  
              if (name.endsWith("Check")) {
                  name = name.substring(0, name.length() - 5);
              }
  
              return name;
          }).collect(Collectors.toCollection(HashSet::new));
      }
  
      /**
       * Gets a set of names of checkstyle's checks which are referenced in checkstyle-checks.xml.
       *
       * @param configFilePath
       *            file path of checkstyle-checks.xml.
       * @return names of checkstyle's checks which are referenced in checkstyle-checks.xml.
       */
      private static Set<String> getCheckStyleModulesReferencedInConfig(String configFilePath) {
          try {
              final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  
              // Validations of XML file make parsing too slow, that is why we
              // disable all validations.
              factory.setNamespaceAware(false);
              factory.setValidating(false);
              factory.setFeature("http://xml.org/sax/features/namespaces", false);
              factory.setFeature("http://xml.org/sax/features/validation", false);
              factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
                      false);
              factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",
                      false);
  
              final DocumentBuilder builder = factory.newDocumentBuilder();
              final Document document = builder.parse(new File(configFilePath));
  
              // optional, but recommended
              // FYI:
              // http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-
              // how-does-it-work
              document.getDocumentElement().normalize();
  
              final NodeList nodeList = document.getElementsByTagName("module");
  
              final Set<String> checksReferencedInCheckstyleChecksXml = new HashSet<>();
              for (int i = 0; i < nodeList.getLength(); i++) {
                  final Node currentNode = nodeList.item(i);
                  if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
                      final Element module = (Element) currentNode;
                      final String checkName = module.getAttribute("name");
                      checksReferencedInCheckstyleChecksXml.add(checkName);
                  }
              }
              return checksReferencedInCheckstyleChecksXml;
          }
          catch (Exception exception) {
              throw new IllegalStateException(exception);
          }
      }
  
      /**
       * Gets all checkstyle's non-abstract checks.
       *
       * @return the set of checkstyle's non-abstract check classes.
       * @throws IOException if the attempt to read class path resources failed.
       */
      public static Set<Class<?>> getCheckstyleChecks() throws IOException {
          final ClassLoader loader = Thread.currentThread()
                  .getContextClassLoader();
          final String packageName = "com.puppycrawl.tools.checkstyle";
          return getCheckstyleModulesRecursive(packageName, loader).stream()
                  .filter(ModuleReflectionUtil::isCheckstyleTreeWalkerCheck)
                  .collect(Collectors.toSet());
      }
  
      /**
       * Gets all checkstyle's modules.
       *
       * @return the set of checkstyle's module classes.
       * @throws IOException if the attempt to read class path resources failed.
       */
      public static Set<Class<?>> getCheckstyleModules() throws IOException {
          final ClassLoader loader = Thread.currentThread()
                  .getContextClassLoader();
          final String packageName = "com.puppycrawl.tools.checkstyle";
          return getCheckstyleModulesRecursive(packageName, loader);
      }
  
      /**
       * Gets checkstyle's modules in the given package recursively.
       *
       * @param packageName the package name to use
       * @param loader the class loader used to load Checkstyle package name
       * @return the set of checkstyle's module classes
       * @throws IOException if the attempt to read class path resources failed
       * @see ModuleReflectionUtil#isCheckstyleModule(Class)
       */
      private static Set<Class<?>> getCheckstyleModulesRecursive(
              String packageName, ClassLoader loader) throws IOException {
          final ClassPath classPath = ClassPath.from(loader);
          return classPath.getTopLevelClassesRecursive(packageName).stream()
                  .map(ClassPath.ClassInfo::load)
                  .filter(ModuleReflectionUtil::isCheckstyleModule)
                  .filter(CheckUtil::isFromAllowedPackages)
                  .collect(Collectors.toSet());
      }
  
      /**
       * Checks that class is from allowed packages.
       *
       * @param cls class to check
       * @return true if class is from allowed packages, false otherwise
       */
      private static boolean isFromAllowedPackages(Class<?> cls) {
          final String canonicalName = cls.getCanonicalName();
          return !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.packageobjectfactory")
              && !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.internal.testmodules");
      }
  
      /**
       * Gets the check's messages.
       *
       * @param module class to examine.
       * @param deepScan scan subclasses.
       * @return a set of checkstyle's module message fields.
       * @throws ClassNotFoundException if the attempt to read a protected class fails.
       * @noinspection BooleanParameter
       * @noinspectionreason BooleanParameter - boolean parameter eases testing
       */
      public static Set<Field> getCheckMessages(Class<?> module, boolean deepScan)
              throws ClassNotFoundException {
          final Set<Field> checkstyleMessages = new HashSet<>();
  
          // get all fields from current class
          final Field[] fields = module.getDeclaredFields();
  
          for (Field field : fields) {
              if (field.getName().startsWith("MSG_")) {
                  checkstyleMessages.add(field);
              }
          }
  
          // deep scan class through hierarchy
          final Class<?> superModule = module.getSuperclass();
  
          if (superModule != null && (deepScan || shouldScanDeepClassForMessages(superModule))) {
              checkstyleMessages.addAll(getCheckMessages(superModule, deepScan));
          }
  
          // special cases that require additional classes
          if (module == RegexpMultilineCheck.class) {
              checkstyleMessages.addAll(getCheckMessages(Class
                      .forName("com.puppycrawl.tools.checkstyle.checks.regexp.MultilineDetector"),
                      deepScan));
          }
          else if (module == RegexpSinglelineCheck.class
                  || module == RegexpSinglelineJavaCheck.class) {
              checkstyleMessages.addAll(getCheckMessages(Class
                      .forName("com.puppycrawl.tools.checkstyle.checks.regexp.SinglelineDetector"),
                      deepScan));
          }
  
          return checkstyleMessages;
      }
  
      /**
       * Should the class be deep scanned for messages.
       *
       * @param superModule The class to examine.
       * @return {@code true} if the class should be deeply scanned.
       */
      private static boolean shouldScanDeepClassForMessages(Class<?> superModule) {
          return superModule == AbstractNameCheck.class
                  || superModule == AbstractAccessControlNameCheck.class
                  || superModule == AbstractParenPadCheck.class
                  || superModule == AbstractSuperCheck.class;
      }
  
      /**
       * Gets the check message 'as is' from appropriate 'messages.properties'
       * file.
       *
       * @param module The package the message is located in.
       * @param locale the locale to get the message for.
       * @param messageKey the key of message in 'messages*.properties' file.
       * @param arguments the arguments of message in 'messages*.properties' file.
       * @return the check's formatted message.
       */
      public static String getCheckMessage(Class<?> module, Locale locale, String messageKey,
              Object... arguments) {
          String checkMessage;
          try {
              final Properties pr = new Properties();
              if (locale == Locale.ENGLISH) {
                  pr.load(module.getResourceAsStream("messages.properties"));
              }
              else {
                  pr.load(module
                          .getResourceAsStream("messages_" + locale.getLanguage() + ".properties"));
              }
              final MessageFormat formatter = new MessageFormat(pr.getProperty(messageKey), locale);
              checkMessage = formatter.format(arguments);
          }
          catch (IOException ignored) {
              checkMessage = null;
          }
          return checkMessage;
      }
  
      public static String getTokenText(int[] tokens, int... subtractions) {
          final String tokenText;
          if (subtractions.length == 0 && Arrays.equals(tokens, TokenUtil.getAllTokenIds())) {
              tokenText = "TokenTypes.";
          }
          else {
              final StringBuilder result = new StringBuilder(50);
              boolean first = true;
  
              for (int token : tokens) {
                  boolean found = false;
  
                  for (int subtraction : subtractions) {
                      if (subtraction == token) {
                          found = true;
                          break;
                      }
                  }
  
                  if (found) {
                      continue;
                  }
  
                  if (first) {
                      first = false;
                  }
                  else {
                      result.append(", ");
                  }
  
                  result.append(TokenUtil.getTokenName(token));
              }
  
              if (result.length() != 0) {
                  result.append('.');
              }
  
              tokenText = result.toString();
          }
          return tokenText;
      }
  
      public static Set<String> getTokenNameSet(int... tokens) {
          final Set<String> result = new HashSet<>();
  
          for (int token : tokens) {
              result.add(TokenUtil.getTokenName(token));
          }
  
          return result;
      }
  
      public static String getJavadocTokenText(int[] tokens, int... subtractions) {
          final StringBuilder result = new StringBuilder(50);
          boolean first = true;
  
          for (int token : tokens) {
              boolean found = false;
  
              for (int subtraction : subtractions) {
                  if (subtraction == token) {
                      found = true;
                      break;
                  }
              }
  
              if (found) {
                  continue;
              }
  
              if (first) {
                  first = false;
              }
              else {
                  result.append(", ");
              }
  
              result.append(JavadocUtil.getTokenName(token));
          }
  
          if (result.length() != 0) {
              result.append('.');
          }
  
          return result.toString();
      }
  
      public static String getLineSeparatorForFile(String filepath, Charset charset)
              throws IOException {
          final boolean[] crFound = {false};
          new FileText(new File(filepath), charset.name())
                  .getFullText()
                  .chars()
                  .peek(character -> {
                      if (character == '\r') {
                          crFound[0] = true;
                      }
                  })
                  .filter(character -> character == '\n')
                  .findFirst();
  
          final String result;
          if (crFound[0]) {
              result = CRLF;
          }
          else {
              result = "\n";
          }
          return result;
      }
  }
  