# SourceType.java

    package org.thewonderlemming.c4plantuml.grammars;

    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.nio.charset.Charset;
    import java.nio.file.Path;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Optional;

    import org.antlr.v4.runtime.BaseErrorListener;
    import org.antlr.v4.runtime.CharStream;
    import org.antlr.v4.runtime.CharStreams;
    import org.antlr.v4.runtime.CommonTokenStream;
    import org.antlr.v4.runtime.Lexer;
    import org.antlr.v4.runtime.Parser;
    import org.antlr.v4.runtime.TokenStream;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L1Lexer;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L1Parser;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L2Lexer;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L2Parser;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L3Lexer;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L3Parser;

    /**
     * An enumeration that binds C4 grammars to their specific {@link Lexer} and {@link Parser}, and provides convenience
     * methods such as parsing source files and identifying a source C4 level.
     *
     * @author thewonderlemming
     *
     */
    public enum SourceType {

        /**
         * A C4 <a href="https://c4model.com/#SystemContextDiagram">System Context</a> grammar.
         */
        C4_L1(C4L1Lexer.class, C4L1Parser.class, "c4l1", "C4_L1"),

        /**
         * A C4 <a href="https://c4model.com/#ContainerDiagram">Container</a> grammar.
         */
        C4_L2(C4L2Lexer.class, C4L2Parser.class, "c4l2", "C4_L2"),

        /**
         * A C4 <a href="https://c4model.com/#ComponentDiagram">Component</a> grammar.
         */
        C4_L3(C4L3Lexer.class, C4L3Parser.class, "c4l3", "C4_L3");

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

        private static final Map<String, SourceType> SOURCE_TYPES_BY_FILENAME = new HashMap<>();

        private final String filenameStartsWith;

        private final String level;

        private final Class<? extends Lexer> lexerClass;

        private final Class<? extends Parser> parserClass;

        static {
            for (final SourceType sourceType : SourceType.values()) {
                SOURCE_TYPES_BY_FILENAME.put(sourceType.filenameStartsWith, sourceType);
            }
        }

        /**
         * Guesses the given source file C4 level and returns an {@link Optional} of the matching enumeration value if any, or
         * empty.
         *
         * @param fullyQualifiedName the C4 source file to guess the level from.
         * @return an {@link Optional} of the matching enumeration if any or empty.
         */
        public static Optional<SourceType> getSourceTypeFromFilename(final Path fullyQualifiedName) {

            final String filename = fullyQualifiedName.getFileName().toString();

            for (final Entry<String, SourceType> entry : SOURCE_TYPES_BY_FILENAME.entrySet()) {
                if (filename.toLowerCase().startsWith(entry.getKey())) {
                    return Optional.of(entry.getValue());
                }
            }

            return Optional.empty();
        }

        private SourceType(final Class<? extends Lexer> lexerClass, final Class<? extends Parser> parserClass,
            final String filenameStartsWith, final String level) {

            this.lexerClass = lexerClass;
            this.parserClass = parserClass;
            this.filenameStartsWith = filenameStartsWith;
            this.level = level;
        }

        /**
         * Parses the given C4 source file and returns an {@link Optional} of the created {@link Parser} or empty if any
         * exception occurs. Parsing errors are reported to the given {@link BaseErrorListener} instance.
         *
         * @param sourcePath the C4 source file to parse.
         * @param charset the {@link Charset} of the source file.
         * @param errorListener the error listener to report syntax error to.
         * @return n {@link Optional} of the new {@link Parser} instance or empty on error.
         */
        public Optional<? extends Parser> createParser(final Path sourcePath, final Charset charset,
            final BaseErrorListener errorListener) {

            try {

                final CharStream stream = CharStreams.fromPath(sourcePath, charset);
                final Lexer lexer = this.lexerClass.getDeclaredConstructor(CharStream.class).newInstance(stream);
                lexer.removeErrorListeners();
                lexer.addErrorListener(errorListener);

                final CommonTokenStream tokens = new CommonTokenStream(lexer);
                final Parser parser = this.parserClass.getDeclaredConstructor(TokenStream.class).newInstance(tokens);
                parser.removeErrorListeners();
                parser.addErrorListener(errorListener);

                return Optional.ofNullable(parser);

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

                LOGGER.error("Something went wrong as the lexer or the parser could not be instantiated", e);
            }

            return Optional.empty();
        }

        /**
         * Returns the associated C4 level of the current enumeration value.
         *
         * @return the current enumeration C4 level.
         */
        public String getC4Level() {
            return this.level;
        }

        /**
         * Returns the associated {@link Parser} type of the current enumeration value.
         *
         * @return the current enumeration {@link Parser} type.
         */
        @SuppressWarnings("unchecked")
        public Class<Parser> getParserType() {
            return (Class<Parser>) this.parserClass;
        }
    }