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.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
  
  import java.util.Locale;
  import java.util.Optional;
  import java.util.Set;
  
  import org.junit.jupiter.api.Test;
  
  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
  import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
  import com.tngtech.archunit.base.DescribedPredicate;
  import com.tngtech.archunit.core.domain.JavaClass;
  import com.tngtech.archunit.core.domain.JavaClasses;
  import com.tngtech.archunit.core.domain.JavaType;
  import com.tngtech.archunit.core.domain.properties.HasName;
  import com.tngtech.archunit.core.importer.ClassFileImporter;
  import com.tngtech.archunit.core.importer.ImportOption;
  import com.tngtech.archunit.lang.ArchCondition;
  import com.tngtech.archunit.lang.ArchRule;
  import com.tngtech.archunit.lang.ConditionEvents;
  import com.tngtech.archunit.lang.SimpleConditionEvent;
  
  public class ArchUnitSuperClassTest {
  
      /**
       * Classes not abiding to {@link #testChecksShouldHaveAllowedAbstractClassAsSuperclass()} rule.
       */
      private static final Set<String> SUPPRESSED_CLASSES = Set.of(
          "com.puppycrawl.tools.checkstyle.checks.coding.SuperCloneCheck",
          "com.puppycrawl.tools.checkstyle.checks.coding.SuperFinalizeCheck",
          "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck",
          "com.puppycrawl.tools.checkstyle.checks.header.RegexpHeaderCheck",
          "com.puppycrawl.tools.checkstyle.checks.metrics.ClassDataAbstractionCouplingCheck",
          "com.puppycrawl.tools.checkstyle.checks.metrics.ClassFanOutComplexityCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.AbstractAccessControlNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.CatchParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.ClassTypeParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.InterfaceTypeParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.LambdaParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.MethodTypeParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.PatternVariableNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.RecordComponentNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.RecordTypeParameterNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.StaticVariableNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.naming.TypeNameCheck",
          "com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck",
          "com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck"
      );
  
      /**
       * ArchCondition checking that a class is the direct subclass of a particular class.
       *
       * @param superclass the superclass
       * @return ArchCondition checking that a class is the direct subclass of a particular class
       */
      private static ArchCondition<JavaClass> beDirectSubclassOf(Class<?> superclass) {
          return new SuperclassArchCondition(superclass);
      }
  
      /**
       * Tests that all checks have {@link AbstractCheck} or {@link AbstractFileSetCheck} or
       * {@link AbstractJavadocCheck} as their super class.
       */
      @Test
      public void testChecksShouldHaveAllowedAbstractClassAsSuperclass() {
          final JavaClasses checksPackage = new ClassFileImporter()
              .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
              .importPackages("com.puppycrawl.tools.checkstyle")
              .that(new DescribedPredicate<>("are checkstyle modules") {
                  @Override
                  public boolean test(JavaClass input) {
                      final Class<?> clazz = input.reflect();
                      return ModuleReflectionUtil.isCheckstyleModule(clazz)
                          && (ModuleReflectionUtil.isCheckstyleTreeWalkerCheck(clazz)
                              || ModuleReflectionUtil.isFileSetModule(clazz));
                  }
              });
  
          final ArchCondition<JavaClass> beSuppressedClass = new SuppressionArchCondition<>(
              SUPPRESSED_CLASSES, "be suppressed");
  
          final ArchRule checksShouldHaveAllowedAbstractClassAsSuper = classes()
              .should(beDirectSubclassOf(AbstractCheck.class)
                          .or(beDirectSubclassOf(AbstractFileSetCheck.class))
                          .or(beDirectSubclassOf(AbstractJavadocCheck.class)))
              .orShould(beSuppressedClass);
  
          checksShouldHaveAllowedAbstractClassAsSuper.check(checksPackage);
      }
  
      /**
       * ArchCondition checking that a given class extends the expected superclass.
       */
      private static final class SuperclassArchCondition extends ArchCondition<JavaClass> {
  
          private final Class<?> expectedSuperclass;
  
          private SuperclassArchCondition(Class<?> expectedSuperclass) {
              super("be subclass of " + expectedSuperclass.getSimpleName());
              this.expectedSuperclass = expectedSuperclass;
          }
  
          @Override
          public void check(JavaClass item, ConditionEvents events) {
              final Optional<JavaType> superclassOptional = item.getSuperclass();
              if (superclassOptional.isPresent()) {
                  final JavaClass superclass = superclassOptional.get().toErasure();
                  if (!superclass.isEquivalentTo(expectedSuperclass)) {
                      final String format = "<%s> is subclass of <%s> instead of <%s>";
                      final String message = String
                          .format(Locale.ROOT, format, item.getFullName(), superclass.getFullName(),
                                  expectedSuperclass.getName());
                      events.add(SimpleConditionEvent.violated(item, message));
                  }
              }
          }
      }
  
      /**
       * ArchCondition checking if a type or a member is present in the suppression list.
       */
      private static final class SuppressionArchCondition<T extends HasName.AndFullName>
          extends ArchCondition<T> {
  
          private final Set<String> suppressions;
  
          private SuppressionArchCondition(Set<String> suppressions, String description) {
              super(description);
              this.suppressions = suppressions;
          }
  
          @Override
          public void check(HasName.AndFullName item, ConditionEvents events) {
              if (!suppressions.contains(item.getFullName())) {
                  final String message = String.format(
                      Locale.ROOT, "should %s or resolved.", getDescription());
                  events.add(SimpleConditionEvent.violated(item, message));
              }
          }
      }
  }
  