Compare commits

...

4 commits

25 changed files with 523 additions and 85 deletions

View file

@ -203,11 +203,12 @@ One restriction is that a state and a messages may share have the same name, i.e
If the parser is successful it will hold the following data in the AST If the parser is successful it will hold the following data in the AST
```java ```java
public record DocumentNode( public record GeneratorNode(
Map<String, String> config, Map<String, String> config,
Set<TypeNode> typeDefinitions, Set<TypeNode> typeDefinitions,
List<EndpointNode> endpoints, List<EndpointNode> endpoints,
Set<StateNode> states) { Set<StateNode> states,
Meta meta) {
} }
``` ```
@ -304,3 +305,22 @@ public record TransitionNode(String message, String toState) {
``` ```
* `name` is the message name. * `name` is the message name.
* `toState` is the name of the target state. * `toState` is the name of the target state.
### Meta
The meta container holds information about the template file used to generate the output file.
```java
public record Meta(
List<String> templateDirectories,
String templateFile
) { }
```
* `templateDirectories` holds the subdirectory below the given `templateDir` where the template was found.
This is useful to generate e.g. java `package`statements like this:
```injectedfreemarker
<#list meta.templateDirectories>package <#items as dir>${dir}<#sep>.</#items>;</#list>
```
* `templateFile` is the filename of the template (including the .ftl-ending) used to generate the output.

View file

@ -0,0 +1,8 @@
# References
## Maven
https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#generatedsourcesdirectory
### For executing the plugin several times
See executions
https://maven.apache.org/guides/mini/guide-configuring-plugins.html

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>nu.zoom.dsl</groupId>
<artifactId>endgen</artifactId>
<version>1.2-SNAPSHOT</version>
</parent>
<artifactId>endgen-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<properties>
<maven-plugin-tools.version>3.15.1</maven-plugin-tools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.9.9</version>
<scope>provided</scope>
</dependency>
<!-- dependency on annotations -->
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven-plugin-tools.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>nu.zoom.dsl</groupId>
<artifactId>parser</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>${maven-plugin-tools.version}</version>
<executions>
<execution>
<id>help-mojo</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>nu.zoom.dsl</groupId>
<artifactId>endgen-maven-plugin</artifactId>
<version>1.2-SNAPSHOT</version>
<executions>
<execution>
<configuration>
<templates>${project.build.sourceDirectory}/main/endgen-templates</templates>
<output>${project.build.sourceDirectory}/generated-sources/endgen endpoints-output</output>
<dsl>${project.basedir}/../test01.endpoints</dsl>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View file

@ -0,0 +1,64 @@
package nu.zoom.dsl.maven;
import nu.zoom.dsl.run.Runner;
import nu.zoom.dsl.run.ValidationException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.File;
import java.util.Optional;
@Mojo(
name = "endgen",
defaultPhase = LifecyclePhase.GENERATE_SOURCES
)
public class EndgenMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.build.sourceDirectory}/main/endgen-templates")
File templates;
@Parameter(defaultValue = "${project.build.outputDirectory}/generated-sources/endgen")
File output;
@Parameter
File dsl;
@Parameter
String parser;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Running endgen");
getLog().info("Using dsl: " + dsl);
try {
Runner.run(
optional(dsl).map(File::toPath).orElseThrow(),
optional(templates).map(File::toPath),
optional(output).map(File::toPath),
getParserType(parser),
new MavenLogger(getLog())
);
} catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
private Optional<Runner.ParserType> getParserType(final String type) throws ValidationException {
if (type == null) {
return Optional.empty();
}
try {
return Optional.of(Runner.ParserType.valueOf(type));
} catch (IllegalArgumentException e) {
throw new ValidationException(e);
}
}
private <T> Optional<T> optional(T arg) {
return arg == null ? Optional.empty() : Optional.of(arg);
}
}

View file

