# Linter.java

    package org.thewonderlemming.c4plantuml.linter;

    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Path;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;

    import org.antlr.v4.runtime.tree.ParseTree;
    import org.antlr.v4.runtime.tree.ParseTreeWalker;
    import org.thewonderlemming.c4plantuml.commons.Reporter;
    import org.thewonderlemming.c4plantuml.grammars.SourceType;
    import org.thewonderlemming.c4plantuml.linter.rules.AbstractLintingRule;

    /**
     * A linting engine that parses C4 files and passes the result {@link ParseTree} to a set of
     * {@link AbstractLintingRule}.
     *
     * @author thewonderlemming
     *
     */
    public class Linter {

        private final Set<AbstractLintingRule> activeRules = new HashSet<>();

        private final Reporter reporter;

        /**
         * returns a new instance of {@link LinterBuilder} since the default constructor is protected.
         *
         * @return a new builder instance.
         */
        public static LinterBuilder builder() {
            return new LinterBuilder();
        }

        /**
         * Default constructor.
         *
         * @param reporter the reporter to report linting errors to.
         * @param activeRules the set of {@link AbstractLintingRule} to use.
         */
        protected Linter(final Reporter reporter, final Set<AbstractLintingRule> activeRules) {

            this.reporter = reporter;
            this.activeRules.addAll(activeRules);
        }

        /**
         * Returns the set of active {@link AbstractLintingRule} to be used on parsed files.
         *
         * @return the set of active {@link AbstractLintingRule} to use.
         */
        public Set<AbstractLintingRule> getActiveRules() {
            return Collections.unmodifiableSet(this.activeRules);
        }

        /**
         * Same as {@link Linter#lint(Path, Charset)} but with UTF-8 as default {@link Charset}.
         *
         * @see Linter#lint(Path, Charset)
         *
         * @param c4SourceFile the C4 file to process.
         */
        public void lint(final Path c4SourceFile) {
            lint(c4SourceFile, StandardCharsets.UTF_8);
        }

        /**
         * Parses a C4 file and if no error occurs, passes the resulting {@link ParseTree} through every active
         * {@link AbstractLintingRule}.
         * <p>
         * Any syntax error in the file will be reported to a {@link LinterSyntaxErrorListener}.
         * <p>
         * Linting errors will be reported to the instance {@link Reporter}.
         *
         * @param c4SourceFile the C4 source file to process.
         * @param charset the {@link Charset} of the file.
         */
        public void lint(final Path c4SourceFile, final Charset charset) {

            SourceType
                .getSourceTypeFromFilename(c4SourceFile)
                    .ifPresent(sourceType -> {

                        final LinterSyntaxErrorListener errorListener = new LinterSyntaxErrorListener();

                        sourceType
                            .createParser(c4SourceFile, charset, errorListener)
                                .ifPresent(parser -> this.activeRules
                                    .stream()
                                        .filter(rule -> rule.acceptableParsersTypes().contains(parser.getClass()))
                                        .forEach(rule -> {

                                            errorListener.registerRule(rule);

                                            rule
                                                .selectParseTree(parser)
                                                    .ifPresent(tree -> {
                                                        rule
                                                            .createParseTreeListener(this.reporter, parser.getClass())
                                                                .ifPresent(
                                                                    listener -> new ParseTreeWalker().walk(listener, tree));

                                                        rule.wrapUp();
                                                    });

                                            errorListener.unregisterRule(rule);
                                        }));
                    });
        }
    }