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;
}
}