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() {
    }
}