@ -0,0 +1,17 @@
package nu.zoom.dsl.maven;
import nu.zoom.dsl.run.Logger;
import org.apache.maven.plugin.logging.Log;
public class MavenLogger implements Logger {
private final Log delegate;
public MavenLogger(Log delegate) {
this.delegate = delegate;
}
@Override
public void println(String message) {
this.delegate.debug(message);
}
}

View file

@ -0,0 +1,52 @@
package nu.zoom.dsl.maven;
import java.io.File;
public class Run {
private File templates;
private File output;
private File dsl;
private String parser;
public File getTemplates() {
return templates;
}
public void setTemplates(File templates) {
this.templates = templates;
}
public File getOutput() {
return output;
}
public void setOutput(File output) {
this.output = output;
}
public File getDsl() {
return dsl;
}
public void setDsl(File dsl) {
this.dsl = dsl;
}
public String getParser() {
return parser;
}
public void setParser(String parser) {
this.parser = parser;
}
@Override
public String toString() {
return "Run{" +
"templates=" + templates +
", output=" + output +
", dsl=" + dsl +
", parser='" + parser + '\'' +
'}';
}
}

View file

@ -1,3 +1,5 @@
Generated from template: ${meta.templateFile}
<#list endpoints as endpoint> <#list endpoints as endpoint>
<#list endpoint.paths.paths> <#list endpoint.paths.paths>
<#items as segment>/${segment}</#items> <#items as segment>/${segment}</#items>

View file

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ${config.package} <#list meta.templateDirectories>package <#items as dir>${dir}<#sep>.</#items>;</#list>
object Codecs: object Codecs:
<#list typeDefinitions as type> <#list typeDefinitions as type>

View file

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ${config.package} <#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.</#items>;</#list>
class Endpoints: class Endpoints:
<#list endpoints as endpoint> <#list endpoints as endpoint>

View file

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ${config.package} <#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.</#items>;</#list>
object Protocol: object Protocol:
<#list typeDefinitions?sort as type> <#list typeDefinitions?sort as type>

View file

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
package nu.zoom.dsl.ast; package nu.zoom.dsl.ast;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;

View file

@ -0,0 +1,13 @@
package nu.zoom.dsl.ast;
import java.util.List;
import java.util.Map;
import java.util.Set;
public record GeneratorNode(
Map<String, String> config,
Set<TypeNode> typeDefinitions,
List<EndpointNode> endpoints,
Set<StateNode> states,
Meta meta) {
}

View file

@ -0,0 +1,9 @@
package nu.zoom.dsl.ast;
import java.util.List;
public record Meta(
List<String> templateDirectories,
String templateFile
) {
}

View file

