# LintingRulesFinder.java

    package org.thewonderlemming.c4plantuml.mojo.linting;

    import java.lang.reflect.Modifier;
    import java.util.Optional;
    import java.util.Set;
    import java.util.stream.Collectors;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.thewonderlemming.c4plantuml.linter.rules.AbstractLintingRule;
    import org.thewonderlemming.c4plantuml.mojo.linting.rules.custom.AbstractCustomLintingRule;
    import org.thewonderlemming.c4plantuml.mojo.linting.rules.custom.AbstractCustomLintingRuleFactory;

    import io.github.classgraph.ClassGraph;
    import io.github.classgraph.ClassInfo;
    import io.github.classgraph.ScanResult;

    /**
     * An utility class to detect and return {@link AbstractLintingRule} and {@link AbstractCustomLintingRule} within the
     * classpath, including the shipped dependencies.
     *
     * @author thewonderlemming
     *
     */
    public class LintingRulesFinder {

        private static final Logger LOGGER = LoggerFactory.getLogger(LintingRulesFinder.class);

        /**
         * Finds and returns every non-abstract child class of {@link AbstractLintingRule} in the classpath, including
         * within the dependencies.
         *
         * @return a set of the found {@link AbstractLintingRule} classes.
         */
        @SuppressWarnings("unchecked")
        public static Set<Class<? extends AbstractLintingRule>> findBuiltInLintingRules() {

            try (final ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {

                return scanResult
                    .getSubclasses(AbstractLintingRule.class.getName())
                        .stream()
                        .map(ClassInfo::getName)
                        .map(LintingRulesFinder::getClassForName)
                        .filter(Optional::isPresent)
                        .map(Optional::get)
                        .map(clazz -> (Class<? extends AbstractLintingRule>) clazz)
                        .filter(LintingRulesFinder::isNotAbstract)
                        .collect(Collectors.toSet());
            }
        }

        /**
         * Finds and returns every non-abstract child class of {@link AbstractCustomLintingRule} in the classpath, including
         * within the dependencies.
         *
         * @return a set of the found {@link AbstractCustomLintingRule} classes.
         */
        @SuppressWarnings("unchecked")
        public static Set<Class<? extends AbstractCustomLintingRuleFactory<?>>> findCustomLintingRuleFactories() {

            try (final ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {

                return scanResult
                    .getSubclasses(AbstractCustomLintingRuleFactory.class.getName())
                        .stream()
                        .map(ClassInfo::getName)
                        .map(LintingRulesFinder::getClassForName)
                        .filter(Optional::isPresent)
                        .map(Optional::get)
                        .map(clazz -> (Class<? extends AbstractCustomLintingRuleFactory<?>>) clazz)
                        .filter(LintingRulesFinder::isNotAbstract)
                        .collect(Collectors.toSet());
            }
        }

        private static Optional<Class<?>> getClassForName(final String className) {

            try {

                return Optional.ofNullable(Class.forName(className));

            } catch (final ClassNotFoundException e) {
                LOGGER.error("Error while scanning classpath to detect linting rules: {}", e.getMessage());
            }

            return Optional.empty();
        }

        private static boolean isNotAbstract(final Class<?> clazz) {
            return !Modifier.isAbstract(clazz.getModifiers());
        }

        private LintingRulesFinder() {
        }
    }