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;
  
  import static com.google.common.truth.Truth.assertWithMessage;
  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
  
  import java.util.List;
  
  import org.junit.jupiter.api.Test;
  
  import com.tngtech.archunit.core.domain.JavaClasses;
  import com.tngtech.archunit.core.domain.JavaModifier;
  import com.tngtech.archunit.core.importer.ClassFileImporter;
  import com.tngtech.archunit.core.importer.ImportOption;
  import com.tngtech.archunit.lang.ArchRule;
  import com.tngtech.archunit.lang.EvaluationResult;
  
  public class ArchUnitTest {
  
      /**
       * Suppression list containing violations from {@code classShouldNotDependOnUtilPackages}
       * ArchRule. Location of the violation (eg - {@code in (AbstractAutomaticBean.java:372)})
       * has been omitted as line number can change with modifications to the file.
       */
      private static final List<String> API_PACKAGE_SUPPRESSION_DETAILS = List.of(
          "Constructor <com.puppycrawl.tools.checkstyle.api.FileText.<init>(java.io.File, java.lang"
              + ".String)> gets field <com.puppycrawl.tools.checkstyle.utils.CommonUtil"
              + ".EMPTY_STRING_ARRAY>",
          "Constructor <com.puppycrawl.tools.checkstyle.api.FileText.<init>(java.io.File, java.util"
              + ".List)> gets field <com.puppycrawl.tools.checkstyle.utils.CommonUtil"
              + ".EMPTY_STRING_ARRAY>",
          "Method <com.puppycrawl.tools.checkstyle.api.AbstractCheck.log(com.puppycrawl.tools"
              + ".checkstyle.api.DetailAST, java.lang.String, [Ljava.lang.Object;)> calls method "
              + "<com.puppycrawl.tools.checkstyle.utils.CommonUtil.lengthExpandedTabs(java.lang"
              + ".String, int, int)>",
          "Method <com.puppycrawl.tools.checkstyle.api.AbstractCheck.log(int, int, java.lang"
              + ".String, [Ljava.lang.Object;)> calls method <com.puppycrawl.tools.checkstyle.utils"
              + ".CommonUtil.lengthExpandedTabs(java.lang.String, int, int)>",
          "Method <com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.log(int, int, java.lang"
              + ".String, [Ljava.lang.Object;)> calls method <com.puppycrawl.tools.checkstyle.utils"
              + ".CommonUtil.lengthExpandedTabs(java.lang.String, int, int)>",
          "Method <com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.process(java.io.File, "
              + "com.puppycrawl.tools.checkstyle.api.FileText)> calls method <com.puppycrawl.tools"
              + ".checkstyle.utils.CommonUtil.matchesFileExtension(java.io.File, [Ljava.lang"
              + ".String;)>",
          "Method <com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.setFileExtensions"
              + "([Ljava.lang.String;)> calls method <com.puppycrawl.tools.checkstyle.utils"
              + ".CommonUtil.startsWithChar(java.lang.String, char)>",
          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$PatternConverter"
              + ".convert(java.lang.Class, java.lang.Object)> calls method <com.puppycrawl.tools"
              + ".checkstyle.utils.CommonUtil.createPattern(java.lang.String)>",
          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$RelaxedStringArray"
              + "Converter.convert(java.lang.Class, java.lang.Object)> gets field <com.puppycrawl"
              + ".tools.checkstyle.utils.CommonUtil.EMPTY_STRING_ARRAY>",
          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$UriConverter.convert("
              + "java.lang.Class, java.lang.Object)> calls method <com.puppycrawl.tools.checkstyle"
              + ".utils.CommonUtil.getUriByFilename(java.lang.String)>",
          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$UriConverter.convert("
              + "java.lang.Class, java.lang.Object)> calls method <com.puppycrawl.tools.checkstyle"
              + ".utils.CommonUtil.isBlank(java.lang.String)>",
          "Method <com.puppycrawl.tools.checkstyle.api.FileContents.lineIsBlank(int)> calls method "
              + "<com.puppycrawl.tools.checkstyle.utils.CommonUtil.isBlank(java.lang.String)>"
      );
  
      /**
       * The goal is to ensure all classes of a specific name pattern have non-protected methods,
       * except for those which are annotated with {@code Override}. In the bytecode there is no
       * trace anymore if this method was annotated with {@code Override} or not (limitation of
       * Archunit), eventually we need to make checkstyle's Check on this.
       */
      @Test
      public void nonProtectedCheckMethodsTest() {
          // This list contains methods which have been overridden and are set to ignore in this test.
          final String[] methodsWithOverrideAnnotation = {
              "processFiltered",
              "getMethodName",
              "mustCheckName",
              "postProcessHeaderLines",
              "getLogMessageId",
          };
          final String ignoreMethodList = String.join("|", methodsWithOverrideAnnotation);
          final JavaClasses importedClasses = new ClassFileImporter()
                  .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                  .importPackages("com.puppycrawl.tools.checkstyle.checks");
  
          final ArchRule checkMethodsShouldNotBeProtectedRule =
                  methods().that()
                  .haveNameNotMatching(".*(" + ignoreMethodList + ")").and()
                  .areDeclaredInClassesThat()
                  .haveSimpleNameEndingWith("Check").and()
                  .areDeclaredInClassesThat()
                  .doNotHaveModifier(JavaModifier.ABSTRACT)
                  .should().notBeProtected();
  
          checkMethodsShouldNotBeProtectedRule.check(importedClasses);
      }
  
      /**
       * The goal is to ensure all classes in api package are not dependent on classes in util
       * packages. Changes in Util classes are not considered to be breaking changes as they are
       * "internal". Therefore classes in api should not depend on them.
       */
      @Test
      public void testClassesInApiDoNotDependOnClassesInUtil() {
          final JavaClasses apiPackage = new ClassFileImporter()
              .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
              .importPackages("com.puppycrawl.tools.checkstyle.api");
  
          final String[] utilPackages = {
              "com.puppycrawl.tools.checkstyle.utils",
              "com.puppycrawl.tools.checkstyle.checks.javadoc.utils",
          };
  
          final ArchRule classShouldNotDependOnUtilPackages = noClasses()
              .should()
              .dependOnClassesThat()
              .resideInAnyPackage(utilPackages);
  
          final EvaluationResult result = classShouldNotDependOnUtilPackages.evaluate(apiPackage);
          final EvaluationResult filtered = result.filterDescriptionsMatching(description -> {
              return API_PACKAGE_SUPPRESSION_DETAILS.stream()
                  .noneMatch(description::startsWith);
          });
  
          assertWithMessage("api package: " + classShouldNotDependOnUtilPackages.getDescription())
              .that(filtered.getFailureReport().getDetails())
              .isEmpty();
      }
  
  }
  