@ -14,7 +14,7 @@
package nu.zoom.dsl.cli; package nu.zoom.dsl.cli;
import nu.zoom.dsl.ast.DocumentNode; import nu.zoom.dsl.ast.DocumentNode;
import nu.zoom.dsl.ast.ParserWrapper; import nu.zoom.dsl.run.*;
import nu.zoom.dsl.freemarker.Generator; import nu.zoom.dsl.freemarker.Generator;
import picocli.CommandLine; import picocli.CommandLine;
import picocli.CommandLine.Command; import picocli.CommandLine.Command;
@ -24,8 +24,8 @@ import picocli.CommandLine.Parameters;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@Command( @Command(
@ -34,20 +34,16 @@ import java.util.concurrent.Callable;
description = "Generate source code from an endpoints specification file." description = "Generate source code from an endpoints specification file."
) )
public class EndpointsCLI implements Callable<Integer> { public class EndpointsCLI implements Callable<Integer> {
public enum ParserType {
Endpoints,
States
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Parameters(index = "0", description = "The source endpoints DSL file.") @Parameters(index = "0", description = "The source endpoints DSL file.")
private Path file; private Path file;
@SuppressWarnings("CanBeFinal") @SuppressWarnings("CanBeFinal")
@Option(names = {"-t", "--template"}, defaultValue = "endpoints-template", description = "The template directory. Default is ${DEFAULT-VALUE}") @Option(names = {"-t", "--template"}, defaultValue = Runner.DEFAULT_TEMPLATE_DIRECTORY_NAME, description = "The template directory. Default is ${DEFAULT-VALUE}")
private Path templateDir ; private Path templateDir ;
@SuppressWarnings("CanBeFinal") @SuppressWarnings("CanBeFinal")
@Option(names = {"-o", "--output"}, defaultValue = "endpoints-output", description = "The directory to write the generated code to. Default is ${DEFAULT-VALUE}") @Option(names = {"-o", "--output"}, defaultValue = Runner.DEFAULT_OUTPUT_DIRECTORY_NAME, description = "The directory to write the generated code to. Default is ${DEFAULT-VALUE}")
private Path outputDir ; private Path outputDir ;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -55,7 +51,7 @@ public class EndpointsCLI implements Callable<Integer> {
private Boolean verbose = false; private Boolean verbose = false;
@Option(names = {"-p", "--parser"}, description = "Force use of a specific parser instead of determining from filename. Valid values: ${COMPLETION-CANDIDATES}.") @Option(names = {"-p", "--parser"}, description = "Force use of a specific parser instead of determining from filename. Valid values: ${COMPLETION-CANDIDATES}.")
private ParserType parser = null; private Runner.ParserType parser = null;
public static void main(String[] args) { public static void main(String[] args) {
int exitCode = new CommandLine(new EndpointsCLI()).execute(args); int exitCode = new CommandLine(new EndpointsCLI()).execute(args);
@ -65,32 +61,14 @@ public class EndpointsCLI implements Callable<Integer> {
@Override @Override
public Integer call() { public Integer call() {
try { try {
validateTemplateDirectory(); final Logger logger = this.verbose ? new StdoutLogger() : new NullLogger() ;
validateInputFile(); Runner.run(
validateOutputDirectory(); this.file,
verbose("Parsing: " + file.toAbsolutePath()); Optional.of(this.templateDir),
if (parser == null) { Optional.of(this.outputDir),
if (file.getFileName().toString().endsWith(".states")) { parser == null ? Optional.empty() : Optional.of(parser),
parser = ParserType.States; logger
} );
}
final DocumentNode rootNode ;
if (parser == ParserType.States) {
verbose("using state grammar.") ;
rootNode = ParserWrapper.parseStates(file);
} else {
verbose("using endpoints grammar.") ;
rootNode = ParserWrapper.parseEndpoints(file);
}
verbose("AST: " + rootNode);
verbose("Generating from templates in: " + templateDir.toAbsolutePath());
Generator generator = new Generator(templateDir, rootNode, outputDir);
List<Path> generatedPaths = generator.generate();
if (generatedPaths.isEmpty()) {
System.out.println("No generated paths found.");
} else {
generatedPaths.forEach(p -> verbose("Generated: " + p.toAbsolutePath()));
}
return 0; return 0;
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());

View file

@ -18,6 +18,8 @@ import freemarker.template.Template;
import freemarker.template.TemplateException; import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateExceptionHandler;
import nu.zoom.dsl.ast.DocumentNode; import nu.zoom.dsl.ast.DocumentNode;
import nu.zoom.dsl.ast.GeneratorNode;
import nu.zoom.dsl.ast.Meta;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -26,51 +28,72 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class Generator { public class Generator {
private final Path templatesDir; private final Path templatesDir;
private final DocumentNode data; private final DocumentNode documentNode;
private final Path outputDir; private final Path outputDir;
private final Configuration cfg; private final Configuration cfg;
private final String TEMPLATE_EXTENSION = ".ftl"; private final String TEMPLATE_EXTENSION = ".ftl";
private final int TEMPLATE_EXTENSION_LENGTH = TEMPLATE_EXTENSION.length(); private final int TEMPLATE_EXTENSION_LENGTH = TEMPLATE_EXTENSION.length();
public Generator(Path templatesDir, DocumentNode data, Path outputDir) throws IOException { public Generator(Path templatesDir, DocumentNode documentNode, Path outputDir) throws IOException {
this.templatesDir = Objects.requireNonNull(templatesDir); this.templatesDir = Objects.requireNonNull(templatesDir);
this.data = Objects.requireNonNull(data); this.documentNode = Objects.requireNonNull(documentNode);
this.outputDir = Objects.requireNonNull(outputDir); this.outputDir = Objects.requireNonNull(outputDir);
this.cfg = new Configuration(Configuration.VERSION_2_3_34); this.cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setDirectoryForTemplateLoading(templatesDir.toFile()); cfg.setDirectoryForTemplateLoading(templatesDir.toFile());
cfg.setDefaultEncoding("UTF-8"); cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false); cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true); cfg.setWrapUncheckedExceptions(true);
cfg.setFallbackOnNullLoopVariable(false); cfg.setFallbackOnNullLoopVariable(false);
} }
public List<Path> generate() throws IOException, TemplateException { public List<Path> generate() throws IOException, TemplateException {
try (Stream<Path> files = Files.list(templatesDir)) { try (Stream<Path> files = Files.walk(templatesDir)) {
List<String> templates = files List<Path> templates = files
.map(Path::getFileName) .filter(p -> {
.map(Path::toString) var fname = p.getFileName().toString();
.filter(p -> p.length() > TEMPLATE_EXTENSION_LENGTH && p.endsWith(TEMPLATE_EXTENSION) return fname.length() > TEMPLATE_EXTENSION_LENGTH && fname.endsWith(TEMPLATE_EXTENSION);
) }
.toList(); )
ArrayList<Path> out = new ArrayList<>(); .map(p -> templatesDir.relativize(p))
for (String template : templates) { .toList();
Path outpath = outputDir.resolve(outputFilenameFromTemplate(template)); ArrayList<Path> out = new ArrayList<>();
Template ftl = this.cfg.getTemplate(template); for (Path template : templates) {
try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) { Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.toString()));
ftl.process(this.data, outw); Files.createDirectories(outpath.getParent());
out.add(outpath); Path templateSubdirectory = template.getParent();
} ArrayList<String> templateDirectories= new ArrayList<>() ;
} if (templateSubdirectory != null) {
return out; var ti = templateSubdirectory.iterator();
} while (ti.hasNext()) {
} templateDirectories.add(ti.next().toString());
}
}
String templateName = template.getFileName().toString();
Meta meta = new Meta(templateDirectories, templateName);
GeneratorNode generatorNode = new GeneratorNode(
this.documentNode.config(),
this.documentNode.typeDefinitions(),
this.documentNode.endpoints(),
this.documentNode.states(),
meta
);
Template ftl = this.cfg.getTemplate(template.toString());
try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) {
ftl.process(generatorNode, outw);
out.add(outpath);
}
}
return out;
}
}
private String outputFilenameFromTemplate(String template) { private String outputFilenameFromTemplate(String template) {
return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH); return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH);
} }
} }

View file

@ -0,0 +1,15 @@
package nu.zoom.dsl.run;
public abstract class EndgenException extends Exception {
public EndgenException(String message, Throwable cause) {
super(message, cause);
}
public EndgenException(Throwable cause) {
super(cause);
}
public EndgenException(String message) {
super(message);
}
}

View file

@ -0,0 +1,15 @@
package nu.zoom.dsl.run;
public class GeneratorException extends EndgenException {
public GeneratorException(String message) {
super(message);
}
public GeneratorException(String message, Throwable cause) {
super(message, cause);
}
public GeneratorException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,5 @@
package nu.zoom.dsl.run;
public interface Logger {
void println(String message);
}

View file

@ -0,0 +1,8 @@
package nu.zoom.dsl.run;
public class NullLogger implements Logger {
@Override
public void println(String message) {
}
}

View file

@ -0,0 +1,15 @@
package nu.zoom.dsl.run;
public class ParserException extends EndgenException {
public ParserException(String message) {
super(message);
}
public ParserException(String message, Throwable cause) {
super(message, cause);
}
public ParserException(Throwable cause) {
super(cause);
}
}

View file

@ -11,8 +11,11 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package nu.zoom.dsl.ast; package nu.zoom.dsl.run;
import nu.zoom.dsl.ast.DocumentNode;
import nu.zoom.dsl.ast.EndpointsVisitorTransformer;
import nu.zoom.dsl.ast.StatesVisitorTransformer;
import nu.zoom.dsl.parser.*; import nu.zoom.dsl.parser.*;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.CommonTokenStream;

View file

@ -0,0 +1,96 @@
package nu.zoom.dsl.run;
import freemarker.template.TemplateException;
import nu.zoom.dsl.ast.DocumentNode;
import nu.zoom.dsl.freemarker.Generator;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
public final class Runner {
public static final String DEFAULT_TEMPLATE_DIRECTORY_NAME = "endpoints-template";
public static final String DEFAULT_OUTPUT_DIRECTORY_NAME = "endpoints-output";
public static void run(
Path dsl,
Optional<Path> templates,
Optional<Path> output,
Optional<ParserType> maybeParser,
Logger logger
) throws ValidationException, IOException, GeneratorException {
Path templatesDir = templates.orElse(Paths.get(DEFAULT_TEMPLATE_DIRECTORY_NAME));
Path outputDir = output.orElse(Paths.get(DEFAULT_OUTPUT_DIRECTORY_NAME));
validateOutputDirectory(outputDir);
validateTemplateDirectory(templatesDir);
validateInputFile(dsl);
logger.println("Parsing: " + dsl.toAbsolutePath());
final ParserType parser =
maybeParser.orElseGet(
() -> {
if (dsl.getFileName().toString().endsWith(".states")) {
return ParserType.States;
} else {
return ParserType.Endpoints;
}
}
);
final DocumentNode rootNode;
if (parser == ParserType.States) {
logger.println("using state grammar.");
rootNode = ParserWrapper.parseStates(dsl);
} else {
logger.println("using endpoints grammar.");
rootNode = ParserWrapper.parseEndpoints(dsl);
}
logger.println("AST: " + rootNode);
logger.println("Generating from templates in: " + templatesDir.toAbsolutePath());
Generator generator = new Generator(templatesDir, rootNode, outputDir);
List<Path> generatedPaths = null;
try {
generatedPaths = generator.generate();
} catch (TemplateException e) {
throw new GeneratorException(e);
}
if (generatedPaths.isEmpty()) {
System.out.println("No generated paths found.");
} else {
generatedPaths.forEach(p -> logger.println("Generated: " + p.toAbsolutePath()));
}
}
private static void validateOutputDirectory(Path outputDir) throws IOException, ValidationException {
if (Files.notExists(outputDir)) {
Files.createDirectories(outputDir);
}
if (!Files.isDirectory(outputDir)) {
throw new ValidationException("Output directory: '" + outputDir + " 'is not a directory.");
}
}
private static void validateTemplateDirectory(Path templateDir) throws ValidationException {
if (!Files.isDirectory(templateDir)) {
throw new ValidationException("Template directory '" + templateDir + "' is not a directory.");
}
}
private static void validateInputFile(Path file) throws ValidationException {
if (Files.notExists(file)) {
throw new ValidationException("Input file '" + file + "' does not exist.");
}
if (!Files.isReadable(file) || !Files.isRegularFile(file)) {
throw new ValidationException("Input file '" + file + "' is not a readable file.");
}
}
public enum ParserType {
Endpoints,
States
}
}

View file

@ -0,0 +1,9 @@
package nu.zoom.dsl.run;
public class StdoutLogger implements Logger {
@Override
public void println(String message) {
System.out.println(message);
}
}

View file

@ -0,0 +1,15 @@
package nu.zoom.dsl.run;
public class ValidationException extends EndgenException {
public ValidationException(String message) {
super(message);
}
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
public ValidationException(Throwable cause) {
super(cause);
}
}

View file

@ -65,6 +65,7 @@
<modules> <modules>
<module>parser</module> <module>parser</module>
<module>endgen-dist</module> <module>endgen-dist</module>
<module>endgen-maven-plugin</module>
</modules> </modules>
</project> </project>