Merge commit 'refs/pull/2/head' of https://github.com/apache/freemarker-generator
diff --git a/README.md b/README.md
index 1ab5a81..26b8923 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,153 @@
will be callable on other ways (say, from Gradle, or as a standalone command
line tool).
+freemarker-generator-maven-plugin
+---------------------------------
+## Table of contents
+
+- [Background](#background)
+- [Install](#install)
+- [Usage](#usage)
+ - [FreeMarker Template Files](#freemarker-template-files)
+ - [JSON Generator Files](#json-generator-files)
+ - [Using POM Properties During Generation](#using-pom-properties-during-generation)
+ - [FreeMarker Configuration](#freemarker-configuration)
+ - [Incremental Builds](#incremental-builds)
+- [Code Coverage](#code-coverage)
+- [Contributing](#contributing)
+- [License](#license)
+
+## Background
+This plugin generates source files from FreeMarker templates with a flexible process that includes the ability to:
+
+- Generate multiple source files from a single template,
+- Generate source files during multiple steps in the build process such as testing, and
+- Specify distinct locations for the templates and data models for different stages of the build.
+
+## Install
+### pom.xml
+
+Add the following snippet within the `<plugins>` tag of your pom.xml:
+
+```xml
+ <plugin>
+ <groupId>com.oath</groupId>
+ <artifactId>freemarker-maven-plugin</artifactId>
+ <version>${freemarker-maven-plugin.version}</version>
+ <configuration>
+ <!-- Required. Specifies the compatibility version for template processing -->
+ <freeMarkerVersion>2.3.23</freeMarkerVersion>
+ </configuration>
+ <executions>
+ <!-- If you want to generate files during other phases, just add more execution
+ tags and specify appropriate phase, sourceDirectory and outputDirectory values.
+ -->
+ <execution>
+ <id>freemarker</id>
+ <!-- Optional, defaults to generate-sources -->
+ <phase>generate-sources</phase>
+ <goals>
+ <!-- Required, must be generate -->
+ <goal>generate</goal>
+ </goals>
+ <configuration>
+ <!-- Optional, defaults to src/main/freemarker/generator -->
+ <sourceDirectory>src/main/freemarker</templateDirectory>
+ <!-- Optional, defaults to src/main/freemarker/generator/template -->
+ <templateDirectory>src/main/freemarker/template</templateDirectory>
+ <!-- Optional, defaults to src/main/freemarker/generator -->
+ <generatorDirectory>src/main/freemarker/generator/generator</generatorDirectory>
+ <!-- Optional, defaults to target/generated-sources/freemarker -->
+ <outputDirectory>target/generated-sources/freemarker/generator</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+```
+
+## Usage
+
+### FreeMarker Template Files
+FreeMarker template files must reside in the `templateDirectory`. For the default configuration,
+this is: `src/main/freemarker/generator/template`.
+
+By convention, file names for FreeMarker template files use the .ftl extension. For details on the FreeMarker
+template syntax, see: [Getting Started](https://freemarker.apache.org/docs/dgui_quickstart.html) and
+[Template Language Reference](https://freemarker.apache.org/docs/ref.html).
+
+### JSON Generator Files
+The JSON generator files must reside in the `generatorDirectory`. For the default
+configuration, this is: `src/main/freemarker/generator/generator`.
+
+For each JSON generator file, freemarker-maven-plugin will generate a file under the outputDirectory.
+The name of the generated file will be based on the name of the JSON data file. For example,
+the following JSON file:
+```
+ <sourceDirectory>/data/my/package/MyClass.java.json
+```
+will result in the following file being generated:
+```
+ <outputDirectory>/my/package/MyClass.java
+```
+
+This plugin parses the JSON generator file's `dataModel` field into a `Map<String, Object>` instance (hereafter, referred
+to as the data model). If the dataModel field is empty, an empty map will be created.
+
+Here are some additional details you need to know.
+
+ - This plugin *requires* one top-level field in the JSON data file: `templateName`. This field is used to locate the template file under `<sourceDirectory>/template` that is used to generate the file. This plugin provides the data model to FreeMarker as the data model to process the template identified by `templateName`.
+ - The parser allows for comments.
+ - This plugin currently assumes that the JSON data file encoded using UTF-8.
+
+Here is an example JSON data file:
+```json
+{
+ // An end-of-line comment.
+ # Another end-of-line comment
+ "templateName": "my-template.ftl", #Required
+ "dataModel": { #Optional
+ /* A multi-line
+ comment */
+ "myString": "a string",
+ "myNumber": 1,
+ "myListOfStrings": ["s1", "s2"],
+ "myListOfNumbers": [1, 2],
+ "myMap": {
+ "key1": "value1",
+ "key2": 2
+ }
+ }
+}
+```
+
+### Using POM Properties During Generation
+After parsing the JSON file, the plugin will add
+a `pomProperties` entry into the data model, which is a map itself, that contains the properties defined in the pom. Thus, your template can reference the pom property `my_property` using `${pomProperties.my_property}`. If you have a period or dash in the property name, use `${pomProperties["my.property"]}`.
+
+
+
+### FreeMarker Configuration
+
+Typically, users of this plugin do not need to mess with the FreeMarker configuration. This plugin explicitly sets two FreeMarker configurations:
+
+ 1. the default encoding is set to UTF-8
+ 2. the template loader is set to be a FileTemplateLoader that reads from `templateDirectory`.
+
+If you need to override these configs or set your own, you can put them in a
+`<sourceDirectory>/freemarker.properties` file. If that file exists, this plugin will read it into a java Properties instance and pass it to freemarker.core.Configurable.setSettings() to establish the FreeMarker configuration. See this [javadoc](https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-) for configuration details.
+
+
+### Incremental Builds
+This plugin supports incremental builds; it only generates sources if the generator file, template file, or pom file have timestamps newer than any existing output file. To force a rebuild if these conditions are not met (for example, if you pass in a model parameter on the command line), first run `mvn clean`.
+
+## Code Coverage
+
+By default, the code coverage report is not generated. It is generated by screwdriver jobs. You can generate code coverage on your dev machine with the following maven command:
+```bash
+mvn clean initialize -Dclover-phase=initialize
+```
+Bring up the coverage report by pointing your browser to target/site/clover/dashboard.html under the root directory of the local repository.
+
Licensing
---------
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4edc9e9
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,172 @@
+<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>
+
+ <groupId>org.apache.freemarker</groupId>
+ <artifactId>freemarker-generator-maven-plugin</artifactId>
+ <version>1.0.${build}</version>
+ <packaging>maven-plugin</packaging>
+
+ <name>Freemarker Generator Maven Plugin</name>
+ <url>http://freemarker.apache.org/</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <maven-core.version>3.5.2</maven-core.version>
+ <maven-plugin-api.version>3.5.2</maven-plugin-api.version>
+ <maven-plugin-annotations.version>3.5</maven-plugin-annotations.version>
+ <fastutil.version>8.1.0</fastutil.version>
+ <freemarker.version>2.3.23</freemarker.version>
+ <gson.version>2.8.2</gson.version>
+ <jmockit.version>1.32</jmockit.version>
+ <org.testng.version>6.8</org.testng.version>
+ <assertj-core.version>3.8.0</assertj-core.version>
+ <clover-target-percentage>100</clover-target-percentage>
+ <clover-phase>pre-site</clover-phase>
+ <target-jdk-version>1.8</target-jdk-version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>${maven-core.version}</version>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>${maven-plugin-api.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>${maven-plugin-annotations.version}</version>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sisu</groupId>
+ <artifactId>org.eclipse.sisu.plexus</artifactId>
+ <version>0.3.3</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.jmockit</groupId>
+ <artifactId>jmockit</artifactId>
+ <version>${jmockit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>${gson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>${freemarker.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>${org.testng.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj-core.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.7.0</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.0.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugin-plugin</artifactId>
+ <version>3.5</version>
+ <configuration>
+ <goalPrefix>freemarker</goalPrefix>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-descriptor</id>
+ <goals>
+ <goal>descriptor</goal>
+ </goals>
+ <phase>process-classes</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.openclover</groupId>
+ <artifactId>clover-maven-plugin</artifactId>
+ <version>4.2.0</version>
+ <executions>
+ <execution>
+ <id>clover</id>
+ <phase>${clover-phase}</phase>
+ <goals>
+ <goal>instrument-test</goal>
+ <goal>clover</goal>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <targetPercentage>${clover-target-percentage}</targetPercentage>
+ <generateHtml>true</generateHtml>
+ <generateXml>true</generateXml>
+ <jdk>${target-jdk-version}</jdk>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/main/java/org/apache/freemarker/generator/FactoryUtil.java b/src/main/java/org/apache/freemarker/generator/FactoryUtil.java
new file mode 100644
index 0000000..359dd3f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/generator/FactoryUtil.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+import freemarker.template.Configuration;
+import freemarker.template.Version;
+
+/**
+ * Simple utility class to call various constructors.
+ * Needed because some jmockit features don't work well with constructors.
+ */
+public class FactoryUtil {
+
+ public static Configuration createConfiguration(String freeMarkerVersion) {
+ return new Configuration(new Version(freeMarkerVersion));
+ }
+
+ public static File createFile(File parent, String child) {
+ return new File(parent, child);
+ }
+
+ public static FileInputStream createFileInputStream(File file) throws FileNotFoundException {
+ return new FileInputStream(file);
+ }
+
+ public static File createFile(String name) {
+ return new File(name);
+ }
+}
diff --git a/src/main/java/org/apache/freemarker/generator/FreeMarkerMojo.java b/src/main/java/org/apache/freemarker/generator/FreeMarkerMojo.java
new file mode 100644
index 0000000..5b0454a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/generator/FreeMarkerMojo.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecution;
+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 freemarker.cache.FileTemplateLoader;
+import freemarker.template.Configuration;
+
+@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
+public class FreeMarkerMojo extends AbstractMojo {
+
+ /** FreeMarker version string used to build FreeMarker Configuration instance. */
+ @Parameter
+ private String freeMarkerVersion;
+
+ @Parameter(defaultValue = "src/main/freemarker/generator")
+ private File sourceDirectory;
+
+ @Parameter(defaultValue = "src/main/freemarker/generator/template")
+ private File templateDirectory;
+
+ @Parameter(defaultValue = "src/main/freemarker/generator/generator")
+ private File generatorDirectory;
+
+ @Parameter(defaultValue = "target/generated-sources/freemarker/generator")
+ private File outputDirectory;
+
+ @Parameter(defaultValue = "${session}", readonly = true)
+ private MavenSession session;
+
+ @Parameter(defaultValue = "${mojoExecution}", readonly = true)
+ private MojoExecution mojo;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+
+ if (freeMarkerVersion == null || freeMarkerVersion.length() == 0) {
+ throw new MojoExecutionException("freeMarkerVersion is required");
+ }
+
+ if (!generatorDirectory.isDirectory()) {
+ throw new MojoExecutionException("Required directory does not exist: " + generatorDirectory);
+ }
+
+ Configuration config = FactoryUtil.createConfiguration(freeMarkerVersion);
+
+ config.setDefaultEncoding("UTF-8");
+
+ if (!templateDirectory.isDirectory()) {
+ throw new MojoExecutionException("Required directory does not exist: " + templateDirectory);
+ }
+ try {
+ config.setTemplateLoader(new FileTemplateLoader(templateDirectory));
+ } catch (Throwable t) {
+ getLog().error("Could not establish file template loader for directory: " + templateDirectory, t);
+ throw new MojoExecutionException("Could not establish file template loader for directory: " + templateDirectory);
+ }
+
+ File freeMarkerProps = FactoryUtil.createFile(sourceDirectory, "freemarker.properties");
+ if (freeMarkerProps.isFile()) {
+ Properties configProperties = new Properties();
+ try (InputStream is = FactoryUtil.createFileInputStream(freeMarkerProps)) {
+ configProperties.load(is);
+ } catch (Throwable t) {
+ getLog().error("Failed to load " + freeMarkerProps, t);
+ throw new MojoExecutionException("Failed to load " + freeMarkerProps);
+ }
+ try {
+ config.setSettings(configProperties);
+ } catch (Throwable t) {
+ getLog().error("Invalid setting(s) in " + freeMarkerProps, t);
+ throw new MojoExecutionException("Invalid setting(s) in " + freeMarkerProps);
+ }
+ }
+
+ if ("generate-sources".equals(mojo.getLifecyclePhase())) {
+ session.getCurrentProject().addCompileSourceRoot(outputDirectory.toString());
+ } else if ("generate-test-sources".equals(mojo.getLifecyclePhase())) {
+ session.getCurrentProject().addTestCompileSourceRoot(outputDirectory.toString());
+ }
+
+ Map<String, OutputGeneratorPropertiesProvider> extensionToBuilders = new HashMap<>(1);
+ extensionToBuilders.put(".json", JsonPropertiesProvider.create(generatorDirectory,templateDirectory,outputDirectory));
+
+ GeneratingFileVisitor fileVisitor = GeneratingFileVisitor.create(config, session, extensionToBuilders);
+ try {
+ Files.walkFileTree(generatorDirectory.toPath(), fileVisitor);
+ } catch (Throwable t) {
+ getLog().error("Failed to process files in generator dir: " + generatorDirectory, t);
+ throw new MojoExecutionException("Failed to process files in generator dir: " + generatorDirectory);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/freemarker/generator/GeneratingFileVisitor.java b/src/main/java/org/apache/freemarker/generator/GeneratingFileVisitor.java
new file mode 100644
index 0000000..a130257
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/generator/GeneratingFileVisitor.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Map;
+
+import org.apache.maven.execution.MavenSession;
+
+import freemarker.template.Configuration;
+
+/**
+ * FileVisitor designed to process json data files. The json file parsed into
+ * a map and given to FreeMarker to
+ */
+public class GeneratingFileVisitor extends SimpleFileVisitor<Path> {
+
+ private final Configuration config;
+ private final MavenSession session;
+ private final long pomLastModifiedTimestamp;
+ private final Map<String, OutputGeneratorPropertiesProvider > extensionToBuilder;
+
+ private GeneratingFileVisitor(Configuration config, MavenSession session, Map<String, OutputGeneratorPropertiesProvider> extensionToBuilder) {
+ this.config = config;
+ this.session = session;
+ this.extensionToBuilder = extensionToBuilder;
+ this.pomLastModifiedTimestamp = session.getAllProjects().stream()
+ .map(project->project.getFile().lastModified())
+ .reduce(Long::max)
+ .orElse(0L);
+ }
+
+ /**
+ * Factory method that calls constructor, added to facilitate testing with jmockit.
+ */
+ public static GeneratingFileVisitor create(Configuration config, MavenSession session, Map<String, OutputGeneratorPropertiesProvider> extensionToBuilder) {
+ return new GeneratingFileVisitor(config, session, extensionToBuilder);
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
+ if (attrs.isRegularFile()) {
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder()
+ .addGeneratorLocation(path)
+ .addPomLastModifiedTimestamp(pomLastModifiedTimestamp);
+ String fileName = path.getFileName().toString();
+ String extenstion = fileName.substring(fileName.lastIndexOf('.'));
+ OutputGeneratorPropertiesProvider pathProcessor = extensionToBuilder.get(extenstion);
+ if (pathProcessor == null) {
+ throw new RuntimeException("Unknown file extension: " + path);
+ }
+ pathProcessor.providePropertiesFromFile(path, builder);
+ builder.addToDataModel("pomProperties", session.getCurrentProject().getProperties());
+ builder.create().generate(config);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+}
diff --git a/src/main/java/org/apache/freemarker/generator/JsonPropertiesProvider.java b/src/main/java/org/apache/freemarker/generator/JsonPropertiesProvider.java
new file mode 100644
index 0000000..ea9e678
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/generator/JsonPropertiesProvider.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public class JsonPropertiesProvider implements OutputGeneratorPropertiesProvider {
+ private final Gson gson;
+ private final Type stringObjectMap;
+ private final File dataDir;
+ private final File templateDir;
+ private final File outputDir;
+
+ private JsonPropertiesProvider(File dataDir, File templateDir, File outputDir) {
+ this.dataDir = dataDir;
+ this.templateDir = templateDir;
+ this.outputDir = outputDir;
+ gson = new GsonBuilder().setLenient().create();
+ stringObjectMap = new TypeToken<Map<String, Object>>() { } .getType();
+ }
+
+ public static JsonPropertiesProvider create(File dataDir, File templateDir, File outputDir) {
+ return new JsonPropertiesProvider(dataDir, templateDir, outputDir);
+ }
+
+ @Override
+ public void providePropertiesFromFile(Path path, OutputGenerator.OutputGeneratorBuilder builder) {
+ File jsonDataFile = path.toFile();
+ Map<String,Object> data = parseJson(jsonDataFile);
+
+ Object obj = data.get("dataModel");
+ if (obj != null) {
+ builder.addDataModel((Map<String, Object>) obj);
+ } else {
+ builder.addDataModel(new HashMap<String,Object>());
+ }
+
+ obj = data.get("templateName");
+ if (obj == null) {
+ throw new RuntimeException("Require json data property not found: templateName");
+ }
+ builder.addTemplateLocation(templateDir.toPath().resolve(obj.toString()));
+
+ String dataDirName = dataDir.getAbsolutePath();
+ String jsonFileName = jsonDataFile.getAbsolutePath();
+ if (!jsonFileName.startsWith(dataDirName)) {
+ throw new IllegalStateException("visitFile() given file not in sourceDirectory: " + jsonDataFile);
+ }
+
+ String outputFileName = jsonFileName.substring(dataDirName.length()+1);
+ outputFileName = outputFileName.substring(0, outputFileName.length() - 5);
+ Path outputPath = outputDir.toPath();
+ Path resolved = outputPath.resolve(outputFileName);
+ builder.addOutputLocation(resolved);
+ }
+
+ private Map<String, Object> parseJson(File jsonDataFile) {
+ try (JsonReader reader = new JsonReader(new InputStreamReader(new FileInputStream(jsonDataFile), "UTF-8"))) {
+ return gson.fromJson(reader, stringObjectMap);
+ } catch (Throwable t) {
+ throw new RuntimeException("Could not parse json data file: " + jsonDataFile, t);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/freemarker/generator/OutputGenerator.java b/src/main/java/org/apache/freemarker/generator/OutputGenerator.java
new file mode 100644
index 0000000..e639392
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/generator/OutputGenerator.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Knows how to generate an output file given five things:
+ * <ul>
+ * <li>The latest update time of the <b>pom file(s)</b> for the project</li>
+ * <li>The location of the <b>generator file</b></li>
+ * <li>The location of the <b>template file</b></li>
+ * <li>The location of the <b>output file</b></li>
+ * <li>A <b>data model<b> used to fill out the template.</li>
+ * </ul>
+ *<p>Given these five pieces of information, the generator will generate a new output file, but only if any existing
+ * generated file is not newer than the inputs (pom, generator, and template).</p>
+ */
+class OutputGenerator {
+ public final long pomModifiedTimestamp;
+ public final Path generatorLocation;
+ public final Path templateLocation;
+ public final Path outputLocation;
+ public final Map<String,Object> dataModel;
+ private OutputGenerator(
+ long pomModifiedTimestamp,
+ Path generatorLocation,
+ Path templateLocation,
+ Path outputLocation,
+ Map<String, Object> dataModel) {
+ this.pomModifiedTimestamp = pomModifiedTimestamp;
+ this.generatorLocation = generatorLocation;
+ this.templateLocation = templateLocation;
+ this.outputLocation = outputLocation;
+ this.dataModel = dataModel;
+ }
+
+ /**
+ * Uses a fluent builder to make the code more legible in place.
+ * Also allows the output generator to be built from multiple locations in source by passing the builder.
+ * @return A new fluent builder for the OutputGenerator class.
+ */
+ public static OutputGeneratorBuilder builder() {
+ return new OutputGeneratorBuilder();
+ }
+
+ public static class OutputGeneratorBuilder {
+ private long pomModifiedTimestamp = Long.MAX_VALUE;
+ private Path generatorLocation = null;
+ private Path templateLocation = null;
+ private Path outputLocation = null;
+ private Map<String,Object> dataModel = null;
+
+ public OutputGeneratorBuilder addPomLastModifiedTimestamp(long pomModifiedTimestamp) {
+ this.pomModifiedTimestamp = pomModifiedTimestamp;
+ return this;
+ }
+
+ public OutputGeneratorBuilder addGeneratorLocation(Path generatorLocation) {
+ this.generatorLocation = generatorLocation;
+ return this;
+ }
+
+ public OutputGeneratorBuilder addTemplateLocation(Path templateLocation) {
+ this.templateLocation = templateLocation;
+ return this;
+ }
+
+ public OutputGeneratorBuilder addOutputLocation(Path outputLocation) {
+ this.outputLocation = outputLocation;
+ return this;
+ }
+
+ public OutputGeneratorBuilder addDataModel(Map<String,Object> dataModel) {
+ this.dataModel = dataModel;
+ return this;
+ }
+
+ public OutputGeneratorBuilder addToDataModel(String key, Object val) {
+ if (this.dataModel == null) {
+ this.dataModel = new HashMap<>(4);
+ }
+ this.dataModel.put(key,val);
+ return this;
+ }
+
+ /**
+ * @throws IllegalStateException if any of the parts of the OutputGenerator were not set.
+ * @return A new output generator (which is immutable).
+ */
+ public OutputGenerator create() {
+ if (pomModifiedTimestamp == Long.MAX_VALUE) throw new IllegalStateException("Must set the pomModifiedTimestamp");
+ if (generatorLocation == null) throw new IllegalStateException("Must set a non-null generatorLocation");
+ if (templateLocation == null) throw new IllegalStateException("Must set a non-null templateLocation");
+ if (outputLocation == null) throw new IllegalStateException("Must set a non-null outputLocation");
+ if (dataModel == null) throw new IllegalStateException("Must set a non-null dataModel");
+ return new OutputGenerator(pomModifiedTimestamp, generatorLocation, templateLocation, outputLocation, dataModel);
+ }
+ }
+
+ /**
+ * <p>Generates an output by applying the model to the template.</p>
+ * <p>Checks the ages of the inputs against an existing output file to early exit if there is no update.</p>
+ * @param config Used to load the template from the template name.
+ */
+ public void generate(Configuration config) {
+ //Use "createFile" for testing purposes only
+ File outputFile = FactoryUtil.createFile(outputLocation.toFile().toString());
+ File templateFile = templateLocation.toFile();
+ File generatorFile = generatorLocation.toFile();
+ if (outputFile.exists()) {
+ //early exit only if the output file is newer than all files that contribute to its generation
+ if (outputFile.lastModified() > generatorFile.lastModified()
+ && outputFile.lastModified() > templateFile.lastModified()
+ && outputFile.lastModified() > pomModifiedTimestamp) {
+ return;
+ }
+ } else {
+ File parentDir = outputFile.getParentFile();
+ if (parentDir.isFile()) {
+ throw new RuntimeException("Parent directory of output file is a file: " + parentDir.getAbsoluteFile());
+ }
+ parentDir.mkdirs();
+ if (!parentDir.isDirectory()) {
+ throw new RuntimeException("Could not create directory: " + parentDir.getAbsoluteFile());
+ }
+ }
+
+ Template template;
+ try {
+ template = config.getTemplate(templateFile.getName());
+ } catch (Throwable t) {
+ throw new RuntimeException("Could not read template: " + templateFile.getName(), t);
+ }
+
+ try (FileWriter writer = new FileWriter(outputFile)) {
+ template.process(dataModel, writer);
+ } catch (Throwable t) {
+ throw new RuntimeException("Could not process template associated with data file: " + generatorLocation, t);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/freemarker/generator/OutputGeneratorPropertiesProvider.java b/src/main/java/org/apache/freemarker/generator/OutputGeneratorPropertiesProvider.java
new file mode 100644
index 0000000..019373b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/generator/OutputGeneratorPropertiesProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import java.nio.file.Path;
+
+public interface OutputGeneratorPropertiesProvider {
+ /**
+ * Must add three properties to the builder: the <b>templateLocation</b>, <b>outputLocation</b>, and <b>dataModel</b>
+ * The <b>pom updated timestamp</b> and <b>generatorLocation</b> are added elsewhere.
+ * @param path The path to the generator file, to be used to decide on the three properties above.
+ * @param builder The builder to which to add the properties.
+ */
+ public void providePropertiesFromFile(Path path, OutputGenerator.OutputGeneratorBuilder builder);
+}
diff --git a/src/test/data/freemarker-mojo/data/test.txt.json b/src/test/data/freemarker-mojo/data/test.txt.json
new file mode 100644
index 0000000..0702dca
--- /dev/null
+++ b/src/test/data/freemarker-mojo/data/test.txt.json
@@ -0,0 +1,3 @@
+{
+ "templateName": "test.ftl"
+}
\ No newline at end of file
diff --git a/src/test/data/freemarker-mojo/freemarker.properties b/src/test/data/freemarker-mojo/freemarker.properties
new file mode 100644
index 0000000..17cefa1
--- /dev/null
+++ b/src/test/data/freemarker-mojo/freemarker.properties
@@ -0,0 +1,3 @@
+# Test properties file used by FreeMarkerMojoTest to verify FreeMarkerMojo
+# passes these settings to the FreeMarker configuration.
+boolean_format: T,F
\ No newline at end of file
diff --git a/src/test/data/freemarker-mojo/template/test.ftl b/src/test/data/freemarker-mojo/template/test.ftl
new file mode 100644
index 0000000..6986c80
--- /dev/null
+++ b/src/test/data/freemarker-mojo/template/test.ftl
@@ -0,0 +1 @@
+This is a dummy test file. It is only here to make sure the directory exists.
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/badPath/success-test.txt.json b/src/test/data/generating-file-visitor/badPath/success-test.txt.json
new file mode 100644
index 0000000..89b6d13
--- /dev/null
+++ b/src/test/data/generating-file-visitor/badPath/success-test.txt.json
@@ -0,0 +1,4 @@
+{
+ "templateName": "test.ftl",
+ "testVar": "test value"
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/data/badParent/bad-parent-test.txt.json b/src/test/data/generating-file-visitor/data/badParent/bad-parent-test.txt.json
new file mode 100644
index 0000000..ad68303
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/badParent/bad-parent-test.txt.json
@@ -0,0 +1,6 @@
+{
+ "templateName": "test.ftl",
+ "dataModel": {
+ "testVar": "test value"
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/data/mydir/bad-extension-test.txt b/src/test/data/generating-file-visitor/data/mydir/bad-extension-test.txt
new file mode 100644
index 0000000..e4747b5
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/mydir/bad-extension-test.txt
@@ -0,0 +1,6 @@
+{
+ "templateName": "test.ftl",
+ "dataModel": {
+ "testVar": "test 2 value"
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/data/mydir/bad-template-name.txt.json b/src/test/data/generating-file-visitor/data/mydir/bad-template-name.txt.json
new file mode 100644
index 0000000..5f01033
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/mydir/bad-template-name.txt.json
@@ -0,0 +1,6 @@
+{
+ "templateName": "missing.ftl",
+ "dataModel": {
+ "testVar": "test value"
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/data/mydir/missing-template-name.txt.json b/src/test/data/generating-file-visitor/data/mydir/missing-template-name.txt.json
new file mode 100644
index 0000000..f80ce71
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/mydir/missing-template-name.txt.json
@@ -0,0 +1,6 @@
+{
+ //not ok to be missing the templateName
+ "dataModel": {
+ "testVar": "test value"
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/data/mydir/missing-var-test.txt.json b/src/test/data/generating-file-visitor/data/mydir/missing-var-test.txt.json
new file mode 100644
index 0000000..b97ea16
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/mydir/missing-var-test.txt.json
@@ -0,0 +1,4 @@
+{
+ "templateName": "test.ftl"
+ //missing dataModel is not ok, since we are missing a variable needed to fill out the template
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/data/mydir/success-test-2.txt.json b/src/test/data/generating-file-visitor/data/mydir/success-test-2.txt.json
new file mode 100644
index 0000000..5dc549c
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/mydir/success-test-2.txt.json
@@ -0,0 +1,4 @@
+{
+ "templateName": "test-pom-only.ftl"
+ //missing dataModel, is OK, assuming all required properies are found in pom
+}
diff --git a/src/test/data/generating-file-visitor/data/mydir/success-test.txt.json b/src/test/data/generating-file-visitor/data/mydir/success-test.txt.json
new file mode 100644
index 0000000..ad68303
--- /dev/null
+++ b/src/test/data/generating-file-visitor/data/mydir/success-test.txt.json
@@ -0,0 +1,6 @@
+{
+ "templateName": "test.ftl",
+ "dataModel": {
+ "testVar": "test value"
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/generating-file-visitor/template/test-pom-only.ftl b/src/test/data/generating-file-visitor/template/test-pom-only.ftl
new file mode 100644
index 0000000..7168154
--- /dev/null
+++ b/src/test/data/generating-file-visitor/template/test-pom-only.ftl
@@ -0,0 +1 @@
+This is a test freemarker template. Test pom data: '${pomProperties.pomVar}'.
diff --git a/src/test/data/generating-file-visitor/template/test.ftl b/src/test/data/generating-file-visitor/template/test.ftl
new file mode 100644
index 0000000..1edee95
--- /dev/null
+++ b/src/test/data/generating-file-visitor/template/test.ftl
@@ -0,0 +1 @@
+This is a test freemarker template. Test json data: '${testVar}'. Test pom data: '${pomProperties.pomVar}'.
\ No newline at end of file
diff --git a/src/test/java/org/apache/freemarker/generator/FreeMarkerMojoTest.java b/src/test/java/org/apache/freemarker/generator/FreeMarkerMojoTest.java
new file mode 100644
index 0000000..f5b846b
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/generator/FreeMarkerMojoTest.java
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.junit.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import freemarker.cache.FileTemplateLoader;
+import freemarker.cache.TemplateLoader;
+import freemarker.template.Configuration;
+import mockit.Deencapsulation;
+import mockit.Expectations;
+import mockit.Mocked;
+import mockit.Verifications;
+
+public class FreeMarkerMojoTest extends Assert {
+
+ public static final File testOutputDir = new File("target/test-output/freemarker-mojo");
+
+ @BeforeClass
+ public static void beforeClass() throws IOException {
+ // Clean output dir before each run.
+ if (testOutputDir.exists()) {
+ // Recursively delete output from previous run.
+ Files.walk(testOutputDir.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+
+ @Test
+ public void executeTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked GeneratingFileVisitor generatingFileVisitor,
+ @Mocked Files files
+ ) throws MojoExecutionException, MojoFailureException, IOException {
+
+ new Expectations(mojoExecution, generatingFileVisitor) {{
+ mojoExecution.getLifecyclePhase(); result = "generate-sources";
+ session.getCurrentProject(); result = project;
+ }};
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ // Validate freeMarkerVersion is required.
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("freeMarkerVersion is required");
+
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "");
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("freeMarkerVersion is required");
+
+ File testCaseOutputDir = new File(testOutputDir, "executeTest");
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", testCaseOutputDir);
+ Deencapsulation.setField(mojo, "templateDirectory", new File(testCaseOutputDir, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File(testCaseOutputDir, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ // Validate source directory.
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("Required directory does not exist: target/test-output/freemarker-mojo/executeTest/data");
+
+ new File(testCaseOutputDir, "data").mkdirs();
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("Required directory does not exist: target/test-output/freemarker-mojo/executeTest/template");
+ new File(testCaseOutputDir, "template").mkdirs();
+
+ // Validate minimum configuration.
+ mojo.execute();
+
+ new Verifications() {{
+ project.addCompileSourceRoot("target/test-output/freemarker-mojo/executeTest/generated-files"); times = 1;
+
+ Configuration config;
+ MavenSession capturedSession;
+ Map<String, OutputGeneratorPropertiesProvider> builders;
+
+ GeneratingFileVisitor.create(
+ config = withCapture(),
+ capturedSession = withCapture(),
+ builders = withCapture()); times = 1;
+
+ assertEquals("UTF-8", config.getDefaultEncoding());
+ assertEquals(session, capturedSession);
+ TemplateLoader loader = config.getTemplateLoader();
+ assertTrue(loader instanceof FileTemplateLoader);
+
+ Path path;
+ FileVisitor<Path> fileVisitor;
+
+ Files.walkFileTree(path = withCapture(), fileVisitor = withCapture()); times = 1;
+
+ assertEquals(new File(testCaseOutputDir, "data").toPath(), path);
+ assertTrue(fileVisitor instanceof GeneratingFileVisitor);
+ }};
+ }
+
+ @Test
+ public void execute_generateTestSourceTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked GeneratingFileVisitor generatingFileVisitor,
+ @Mocked Files files
+ ) throws MojoExecutionException, MojoFailureException, IOException {
+
+ new Expectations(mojoExecution, generatingFileVisitor) {{
+ mojoExecution.getLifecyclePhase(); result = "generate-test-sources";
+ session.getCurrentProject(); result = project;
+ }};
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ File testCaseOutputDir = new File(testOutputDir, "generateTestSourceTest");
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", testCaseOutputDir);
+ Deencapsulation.setField(mojo, "templateDirectory", new File(testCaseOutputDir, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File(testCaseOutputDir, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ new File(testCaseOutputDir, "data").mkdirs();
+ new File(testCaseOutputDir, "template").mkdirs();
+
+ mojo.execute();
+
+ new Verifications() {{
+ project.addTestCompileSourceRoot("target/test-output/freemarker-mojo/generateTestSourceTest/generated-files"); times = 1;
+ }};
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Test
+ public void execute_walkFileTreeExceptionTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked GeneratingFileVisitor generatingFileVisitor,
+ @Mocked Files files
+ ) throws MojoExecutionException, MojoFailureException, IOException {
+
+ new Expectations(mojoExecution, generatingFileVisitor) {{
+ mojoExecution.getLifecyclePhase(); result = "generate-test-sources";
+ session.getCurrentProject(); result = project;
+ Files.walkFileTree((Path) any,(FileVisitor) any); result = new RuntimeException("test exception");
+ }};
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ File testCaseOutputDir = new File(testOutputDir, "generateTestSourceTest");
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", testCaseOutputDir);
+ Deencapsulation.setField(mojo, "templateDirectory", new File(testCaseOutputDir, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File(testCaseOutputDir, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ new File(testCaseOutputDir, "data").mkdirs();
+ new File(testCaseOutputDir, "template").mkdirs();
+
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("Failed to process files in generator dir: target/test-output/freemarker-mojo/generateTestSourceTest/data");
+ }
+
+ @Test
+ public void execute_setTemplateLoaderExceptionTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked FactoryUtil factoryUtil,
+ @Mocked Configuration config) {
+
+ new Expectations(config, FactoryUtil.class) {{
+ FactoryUtil.createConfiguration("2.3.23"); result = config;
+ config.setTemplateLoader((TemplateLoader) any); result = new RuntimeException("test exception");
+ }};
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ File testCaseOutputDir = new File(testOutputDir, "setTemplateLoaderException");
+
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", testCaseOutputDir);
+ Deencapsulation.setField(mojo, "templateDirectory", new File(testCaseOutputDir, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File(testCaseOutputDir, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ new File(testCaseOutputDir, "data").mkdirs();
+ new File(testCaseOutputDir, "template").mkdirs();
+
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("Could not establish file template loader for directory: target/test-output/freemarker-mojo/setTemplateLoaderException/template");
+ }
+
+ @Test
+ public void execute_loadFreemarkerPropertiesTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked Configuration config) throws Exception {
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ File sourceDirectory = new File("src/test/data/freemarker-mojo");
+ File testCaseOutputDir = new File(testOutputDir, "loadFreemarkerProperties");
+
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", sourceDirectory);
+ Deencapsulation.setField(mojo, "templateDirectory", new File( sourceDirectory, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File( sourceDirectory, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ mojo.execute();
+
+ new Verifications() {{
+ Properties properties;
+
+ config.setSettings(properties = withCapture()); times = 1;
+
+ assertEquals("T,F", properties.getProperty("boolean_format"));
+ }};
+ }
+
+ @Test
+ public void execute_loadFreemarkerPropertiesExceptionTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked FactoryUtil factoryUtil,
+ @Mocked Configuration config) throws Exception {
+
+ new Expectations(FactoryUtil.class) {{
+ FactoryUtil.createFileInputStream((File) any); result = new RuntimeException("test exception");
+ }};
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ File sourceDirectory = new File("src/test/data/freemarker-mojo");
+ File testCaseOutputDir = new File(testOutputDir, "loadFreemarkerPropertiesExceptionTest");
+
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", sourceDirectory);
+ Deencapsulation.setField(mojo, "templateDirectory", new File( sourceDirectory, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File( sourceDirectory, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ System.out.println("==== before mojo execute");
+ try {
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("Failed to load src/test/data/freemarker-mojo/freemarker.properties");
+ } catch ( Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ @Test
+ public void execute_setSettingsExceptionTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked MojoExecution mojoExecution,
+ @Mocked Configuration config) throws Exception {
+
+ new Expectations() {{
+ config.setSettings((Properties) any); result = new RuntimeException("test exception");
+ }};
+
+ FreeMarkerMojo mojo = new FreeMarkerMojo();
+
+ File sourceDirectory = new File("src/test/data/freemarker-mojo");
+ File testCaseOutputDir = new File(testOutputDir, "loadFreemarkerProperties");
+
+ Deencapsulation.setField(mojo, "freeMarkerVersion", "2.3.23");
+ Deencapsulation.setField(mojo, "sourceDirectory", sourceDirectory);
+ Deencapsulation.setField(mojo, "templateDirectory", new File( sourceDirectory, "template"));
+ Deencapsulation.setField(mojo, "generatorDirectory", new File( sourceDirectory, "data"));
+ Deencapsulation.setField(mojo, "outputDirectory", new File(testCaseOutputDir, "generated-files"));
+ Deencapsulation.setField(mojo, "mojo", mojoExecution);
+ Deencapsulation.setField(mojo, "session", session);
+
+ assertThatExceptionOfType(MojoExecutionException.class).isThrownBy(() -> {
+ mojo.execute();
+ }).withMessage("Invalid setting(s) in src/test/data/freemarker-mojo/freemarker.properties");
+ }
+
+}
diff --git a/src/test/java/org/apache/freemarker/generator/GeneratingFileVisitorTest.java b/src/test/java/org/apache/freemarker/generator/GeneratingFileVisitorTest.java
new file mode 100644
index 0000000..8770a27
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/generator/GeneratingFileVisitorTest.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.*;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.junit.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import freemarker.cache.FileTemplateLoader;
+import freemarker.template.Configuration;
+import mockit.Expectations;
+import mockit.Mocked;
+
+public class GeneratingFileVisitorTest extends Assert {
+
+ private static File testDir = new File("src/test/data/generating-file-visitor");
+ private static File dataDir = new File(testDir, "data");
+ private static File templateDir = new File(testDir, "template");
+ private static File outputDir = new File("target/test-output/generating-file-visitor");
+ private static Map<String, OutputGeneratorPropertiesProvider> builders = new HashMap<>();
+ private Configuration config;
+ private Properties pomProperties = new Properties();
+
+ @BeforeClass
+ public static void beforeClass() throws IOException {
+ builders.put(".json", JsonPropertiesProvider.create(dataDir,templateDir,outputDir));
+ // Clean output dir before each run.
+ File outputDir = new File("target/test-output/generating-file-visitor");
+ if (outputDir.exists()) {
+ // Recursively delete output from previous run.
+ Files.walk(outputDir.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+
+ @BeforeMethod
+ public void before() throws IOException {
+ if (!testDir.isDirectory()) {
+ throw new RuntimeException("Can't find required test data directory. "
+ + "If running test outside of maven, make sure working directory is the project directory. "
+ + "Looking for: " + testDir);
+ }
+
+ config = new Configuration(Configuration.VERSION_2_3_23);
+ config.setDefaultEncoding("UTF-8");
+ config.setTemplateLoader(new FileTemplateLoader(templateDir));
+ pomProperties.put("pomVar", "pom value");
+ }
+
+ @Test
+ public void functionalHappyPathTestNoDataModel(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked File mockFile,
+ @Mocked BasicFileAttributes attrs) throws IOException {
+ List<MavenProject> projects = new ArrayList<>();
+ projects.add(project);
+ new Expectations(session, project, mockFile) {{
+ session.getCurrentProject(); result = project;
+ session.getAllProjects(); result = projects;
+ project.getProperties(); result = pomProperties;
+ attrs.isRegularFile(); result = true;
+ project.getFile(); result = mockFile;
+ mockFile.lastModified(); result = 10;
+ }};
+
+ File file = new File(dataDir, "mydir/success-test-2.txt.json");
+ GeneratingFileVisitor gfv = GeneratingFileVisitor.create(config, session, builders);
+ assertEquals(FileVisitResult.CONTINUE, gfv.visitFile(file.toPath(), attrs));
+
+ File outputFile = new File(outputDir, "mydir/success-test-2.txt");
+ assertTrue(outputFile.isFile());
+ List<String> lines = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
+ assertEquals(1, lines.size());
+ assertEquals("This is a test freemarker template. Test pom data: 'pom value'.", lines.get(0));
+ }
+
+ @Test
+ public void functionalHappyPathTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked File mockFile,
+ @Mocked BasicFileAttributes attrs) throws IOException {
+ List<MavenProject> projects = new ArrayList<>();
+ projects.add(project);
+ new Expectations(session, project, mockFile) {{
+ session.getCurrentProject(); result = project;
+ session.getAllProjects(); result = projects;
+ project.getProperties(); result = pomProperties;
+ attrs.isRegularFile(); result = true;
+ project.getFile(); result = mockFile;
+ mockFile.lastModified(); result = 10;
+ }};
+
+ File file = new File(dataDir, "mydir/success-test.txt.json");
+ GeneratingFileVisitor gfv = GeneratingFileVisitor.create(config, session, builders);
+ assertEquals(FileVisitResult.CONTINUE, gfv.visitFile(file.toPath(), attrs));
+
+ File outputFile = new File(outputDir, "mydir/success-test.txt");
+ assertTrue(outputFile.isFile());
+ List<String> lines = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
+ assertEquals(1, lines.size());
+ assertEquals("This is a test freemarker template. Test json data: 'test value'. Test pom data: 'pom value'.", lines.get(0));
+ }
+
+ @Test
+ public void visitFile_badExtensionTest(
+ @Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked File mockFile,
+ @Mocked BasicFileAttributes attrs) throws IOException {
+ List<MavenProject> projects = new ArrayList<>();
+ projects.add(project);
+ new Expectations(session, project, mockFile) {{
+ attrs.isRegularFile(); result = true;
+ session.getAllProjects(); result = projects;
+ project.getFile(); result = mockFile;
+ mockFile.lastModified(); result = 10;
+ }};
+ // Test file without .json suffix.
+ File file = new File(dataDir, "mydir/bad-extension-test.txt");
+ GeneratingFileVisitor gfv = GeneratingFileVisitor.create(config, session, builders);
+ assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ gfv.visitFile(file.toPath(), attrs);
+ }).withMessage("Unknown file extension: " + file.toPath());
+ }
+
+ @Test
+ public void visitFile_notRegularFileTest(@Mocked MavenSession session,
+ @Mocked MavenProject project,
+ @Mocked BasicFileAttributes attrs,
+ @Mocked File mockFile
+ ) {
+ List<MavenProject> projects = new ArrayList<>();
+ projects.add(project);
+ new Expectations(session, project, mockFile) {{
+ attrs.isRegularFile(); result = false;
+ session.getAllProjects(); result = projects;
+ project.getFile(); result = mockFile;
+ mockFile.lastModified(); result = 10;
+ }};
+ // FYI: if you change above result to true, test will fail trying to read the 'mydir' directory
+ // as a json file.
+ File dir = new File(dataDir, "mydir");
+ GeneratingFileVisitor gfv = GeneratingFileVisitor.create(config, session, builders);
+ assertEquals(FileVisitResult.CONTINUE, gfv.visitFile(dir.toPath(), attrs));
+ }
+}
diff --git a/src/test/java/org/apache/freemarker/generator/JsonPropertiesProviderTest.java b/src/test/java/org/apache/freemarker/generator/JsonPropertiesProviderTest.java
new file mode 100644
index 0000000..3f5b2ff
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/generator/JsonPropertiesProviderTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonReader;
+import mockit.Expectations;
+import mockit.Mocked;
+import mockit.Verifications;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.lang.reflect.Type;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
+
+public class JsonPropertiesProviderTest {
+ private File testDir = new File("src/test/data/generating-file-visitor");
+ private File dataDir = new File(testDir, "data");
+ private File templateDir = new File(testDir, "template");
+ private File outputDir = new File("target/test-output/generating-file-visitor");
+
+ @Test
+ public void testSuccess(@Mocked OutputGenerator.OutputGeneratorBuilder builder) {
+ Path path = dataDir.toPath().resolve("mydir/success-test.txt.json");
+ Path expectedTemplateLocation = templateDir.toPath().resolve("test.ftl");
+ Path expectedOutputLocation = outputDir.toPath().resolve("mydir/success-test.txt");
+ Map<String,Object> expectedMap = new HashMap<String,Object>(4);
+ expectedMap.put("testVar", "test value");
+ JsonPropertiesProvider toTest = JsonPropertiesProvider.create(dataDir, templateDir, outputDir);
+ toTest.providePropertiesFromFile(path, builder);
+ new Verifications(){{
+ Path templateLocation;
+ builder.addTemplateLocation(templateLocation = withCapture());
+ Path outputLocation;
+ builder.addOutputLocation(outputLocation = withCapture());
+ Map<String,Object> actualMap;
+ builder.addDataModel(actualMap = withCapture());
+
+ assertEquals(expectedTemplateLocation, templateLocation);
+ assertEquals(expectedOutputLocation, outputLocation);
+ assertArrayEquals(expectedMap.entrySet().toArray(), actualMap.entrySet().toArray());
+ }};
+ }
+
+ @Test
+ public void testSuccessNoDataModel(@Mocked OutputGenerator.OutputGeneratorBuilder builder) {
+ Path path = dataDir.toPath().resolve("mydir/success-test-2.txt.json");
+ Path expectedTemplateLocation = templateDir.toPath().resolve("test-pom-only.ftl");
+ Path expectedOutputLocation = outputDir.toPath().resolve("mydir/success-test-2.txt");
+ Map<String,Object> expectedMap = new HashMap<String,Object>(4);
+ JsonPropertiesProvider toTest = JsonPropertiesProvider.create(dataDir, templateDir, outputDir);
+ toTest.providePropertiesFromFile(path, builder);
+ new Verifications(){{
+ Path templateLocation;
+ builder.addTemplateLocation(templateLocation = withCapture());
+ Path outputLocation;
+ builder.addOutputLocation(outputLocation = withCapture());
+ Map<String,Object> actualMap;
+ builder.addDataModel(actualMap = withCapture());
+
+ assertEquals(expectedTemplateLocation, templateLocation);
+ assertEquals(expectedOutputLocation, outputLocation);
+ assertArrayEquals(expectedMap.entrySet().toArray(), actualMap.entrySet().toArray());
+ }};
+ }
+
+ @Test
+ public void testParsingException(@Mocked OutputGenerator.OutputGeneratorBuilder builder, @Mocked Gson gson) {
+ Path path = dataDir.toPath().resolve("mydir/success-test.txt.json");
+ new Expectations() {{
+ gson.fromJson((JsonReader) any, (Type) any); result = new RuntimeException("test exception");
+ }};
+ JsonPropertiesProvider toTest = JsonPropertiesProvider.create(dataDir, templateDir, outputDir);
+
+ assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ toTest.providePropertiesFromFile(path, builder);
+ }).withMessage("Could not parse json data file: src/test/data/generating-file-visitor/data/mydir/success-test.txt.json");
+ }
+
+ @Test
+ public void testMissingTemplateName(@Mocked OutputGenerator.OutputGeneratorBuilder builder) {
+ Path path = dataDir.toPath().resolve("mydir/missing-template-name.txt.json");
+ JsonPropertiesProvider toTest = JsonPropertiesProvider.create(dataDir, templateDir, outputDir);
+
+ assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ toTest.providePropertiesFromFile(path, builder);
+ }).withMessage("Require json data property not found: templateName");
+ }
+
+ @Test
+ public void testBadPath(@Mocked OutputGenerator.OutputGeneratorBuilder builder) {
+ Path path = testDir.toPath().resolve("badPath/success-test.txt.json");
+ JsonPropertiesProvider toTest = JsonPropertiesProvider.create(dataDir, templateDir, outputDir);
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ toTest.providePropertiesFromFile(path, builder);
+ }).withMessage("visitFile() given file not in sourceDirectory: src/test/data/generating-file-visitor/badPath/success-test.txt.json");
+ }
+}
diff --git a/src/test/java/org/apache/freemarker/generator/OutputGeneratorTest.java b/src/test/java/org/apache/freemarker/generator/OutputGeneratorTest.java
new file mode 100644
index 0000000..63e5eeb
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/generator/OutputGeneratorTest.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.generator;
+
+import freemarker.cache.FileTemplateLoader;
+import freemarker.template.Configuration;
+import mockit.Expectations;
+import mockit.Mocked;
+import org.assertj.core.api.Assertions;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+import static junit.framework.Assert.assertEquals;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OutputGeneratorTest {
+
+ private File testDir = new File("src/test/data/generating-file-visitor");
+ private File dataDir = new File(testDir, "data");
+ private File templateDir = new File(testDir, "template");
+ private File outputDir = new File("target/test-output/generating-file-visitor");
+ private Configuration config;
+ private Map<String, Object> dataModel = new HashMap<String,Object>();
+
+ @BeforeMethod
+ public void setupDataModel() {
+ dataModel.clear();
+ dataModel.put("testVar", "test value");
+ dataModel.put("pomProperties", new HashMap<String,String>());
+ ((Map<String,String>)dataModel.get("pomProperties")).put("pomVar", "pom value");
+ }
+
+ @BeforeClass
+ public static void cleanFields() throws IOException {
+ // Clean output dir before each run.
+ File outputDir = new File("target/test-output/generating-file-visitor");
+ if (outputDir.exists()) {
+ // Recursively delete output from previous run.
+ Files.walk(outputDir.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+
+ @BeforeMethod
+ public void before() throws IOException {
+ if (!testDir.isDirectory()) {
+ throw new RuntimeException("Can't find required test data directory. "
+ + "If running test outside of maven, make sure working directory is the project directory. "
+ + "Looking for: " + testDir);
+ }
+
+ config = new Configuration(Configuration.VERSION_2_3_23);
+ config.setDefaultEncoding("UTF-8");
+ config.setTemplateLoader(new FileTemplateLoader(templateDir));
+ }
+
+ @Test
+ public void createTest() {
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set the pomModifiedTimestamp");
+
+ builder.addPomLastModifiedTimestamp(0);
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null generatorLocation");
+
+ File file = new File(dataDir, "mydir/success-test.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null templateLocation");
+
+ File templateFile = new File(templateDir, "test.ftl");
+ builder.addTemplateLocation(templateFile.toPath());
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null outputLocation");
+
+ File outputFile = new File(outputDir, "mydir/success-test.txt");
+ builder.addOutputLocation(outputFile.toPath());
+
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null dataModel");
+
+ builder.addDataModel(dataModel);
+ OutputGenerator generator = builder.create();
+
+ assertEquals(0, generator.pomModifiedTimestamp);
+ assertEquals(file.toPath(), generator.generatorLocation);
+ assertEquals(templateFile.toPath(), generator.templateLocation);
+ assertEquals(outputFile.toPath(), generator.outputLocation);
+ assertEquals(dataModel.size(), generator.dataModel.size());
+ assertArrayEquals(dataModel.entrySet().toArray(), generator.dataModel.entrySet().toArray());
+ }
+
+ @Test
+ public void addToDataModelTest() {
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set the pomModifiedTimestamp");
+
+ builder.addPomLastModifiedTimestamp(0);
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null generatorLocation");
+
+ File file = new File(dataDir, "mydir/success-test.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null templateLocation");
+
+ File templateFile = new File(templateDir, "test.ftl");
+ builder.addTemplateLocation(templateFile.toPath());
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null outputLocation");
+
+ File outputFile = new File(outputDir, "mydir/success-test.txt");
+ builder.addOutputLocation(outputFile.toPath());
+
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {
+ builder.create();
+ }).withMessage("Must set a non-null dataModel");
+
+ builder.addToDataModel("testVar", "testVal");
+ OutputGenerator generator = builder.create();
+
+ assertEquals(1, generator.dataModel.size());
+ assertEquals( "testVal", generator.dataModel.get("testVar"));
+
+ builder.addDataModel(dataModel);
+ builder.addToDataModel("testVar2", "testVal2");
+
+ generator = builder.create();
+
+ assertEquals(3, generator.dataModel.size());
+ assertEquals( "test value", generator.dataModel.get("testVar"));
+ assertEquals( "testVal2", generator.dataModel.get("testVar2"));
+ }
+
+ @Test
+ public void generate_SuccessTest()
+ throws IOException {
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ builder.addPomLastModifiedTimestamp(0);
+ File file = new File(dataDir, "mydir/success-test.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ File outputFile = new File(outputDir, "mydir/success-test.txt");
+ builder.addOutputLocation(outputFile.toPath());
+ File templateFile = new File(templateDir, "test.ftl");
+ builder.addTemplateLocation(templateFile.toPath());
+ builder.addDataModel(dataModel);
+ OutputGenerator generator = builder.create();
+ generator.generate(config);
+
+ assertTrue(outputFile.isFile());
+ List<String> lines = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
+ assertEquals(1, lines.size());
+ assertEquals("This is a test freemarker template. Test json data: 'test value'. Test pom data: 'pom value'.", lines.get(0));
+
+ // Process same file again, should not regenerate file.
+ long lastMod = outputFile.lastModified();
+ generator.generate(config);
+ assertEquals(lastMod, outputFile.lastModified());
+
+ // Set mod time to before json file.
+ lastMod = file.lastModified() - 1000; // File system may only keep 1 second precision.
+ outputFile.setLastModified(lastMod);
+ generator.generate(config);
+ assertTrue(lastMod < outputFile.lastModified());
+
+ // Set mod time to before template file.
+ lastMod = templateFile.lastModified() - 1000; // File system may only keep 1 second precision.
+ outputFile.setLastModified(lastMod);
+ generator.generate(config);
+ assertTrue(lastMod < outputFile.lastModified());
+ }
+
+ @Test
+ public void generate_badTemplateNameTest(){
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ builder.addPomLastModifiedTimestamp(0);
+ File file = new File(dataDir, "mydir/bad-template-name.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ File outputFile = new File(outputDir, "mydir/bad-template-name.txt");
+ builder.addOutputLocation(outputFile.toPath());
+ File templateFile = new File(templateDir, "missing.ftl"); //this doesn't exist
+ builder.addTemplateLocation(templateFile.toPath());
+ builder.addDataModel(dataModel);
+ OutputGenerator generator = builder.create();
+ Assertions.assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ generator.generate(config);
+ }).withMessage("Could not read template: missing.ftl");
+ }
+
+ @Test
+ public void generate_missingVarTest() {
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ builder.addPomLastModifiedTimestamp(0);
+ File file = new File(dataDir, "mydir/missing-var-test.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ File outputFile = new File(outputDir, "mydir/missing-var-test.txt");
+ builder.addOutputLocation(outputFile.toPath());
+ File templateFile = new File(templateDir, "test.ftl"); //this is missing a
+ builder.addTemplateLocation(templateFile.toPath());
+ dataModel.remove("testVar");
+ builder.addDataModel(dataModel);
+ OutputGenerator generator = builder.create();
+ Assertions.assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ generator.generate(config);
+ }).withMessage("Could not process template associated with data file: src/test/data/generating-file-visitor/data/mydir/missing-var-test.txt.json");
+ }
+
+ @Test
+ public void generate_badParentTest() throws IOException {
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ builder.addPomLastModifiedTimestamp(0);
+ File file = new File(dataDir, "badParent/bad-parent-test.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ File outputFile = new File(outputDir, "badParent/bad-parent-test.txt");
+ builder.addOutputLocation(outputFile.toPath());
+ File templateFile = new File(templateDir, "test.ftl"); //this is missing a
+ builder.addTemplateLocation(templateFile.toPath());
+ builder.addDataModel(dataModel);
+ OutputGenerator generator = builder.create();
+ outputDir.mkdirs();
+ outputFile.getParentFile().createNewFile();
+
+ Assertions.assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ generator.generate(config);
+ }).withMessage("Parent directory of output file is a file: " + outputFile.getParentFile().getAbsolutePath());
+ }
+
+ @Test
+ public void generate_cantCreateOutputFileParentDirTest(
+ @Mocked FactoryUtil factoryUtil,
+ @Mocked File mockOutputFile) throws IOException {
+
+ File parentDir = new File("target/test-output/generating-file-visitor/mydir");
+ new Expectations(mockOutputFile, parentDir) {{
+ FactoryUtil.createFile(anyString); result = mockOutputFile;
+ mockOutputFile.exists(); result = false;
+ mockOutputFile.getParentFile(); result = parentDir;
+ parentDir.isDirectory(); result = false;
+ }};
+
+ OutputGenerator.OutputGeneratorBuilder builder = OutputGenerator.builder();
+ builder.addPomLastModifiedTimestamp(0);
+ File file = new File(dataDir, "mydir/missing-var-test.txt.json");
+ builder.addGeneratorLocation(file.toPath());
+ File outputFile = new File(outputDir, "mydir/missing-var-test.txt");
+ builder.addOutputLocation(outputFile.toPath());
+ File templateFile = new File(templateDir, "test.ftl"); //this is missing a
+ builder.addTemplateLocation(templateFile.toPath());
+ builder.addDataModel(dataModel);
+ OutputGenerator generator = builder.create();
+ Assertions.assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
+ generator.generate(config);
+ }).withMessage("Could not create directory: " + parentDir.getAbsoluteFile().toString());
+ }
+}