BuiltInLintingRulesFactory.java

package org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thewonderlemming.c4plantuml.linter.rules.AbstractLintingRule;
import org.thewonderlemming.c4plantuml.linter.rules.RuleParameters;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.AliasesShouldBeListedInDictionaryRule;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.AliasesShouldBeUniqueRule;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.AliasesShouldFollowStandardConventionRule;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.NoDuplicateRelationshipsRule;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.NoOrphanAliasInBoundariesRule;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.NoOrphanAliasInLayoutsRule;
import org.thewonderlemming.c4plantuml.linter.rules.builtin.NoOrphanAliasInRelationshipsRule;
import org.thewonderlemming.c4plantuml.mojo.linting.BuiltInRules;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.AliasesShouldBeListedInDictionaryRuleParametersFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.AliasesShouldBeUniqueRuleParametersFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.AliasesShouldFollowStandardConventionRuleParametersFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.NoDuplicateRelationshipsRuleParametersFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.NoOrphanAliasInBoundariesRuleParametersFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.NoOrphanAliasInLayoutsRuleParametersFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.parameters.NoOrphanAliasInRelationshipsRuleParametersFactory;

/**
 * Maps {@link AbstractLintingRule} classes to their {@link AbstractRuleParametersFactory} and their name.
 *
 * @author thewonderlemming
 *
 */
public enum BuiltInLintingRulesFactory {

    /**
     * The {@link AliasesShouldBeListedInDictionaryRule} mapping.
     */
    ALIASES_SHOULD_BE_LISTED_IN_DICTIONARY(
        "aliasesShouldBeListedInDictionary",
        AliasesShouldBeListedInDictionaryRule.class,
        AliasesShouldBeListedInDictionaryRuleParametersFactory.class),

    /**
     * The {@link AliasesShouldBeUniqueRule} mapping.
     */
    ALIASES_SHOULD_BE_UNIQUE(
        "aliasesShouldBeUnique",
        AliasesShouldBeUniqueRule.class,
        AliasesShouldBeUniqueRuleParametersFactory.class),

    /**
     * The {@link AliasesShouldFollowStandardConventionRule} mapping.
     */
    ALIASES_SHOULD_FOLLOW_STANDARD_CONVENTION(
        "aliasesShouldFollowStandardConvention",
        AliasesShouldFollowStandardConventionRule.class,
        AliasesShouldFollowStandardConventionRuleParametersFactory.class),

    /**
     * The {@link NoDuplicateRelationshipsRule} mapping.
     */
    NO_DUPLICATES_IN_RELATIONSHIPS(
        "noDuplicateRelationships",
        NoDuplicateRelationshipsRule.class,
        NoDuplicateRelationshipsRuleParametersFactory.class),

    /**
     * The {@link NoOrphanAliasInBoundariesRule} mapping.
     */
    NO_ORPHAN_ALIAS_IN_BOUNDARIES(
        "noOrphanAliasInBoundaries",
        NoOrphanAliasInBoundariesRule.class,
        NoOrphanAliasInBoundariesRuleParametersFactory.class),

    /**
     * The {@link NoOrphanAliasInLayoutsRule} mapping.
     */
    NO_ORPHAN_ALIAS_IN_LAYOUTS(
        "noOrphanAliasInLayouts",
        NoOrphanAliasInLayoutsRule.class,
        NoOrphanAliasInLayoutsRuleParametersFactory.class),

    /**
     * The {@link NoOrphanAliasInRelationshipsRule} mapping.
     */
    NO_ORPHAN_ALIAS_IN_RELATIONSHIPS(
        "noOrphanAliasInRelationships",
        NoOrphanAliasInRelationshipsRule.class,
        NoOrphanAliasInRelationshipsRuleParametersFactory.class);


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

    private static final Map<String, BuiltInLintingRulesFactory> LOOKUP_BY_NAME = new HashMap<>();

    private static final Map<Class<? extends AbstractLintingRule>, BuiltInLintingRulesFactory> LOOKUP_BY_TYPE = new HashMap<>();

    private final String ruleName;

    private final Class<? extends AbstractRuleParametersFactory> ruleParametersFactoryType;

    private final Class<? extends AbstractLintingRule> ruleType;


    static {

        Stream
            .of(BuiltInLintingRulesFactory.values())
                .forEach(value -> {
                    LOOKUP_BY_NAME.put(value.ruleName, value);
                    LOOKUP_BY_TYPE.put(value.getRuleType(), value);
                });
    }


    /**
     * Builds a {@link AbstractLintingRule} instance, given its name and the {@link BuiltInRules} parameters POJO.
     *
     * @param ruleName the name of the rule to build.
     * @param parameters the {@code pom.xml} configuration.
     * @return an {@link Optional} of the built rule on success, or empty else.
     */
    public static Optional<AbstractLintingRule> createInstanceForName(final String ruleName,
        final BuiltInRules parameters) {

        final BuiltInLintingRulesFactory rule = LOOKUP_BY_NAME.get(ruleName);

        if (null != rule) {

            try {

                final AbstractRuleParametersFactory config = rule.ruleParametersFactoryType
                    .getDeclaredConstructor(BuiltInRules.class)
                        .newInstance(parameters);

                LOGGER
                    .debug("Linting rule {} is activated: {}",
                        ruleName,
                        config.isActivated());

                return config.isActivated()
                    ? Optional.ofNullable(rule.createInstance(config.getRuleParameters()))
                    : Optional.empty();

            } catch (
                InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {

                LOGGER
                    .error("Cannot create instance of rule named '{}' because of exception: {}",
                        ruleName,
                        e.getMessage(),
                        e);
            }
        }

        return Optional.empty();
    }

    /**
     * Returns the mapped name of a {@link AbstractLintingRule}, given its type.
     *
     * @param ruleType the type of the rule to retrieve the name from.
     * @return an {@link Optional} of the name of the rule if found, empty else.
     */
    public static Optional<String> getRuleNameForType(final Class<? extends AbstractLintingRule> ruleType) {

        return LOOKUP_BY_TYPE.containsKey(ruleType)
            ? Optional.ofNullable(LOOKUP_BY_TYPE.get(ruleType).getRuleName())
            : Optional.empty();
    }

    /**
     * Tells whether or not the given {@link AbstractLintingRule} is a built-in rule, or a custom one, which requires
     * a different building process.
     *
     * @param lintingRuleType the type of the rule to check for.
     * @return {@code true} if the rule is a built-in one, {@code false} else.
     */
    public static boolean isBuiltInLintingRule(final Class<? extends AbstractLintingRule> lintingRuleType) {
        return LOOKUP_BY_TYPE.containsKey(lintingRuleType);
    }

    private BuiltInLintingRulesFactory(final String ruleName, final Class<? extends AbstractLintingRule> ruleType,
        final Class<? extends AbstractRuleParametersFactory> ruleParametersFactoryType) {

        this.ruleName = ruleName;
        this.ruleType = ruleType;
        this.ruleParametersFactoryType = ruleParametersFactoryType;
    }

    /**
     * Returns the name of the current mapping.
     *
     * @return the name of the mapping.
     */
    public String getRuleName() {
        return this.ruleName;
    }

    /**
     * Returns the {@link AbstractLintingRule} type of the current mapping.
     *
     * @return the type of the current mapping.
     */
    public Class<? extends AbstractLintingRule> getRuleType() {
        return this.ruleType;
    }

    private AbstractLintingRule createInstance(final RuleParameters parameters)
        throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
        NoSuchMethodException, SecurityException {

        return this.ruleType.getConstructor(RuleParameters.class).newInstance(parameters);
    }
}