From 636c4dd2d6681d67a11c5c374814034257ef7771 Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Fri, 22 May 2020 14:31:05 -0400 Subject: [PATCH 01/26] [core] Refactor templating management This refactors template management to get logic out of DefaultGenerator and to provide a cleaner API to template search and read/compile. Deprecates MockDefaultGenerator, which is not a mock and causes in-memory retention of file contents. Maintainers should prefer executing a "dryRun" with new DefaultGenerator(true) or do true mock/spies if evaluating template intermediaries is truly necessary. Tests may read written files with lower overhead than the in-process retention of those bytes. This attempts to maintain some compatibility with existing templating adapter interfaces. Any breaking change here would be unintentional but minimal effort to retarget the new interface. --- .../org/openapitools/codegen/cmd/Meta.java | 9 +- .../codegen/api/TemplatePathLocator.java | 14 + .../codegen/api/TemplateProcessor.java | 56 ++ .../codegen/api/TemplatingEngineAdapter.java | 24 +- .../codegen/api/TemplatingExecutor.java | 26 + .../codegen/api/TemplatingGenerator.java | 24 +- .../generator/gradle/plugin/tasks/MetaTask.kt | 21 +- .../openapitools/codegen/CodegenConfig.java | 2 - .../openapitools/codegen/DefaultCodegen.java | 15 - .../codegen/DefaultGenerator.java | 418 +++-------- .../codegen/DryRunTemplateManager.java | 107 +++ .../codegen/GlobalSupportingFile.java | 29 - ...actGenerator.java => TemplateManager.java} | 312 +++++---- .../CommonTemplateContentLocator.java | 36 + .../GeneratorTemplateContentLocator.java | 91 +++ .../templating/HandlebarsEngineAdapter.java | 9 +- .../templating/MustacheEngineAdapter.java | 20 +- .../templating/TemplateManagerOptions.java | 45 ++ .../src/main/resources/_common/LICENSE | 201 ------ .../codegen/DefaultGeneratorTest.java | 52 +- .../codegen/MockDefaultGenerator.java | 81 ++- .../org/openapitools/codegen/TestUtils.java | 82 ++- .../asciidoc/AsciidocGeneratorTest.java | 7 +- .../HaskellServantCodegenTest.java | 16 +- .../codegen/java/JavaClientCodegenTest.java | 296 ++++---- .../JavaJAXRSCXFExtServerCodegenTest.java | 47 +- .../jaxrs/JavaJAXRSSpecServerCodegenTest.java | 70 +- .../codegen/java/jaxrs/JavaJaxrsBaseTest.java | 19 +- .../java/spring/SpringCodegenTest.java | 655 ++++++++++-------- .../kotlin/KotlinModelCodegenTest.java | 14 +- .../scalaakka/ScalaAkkaClientCodegenTest.java | 93 ++- .../codegen/yaml/YamlGeneratorTest.java | 36 +- 32 files changed, 1505 insertions(+), 1422 deletions(-) create mode 100644 modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatePathLocator.java create mode 100644 modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplateProcessor.java create mode 100644 modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingExecutor.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunTemplateManager.java delete mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/GlobalSupportingFile.java rename modules/openapi-generator/src/main/java/org/openapitools/codegen/{AbstractGenerator.java => TemplateManager.java} (52%) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/CommonTemplateContentLocator.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/GeneratorTemplateContentLocator.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/TemplateManagerOptions.java delete mode 100644 modules/openapi-generator/src/main/resources/_common/LICENSE diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Meta.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Meta.java index 9fa400098fb1..560768ae2b14 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Meta.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Meta.java @@ -30,6 +30,7 @@ import org.openapitools.codegen.CodegenConfig; import org.openapitools.codegen.DefaultGenerator; import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.TemplateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -146,9 +147,9 @@ public File convert(SupportingFile support) { new File(new File(targetDir.getAbsolutePath()), support.folder); File outputFile = new File(destinationFolder, support.destinationFilename); - String template = - generator.readTemplate(new File(TEMPLATE_DIR_CLASSPATH, - support.templateFile).getPath()); + String template =((TemplateManager)generator.getTemplateProcessor()).readTemplate(new File(TEMPLATE_DIR_CLASSPATH, + support.templateFile).getPath()); + String formatted = template; if (support.templateFile.endsWith(MUSTACHE_EXTENSION)) { @@ -180,7 +181,7 @@ private static Mustache.TemplateLoader loader(final DefaultGenerator generator) return new Mustache.TemplateLoader() { @Override public Reader getTemplate(String name) { - return generator.getTemplateReader(TEMPLATE_DIR_CLASSPATH + File.separator + return ((TemplateManager)generator.getTemplateProcessor()).getTemplateReader(TEMPLATE_DIR_CLASSPATH + File.separator + name.concat(MUSTACHE_EXTENSION)); } }; diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatePathLocator.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatePathLocator.java new file mode 100644 index 000000000000..9bcd4e2cc8b2 --- /dev/null +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatePathLocator.java @@ -0,0 +1,14 @@ +package org.openapitools.codegen.api; + +/** + * Provides means for searching for "actual" template location based on relative template file. + */ +public interface TemplatePathLocator { + /** + * Get the full path to a relative template file. + * + * @param relativeTemplateFile Template file + * @return String Full template file path + */ + String getFullTemplatePath(String relativeTemplateFile); +} diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplateProcessor.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplateProcessor.java new file mode 100644 index 000000000000..4d0516261075 --- /dev/null +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplateProcessor.java @@ -0,0 +1,56 @@ +package org.openapitools.codegen.api; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +/** + * Interface for abstractions around writing templated data to a file. + */ +public interface TemplateProcessor { + /** + * Writes data to a compiled template + * + * @param data Input data + * @param template Input template location + * @param target The targeted file output location + * + * @return The actual file + */ + File write(Map data, String template, File target) throws IOException; + + /** + * Write bytes to a file + * + * @param filename The name of file to write + * @param contents The contents bytes. Typically this is a UTF-8 formatted string. + * @return File representing the written file. + * @throws IOException If file cannot be written. + */ + File writeToFile(String filename, byte[] contents) throws IOException; + + /** + * Allow a caller to mark a path as ignored with accompanying reason + * + * @param path The ignored path + * @param context The reason for ignoring this path + */ + void ignore(Path path, String context); + + /** + * Allow a caller to mark a path as skipped with accompanying reason + * + * @param path The skipped path + * @param context The reason for skipping this path + */ + void skip(Path path, String context); + + /** + * Allow a caller to mark a path having errored during processing with accompanying reason + * + * @param path The path which has caused an error + * @param context The reason for the error + */ + default void error(Path path, String context) { }; +} diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java index 7fceac5246a3..c7240cfe6c2c 100644 --- a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java @@ -36,27 +36,27 @@ public interface TemplatingEngineAdapter { */ String getIdentifier(); + /** + * During generation, if a supporting file has a file extension that is + * inside that array, then it is considered a templated supporting file + * and we use the templating engine adapter to generate it + * + * @return string array of the valid file extensions for this templating engine + */ + String[] getFileExtensions(); + /** * Compiles a template into a string * - * @param generator From where we can fetch the templates content (e.g. an instance of DefaultGenerator) + * @param executor From where we can fetch the templates content (e.g. an instance of DefaultGenerator) * @param bundle The map of values to pass to the template * @param templateFile The name of the template (e.g. model.mustache ) * @return the processed template result * @throws IOException an error ocurred in the template processing */ - String compileTemplate(TemplatingGenerator generator, Map bundle, + String compileTemplate(TemplatingExecutor executor, Map bundle, String templateFile) throws IOException; - /** - * During generation, if a supporting file has a file extension that is - * inside that array, then it is considered a templated supporting file - * and we use the templating engine adapter to generate it - * - * @return string array of the valid file extensions for this templating engine - */ - String[] getFileExtensions(); - /** * Determines whether the template file with supported extensions exists. This may be on the filesystem, * external filesystem, or classpath (implementation is up to TemplatingGenerator). @@ -65,7 +65,7 @@ String compileTemplate(TemplatingGenerator generator, Map bundle * @param templateFile The original target filename * @return True if the template is available in the template search path, false if it can not be found */ - default boolean templateExists(TemplatingGenerator generator, String templateFile) { + default boolean templateExists(TemplatingExecutor generator, String templateFile) { return Arrays.stream(getFileExtensions()).anyMatch(ext -> { int idx = templateFile.lastIndexOf("."); String baseName; diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingExecutor.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingExecutor.java new file mode 100644 index 000000000000..d0fbb86b5db9 --- /dev/null +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingExecutor.java @@ -0,0 +1,26 @@ +package org.openapitools.codegen.api; + +import java.nio.file.Path; + +/** + * interface to the full template content + * implementers might take into account the -t cli option, + * look in the resources for a generator specific template, etc + */ +public interface TemplatingExecutor { + /** + * returns the template content by name + * + * @param name the template name (e.g. model.mustache) + * @return the contents of that template + */ + String getFullTemplateContents(String name); + + /** + * Returns the path of a template, allowing access to the template where consuming literal contents aren't desirable or possible. + * + * @param name the template name (e.g. model.mustache) + * @return The {@link Path} to the template + */ + Path getFullTemplatePath(String name); +} \ No newline at end of file diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java index 268cda6cdfba..007e606b2cf2 100644 --- a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java @@ -16,28 +16,16 @@ package org.openapitools.codegen.api; -import java.nio.file.Path; - +// TODO: 6.0 Remove /** * interface to the full template content * implementers might take into account the -t cli option, * look in the resources for a language specific template, etc + * + * @deprecated as of 5.0, replaced by {@link TemplatingExecutor} + * @apiNote Deprecation is considered low-impact breaking change because tooling only supports internal implementations. */ -public interface TemplatingGenerator { - - /** - * returns the template content by name - * - * @param name the template name (e.g. model.mustache) - * @return the contents of that template - */ - String getFullTemplateContents(String name); +@Deprecated() +public interface TemplatingGenerator extends TemplatingExecutor { - /** - * Returns the path of a template, allowing access to the template where consuming literal contents aren't desirable or possible. - * - * @param name the template name (e.g. model.mustache) - * @return The {@link Path} to the template - */ - Path getFullTemplatePath(String name); } diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt index 6a4a180665aa..a4668f68afc9 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt @@ -24,10 +24,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.internal.logging.text.StyledTextOutput import org.gradle.internal.logging.text.StyledTextOutputFactory import org.gradle.kotlin.dsl.property -import org.openapitools.codegen.CodegenConfig -import org.openapitools.codegen.CodegenConstants -import org.openapitools.codegen.DefaultGenerator -import org.openapitools.codegen.SupportingFile +import org.openapitools.codegen.* import java.io.File import java.io.IOException import java.nio.charset.Charset @@ -90,9 +87,17 @@ open class MetaTask : DefaultTask() { destinationFolder.mkdirs() val outputFile = File(destinationFolder, it.destinationFilename) - val template = generator.readTemplate(File("codegen", it.templateFile).path) + val templateProcessor = generator.templateProcessor as TemplateManager + + val template = templateProcessor.getFullTemplateContents(File("codegen", it.templateFile).path) var formatted = template + val loader: (DefaultGenerator) -> Mustache.TemplateLoader = { g -> + Mustache.TemplateLoader { name -> + templateProcessor.getTemplateReader("codegen${File.separator}$name.mustache") + } + } + if (it.templateFile.endsWith(".mustache")) { formatted = Mustache.compiler() .withLoader(loader(generator)) @@ -115,12 +120,6 @@ open class MetaTask : DefaultTask() { out.formatln("Created generator %s", klass) } - private fun loader(generator: DefaultGenerator): Mustache.TemplateLoader { - return Mustache.TemplateLoader { name -> - generator.getTemplateReader("codegen${File.separator}$name.mustache") - } - } - private fun String.titleCasedTextOnly(): String = this.split(Regex("[^a-zA-Z0-9]")).joinToString(separator = "", transform = String::capitalize) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java index 06fd6a30d627..5b05c3ccf422 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java @@ -247,8 +247,6 @@ public interface CodegenConfig { String getDocExtension(); - String getCommonTemplateDir(); - void setIgnoreFilePathOverride(String ignoreFileOverride); String getIgnoreFilePathOverride(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 08894cd58a43..f52020409dff 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -163,7 +163,6 @@ apiTemplateFiles are for API outputs only (controllers/handlers). protected Map reservedWordsMappings = new HashMap(); protected String templateDir; protected String embeddedTemplateDir; - protected String commonTemplateDir = "_common"; protected Map additionalProperties = new HashMap(); protected Map serverVariables = new HashMap(); protected Map vendorExtensions = new HashMap(); @@ -946,14 +945,6 @@ public String embeddedTemplateDir() { } } - public String getCommonTemplateDir() { - return this.commonTemplateDir; - } - - public void setCommonTemplateDir(String commonTemplateDir) { - this.commonTemplateDir = commonTemplateDir; - } - public Map apiDocTemplateFiles() { return apiDocTemplateFiles; } @@ -1426,12 +1417,6 @@ public DefaultCodegen() { importMapping.put("LocalDate", "org.joda.time.*"); importMapping.put("LocalTime", "org.joda.time.*"); - // we've used the .openapi-generator-ignore approach as - // suppportingFiles can be cleared by code generator that extends - // the default codegen, leaving the commented code below for - // future reference - //supportingFiles.add(new GlobalSupportingFile("LICENSE", "LICENSE")); - cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString())); cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG, diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 7a1c3a29511b..15e75bd9973f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -29,9 +29,10 @@ import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.security.*; import io.swagger.v3.oas.models.tags.Tag; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.api.TemplatePathLocator; +import org.openapitools.codegen.api.TemplateProcessor; import org.openapitools.codegen.config.GlobalSettings; import org.openapitools.codegen.api.TemplatingEngineAdapter; import org.openapitools.codegen.ignore.CodegenIgnoreProcessor; @@ -39,7 +40,10 @@ import org.openapitools.codegen.meta.GeneratorMetadata; import org.openapitools.codegen.meta.Stability; import org.openapitools.codegen.serializer.SerializerUtils; +import org.openapitools.codegen.templating.CommonTemplateContentLocator; +import org.openapitools.codegen.templating.GeneratorTemplateContentLocator; import org.openapitools.codegen.templating.MustacheEngineAdapter; +import org.openapitools.codegen.templating.TemplateManagerOptions; import org.openapitools.codegen.utils.ImplementationVersion; import org.openapitools.codegen.utils.ModelUtils; import org.openapitools.codegen.utils.ProcessUtils; @@ -49,6 +53,7 @@ import java.io.*; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.ZonedDateTime; import java.util.*; @@ -56,13 +61,13 @@ import static org.openapitools.codegen.utils.OnceLogger.once; @SuppressWarnings("rawtypes") -public class DefaultGenerator extends AbstractGenerator implements Generator { +public class DefaultGenerator implements Generator { protected final Logger LOGGER = LoggerFactory.getLogger(DefaultGenerator.class); + private final boolean dryRun; protected CodegenConfig config; protected ClientOptInput opts; protected OpenAPI openAPI; protected CodegenIgnoreProcessor ignoreProcessor; - protected TemplatingEngineAdapter templatingEngine; private Boolean generateApis = null; private Boolean generateModels = null; private Boolean generateSupportingFiles = null; @@ -75,9 +80,11 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { private String basePathWithoutHost; private String contextPath; private Map generatorPropertyDefaults = new HashMap<>(); + protected TemplateProcessor templateProcessor = null; public DefaultGenerator() { + this(false); } public DefaultGenerator(Boolean dryRun) { @@ -85,18 +92,33 @@ public DefaultGenerator(Boolean dryRun) { LOGGER.info("Generating with dryRun={}", this.dryRun); } - @Override - public boolean getEnableMinimalUpdate() { - return config.isEnableMinimalUpdate(); - } - @SuppressWarnings("deprecation") @Override public Generator opts(ClientOptInput opts) { this.opts = opts; this.openAPI = opts.getOpenAPI(); this.config = opts.getConfig(); - this.templatingEngine = this.config.getTemplatingEngine(); + + TemplateManagerOptions templateManagerOptions = new TemplateManagerOptions(this.config.isEnableMinimalUpdate(),this.config.isSkipOverwrite()); + + if (this.dryRun) { + this.templateProcessor = new DryRunTemplateManager(templateManagerOptions); + } else { + TemplatingEngineAdapter templatingEngine = this.config.getTemplatingEngine(); + + if (templatingEngine instanceof MustacheEngineAdapter) { + MustacheEngineAdapter mustacheEngineAdapter = (MustacheEngineAdapter) templatingEngine; + mustacheEngineAdapter.setCompiler(this.config.processCompiler(mustacheEngineAdapter.getCompiler())); + } + + TemplatePathLocator commonTemplateLocator = new CommonTemplateContentLocator(); + TemplatePathLocator generatorTemplateLocator = new GeneratorTemplateContentLocator(this.config); + this.templateProcessor = new TemplateManager( + templateManagerOptions, + templatingEngine, + new TemplatePathLocator[]{generatorTemplateLocator, commonTemplateLocator} + ); + } String ignoreFileLocation = this.config.getIgnoreFilePathOverride(); if (ignoreFileLocation != null) { @@ -115,11 +137,14 @@ public Generator opts(ClientOptInput opts) { return this; } - private void configPostProcessMustacheCompiler() { - if (this.templatingEngine instanceof MustacheEngineAdapter) { - MustacheEngineAdapter mustacheEngineAdapter = (MustacheEngineAdapter) this.templatingEngine; - mustacheEngineAdapter.setCompiler(this.config.processCompiler(mustacheEngineAdapter.getCompiler())); - } + /** + * Retrieves an instance to the configured template processor, available after user-defined options are + * applied via {@link DefaultGenerator#opts(ClientOptInput)}. + * + * @return A configured {@link TemplateProcessor}, or null. + */ + public TemplateProcessor getTemplateProcessor() { + return templateProcessor; } /** @@ -299,21 +324,16 @@ private void generateModelTests(List files, Map models, St String suffix = config.modelTestTemplateFiles().get(templateName); String filename = config.modelTestFileFolder() + File.separator + config.toModelTestFilename(modelName) + suffix; + // TODO: Verify logic to never allow overwriting of tests. + if (generateModelTests) { // do not overwrite test file that already exists (regardless of config's skipOverwrite setting) if (new File(filename).exists()) { LOGGER.info("File exists. Skipped overwriting {}", filename); - if (dryRun) { - dryRunStatusMap.put(filename, - new DryRunStatus( - java.nio.file.Paths.get(filename), - DryRunStatus.State.SkippedOverwrite, - "Test files never overwrite an existing file of the same name." - )); - } + this.templateProcessor.skip(java.nio.file.Paths.get(filename), "Test files never overwrite an existing file of the same name."); continue; } - File written = processTemplateToFile(models, templateName, filename); + File written = processTemplateToFile(models, templateName, filename, generateModelTests, CodegenConstants.MODEL_TESTS); if (written != null) { files.add(written); if (config.isEnablePostProcessFile() && !dryRun) { @@ -321,12 +341,8 @@ private void generateModelTests(List files, Map models, St } } } else if (dryRun) { - dryRunStatusMap.put(filename, - new DryRunStatus( - java.nio.file.Paths.get(filename), - DryRunStatus.State.Skipped, - "Skipped by modelTests option supplied by user." - )); + Path skippedPath = java.nio.file.Paths.get(filename); + this.templateProcessor.skip(skippedPath, "Skipped by modelTests option supplied by user."); } } } @@ -337,28 +353,12 @@ private void generateModelDocumentation(List files, Map mo String suffix = docExtension != null ? docExtension : config.modelDocTemplateFiles().get(templateName); String filename = config.modelDocFileFolder() + File.separator + config.toModelDocFilename(modelName) + suffix; - if (generateModelDocumentation) { - if (!config.shouldOverwrite(filename)) { - LOGGER.info("Skipped overwriting {}", filename); - if (dryRun) { - dryRunStatusMap.put(filename, new DryRunStatus(java.nio.file.Paths.get(filename), DryRunStatus.State.SkippedOverwrite)); - } - continue; - } - File written = processTemplateToFile(models, templateName, filename); - if (written != null) { - files.add(written); - if (config.isEnablePostProcessFile() && !dryRun) { - config.postProcessFile(written, "model-doc"); - } + File written = processTemplateToFile(models, templateName, filename, generateModelDocumentation, CodegenConstants.MODEL_DOCS); + if (written != null) { + files.add(written); + if (config.isEnablePostProcessFile() && !dryRun) { + config.postProcessFile(written, "model-doc"); } - } else if (dryRun) { - dryRunStatusMap.put(filename, - new DryRunStatus( - java.nio.file.Paths.get(filename), - DryRunStatus.State.Skipped, - "Skipped by modelDocs option supplied by user." - )); } } } @@ -371,24 +371,12 @@ private String getModelFilenameByTemplate(String modelName, String templateName) private void generateModel(List files, Map models, String modelName) throws IOException { for (String templateName : config.modelTemplateFiles().keySet()) { String filename = getModelFilenameByTemplate(modelName, templateName); - if (!config.shouldOverwrite(filename)) { - LOGGER.info("Skipped overwriting {}", filename); - if (dryRun) { - dryRunStatusMap.put(filename, new DryRunStatus( - java.nio.file.Paths.get(filename), - DryRunStatus.State.SkippedOverwrite - )); - } - continue; - } - File written = processTemplateToFile(models, templateName, filename); + File written = processTemplateToFile(models, templateName, filename, generateModels, CodegenConstants.MODELS); if (written != null) { files.add(written); if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(written, "model"); } - } else { - LOGGER.warn("Unknown issue writing {}", filename); } } } @@ -438,16 +426,12 @@ void generateModels(List files, List allModels, List unuse //don't generate models that have an import mapping if (config.importMapping().containsKey(name)) { LOGGER.debug("Model {} not imported due to import mapping", name); - if (dryRun) { + + for (String templateName : config.modelTemplateFiles().keySet()) { // HACK: Because this returns early, could lead to some invalid model reporting. - for (String templateName : config.modelTemplateFiles().keySet()) { - String filename = getModelFilenameByTemplate(name, templateName); - dryRunStatusMap.put(filename, new DryRunStatus( - java.nio.file.Paths.get(filename), - DryRunStatus.State.Skipped, - "Skipped prior to model processing due to import mapping conflict (either by user or by generator)." - )); - } + String filename = getModelFilenameByTemplate(name, templateName); + Path path = java.nio.file.Paths.get(filename); + this.templateProcessor.skip(path,"Skipped prior to model processing due to import mapping conflict (either by user or by generator)." ); } continue; } @@ -633,17 +617,7 @@ private void generateApis(List files, List allOperations, List files, List allOperations, List files, Map bundl String outputFilename = new File(support.destinationFilename).isAbsolute() // split ? support.destinationFilename : outputFolder + File.separator + support.destinationFilename.replace('/', File.separatorChar); - if (!config.shouldOverwrite(outputFilename)) { - LOGGER.info("Skipped overwriting {}", outputFilename); - if (dryRun) { - Path skippedSupportingFile = java.nio.file.Paths.get(outputFilename); - DryRunStatus status = new DryRunStatus( - skippedSupportingFile, - DryRunStatus.State.SkippedOverwrite - ); - } - continue; - } - String templateFile; - if (support instanceof GlobalSupportingFile) { - templateFile = config.getCommonTemplateDir() + File.separator + support.templateFile; - } else { - templateFile = getFullTemplateFile(config, support.templateFile); - } + boolean shouldGenerate = true; if (supportingFilesToGenerate != null && !supportingFilesToGenerate.isEmpty()) { shouldGenerate = supportingFilesToGenerate.contains(support.destinationFilename); } - if (!shouldGenerate) { - if (dryRun) { - Path skippedSupportingFile = java.nio.file.Paths.get(outputFilename); - DryRunStatus status = new DryRunStatus( - skippedSupportingFile, - DryRunStatus.State.Skipped, - "Skipped by supportingFiles option supplied by user." - ); - dryRunStatusMap.put(outputFilename, status); - } - continue; - } - if (ignoreProcessor.allowsFile(new File(outputFilename))) { - // support.templateFile is the unmodified/original supporting file name (e.g. build.sh.mustache) - // templatingEngine.templateExists dispatches resolution to this, performing template-engine specific inspect of support file extensions. - if (templatingEngine.templateExists(this, support.templateFile)) { - String templateContent = templatingEngine.compileTemplate(this, bundle, support.templateFile); - writeToFile(outputFilename, templateContent); - File written = new File(outputFilename); - files.add(written); - if (config.isEnablePostProcessFile()) { - config.postProcessFile(written, "supporting-mustache"); - } - } else { - if (Arrays.stream(templatingEngine.getFileExtensions()).anyMatch(templateFile::endsWith)) { - String templateContent = templatingEngine.compileTemplate(this, bundle, support.templateFile); - writeToFile(outputFilename, templateContent); - File written = new File(outputFilename); - files.add(written); - if (config.isEnablePostProcessFile()) { - config.postProcessFile(written, "supporting-mustache"); - } - } else { - InputStream in = null; - - try { - in = new FileInputStream(templateFile); - } catch (Exception e) { - // continue - } - if (in == null) { - in = this.getClass().getClassLoader().getResourceAsStream(getCPResourcePath(templateFile)); - } - File outputFile = writeInputStreamToFile(outputFilename, in, templateFile); - files.add(outputFile); - if (config.isEnablePostProcessFile()) { - config.postProcessFile(outputFile, "supporting-common"); - } - } - } - - } else { - if (dryRun) { - dryRunStatusMap.put(outputFilename, new DryRunStatus(java.nio.file.Paths.get(outputFilename), DryRunStatus.State.Ignored)); + File written = processTemplateToFile(bundle, support.templateFile, outputFilename, shouldGenerate, CodegenConstants.SUPPORTING_FILES); + if (written != null) { + files.add(written); + if (config.isEnablePostProcessFile() && !dryRun) { + config.postProcessFile(written, "api-doc"); } - LOGGER.info("Skipped generation of {} due to rule in .openapi-generator-ignore", outputFilename); } } catch (Exception e) { throw new RuntimeException("Could not generate supporting file '" + support + "'", e); @@ -838,33 +711,29 @@ private void generateSupportingFiles(List files, Map bundl final String openapiGeneratorIgnore = ".openapi-generator-ignore"; String ignoreFileNameTarget = config.outputFolder() + File.separator + openapiGeneratorIgnore; File ignoreFile = new File(ignoreFileNameTarget); - if (generateMetadata && !ignoreFile.exists()) { - String ignoreFileNameSource = File.separator + config.getCommonTemplateDir() + File.separator + openapiGeneratorIgnore; - String ignoreFileContents = readResourceContents(ignoreFileNameSource); + if (generateMetadata) { try { - writeToFile(ignoreFileNameTarget, ignoreFileContents); - } catch (IOException e) { - throw new RuntimeException("Could not generate supporting file '" + openapiGeneratorIgnore + "'", e); - } - files.add(ignoreFile); - if (config.isEnablePostProcessFile() && !dryRun) { - config.postProcessFile(ignoreFile, "openapi-generator-ignore"); + boolean shouldGenerate = !ignoreFile.exists(); + if (shouldGenerate && supportingFilesToGenerate != null && !supportingFilesToGenerate.isEmpty()) { + shouldGenerate = supportingFilesToGenerate.contains(openapiGeneratorIgnore); + } + File written = processTemplateToFile(bundle, openapiGeneratorIgnore, ignoreFileNameTarget, shouldGenerate, CodegenConstants.SUPPORTING_FILES); + if (written != null) { + files.add(written); + if (config.isEnablePostProcessFile() && !dryRun) { + config.postProcessFile(written, "openapi-generator-ignore"); + } + } + } catch (Exception e) { + throw new RuntimeException("Could not generate supporting file '" + ignoreFileNameTarget + "'", e); } - } else if (generateMetadata && dryRun && ignoreFile.exists()) { - dryRunStatusMap.put(ignoreFileNameTarget, new DryRunStatus(ignoreFile.toPath(), DryRunStatus.State.SkippedOverwrite)); - } else if (!generateMetadata && dryRun) { - dryRunStatusMap.put(ignoreFileNameTarget, new DryRunStatus( - ignoreFile.toPath(), - DryRunStatus.State.Skipped, - "Skipped by generateMetadata option supplied by user" - )); } String versionMetadata = config.outputFolder() + File.separator + ".openapi-generator" + File.separator + "VERSION"; if (generateMetadata) { File versionMetadataFile = new File(versionMetadata); try { - writeToFile(versionMetadata, ImplementationVersion.read()); + this.templateProcessor.writeToFile(versionMetadata, ImplementationVersion.read().getBytes(StandardCharsets.UTF_8)); files.add(versionMetadataFile); if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(ignoreFile, "openapi-generator-version"); @@ -872,30 +741,10 @@ private void generateSupportingFiles(List files, Map bundl } catch (IOException e) { throw new RuntimeException("Could not generate supporting file '" + versionMetadata + "'", e); } - } else if(!generateMetadata && dryRun) { + } else { Path metadata = java.nio.file.Paths.get(versionMetadata); - DryRunStatus status = new DryRunStatus(metadata, DryRunStatus.State.Skipped, "Skipped by generateMetadata option supplied by user."); - dryRunStatusMap.put(versionMetadata, status); + this.templateProcessor.skip(metadata, "Skipped by generateMetadata option supplied by user."); } - - /* - * The following code adds default LICENSE (Apache-2.0) for all generators - * To use license other than Apache2.0, update the following file: - * modules/openapi-generator/src/main/resources/_common/LICENSE - * - final String apache2License = "LICENSE"; - String licenseFileNameTarget = config.outputFolder() + File.separator + apache2License; - File licenseFile = new File(licenseFileNameTarget); - String licenseFileNameSource = File.separator + config.getCommonTemplateDir() + File.separator + apache2License; - String licenseFileContents = readResourceContents(licenseFileNameSource); - try { - writeToFile(licenseFileNameTarget, licenseFileContents); - } catch (IOException e) { - throw new RuntimeException("Could not generate LICENSE file '" + apache2License + "'", e); - } - files.add(licenseFile); - */ - } @SuppressWarnings("unchecked") @@ -1005,9 +854,6 @@ public List generate() { configureGeneratorProperties(); configureOpenAPIInfo(); - // If the template adapter is mustache, we'll set the config-modified Compiler. - configPostProcessMustacheCompiler(); - List files = new ArrayList<>(); // models List filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI); @@ -1030,6 +876,8 @@ public List generate() { sb.append("Dry Run Results:"); sb.append(System.lineSeparator()).append(System.lineSeparator()); + Map dryRunStatusMap = ((DryRunTemplateManager) this.templateProcessor).getDryRunStatusMap(); + dryRunStatusMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { DryRunStatus status = entry.getValue(); try { @@ -1065,37 +913,22 @@ public List generate() { return files; } - @Override - public String getFullTemplateContents(String templateName) { - return readTemplate(getFullTemplateFile(config, templateName)); - } - /** - * Returns the path of a template, allowing access to the template where consuming literal contents aren't desirable or possible. - * - * @param name the template name (e.g. model.mustache) - * @return The {@link Path} to the template - */ - @Override - public Path getFullTemplatePath(String name) { - String fullPath = getFullTemplateFile(config, name); - return java.nio.file.Paths.get(fullPath); - } - - protected File processTemplateToFile(Map templateData, String templateName, String outputFilename) throws IOException { + protected File processTemplateToFile(Map templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption) throws IOException { String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar); File target = new File(adjustedOutputFilename); if (ignoreProcessor.allowsFile(target)) { - String templateContent = templatingEngine.compileTemplate(this, templateData, templateName); - writeToFile(adjustedOutputFilename, templateContent); - return target; - } else if (this.dryRun) { - dryRunStatusMap.put(adjustedOutputFilename, new DryRunStatus(target.toPath(), DryRunStatus.State.Ignored)); + if (shouldGenerate) { + return this.templateProcessor.write(templateData,templateName, target); + } else { + this.templateProcessor.skip(target.toPath(), String.format(Locale.ROOT, "Skipped by %s options supplied by user.", skippedByOption)); + } + } else { + this.templateProcessor.ignore(target.toPath(), "Ignored by rule in ignore file."); return target; } - LOGGER.info("Skipped generation of {} due to rule in .openapi-generator-ignore", adjustedOutputFilename); - return null; + return target; } public Map> processPaths(Paths paths) { @@ -1444,53 +1277,4 @@ private List filterAuthMethods(List authMethod return result; } - - - - protected File writeInputStreamToFile(String filename, InputStream in, String templateFile) throws IOException { - if (in != null) { - byte[] bytes = IOUtils.toByteArray(in); - if (dryRun) { - Path path = java.nio.file.Paths.get(filename); - dryRunStatusMap.put(filename, new DryRunStatus(path)); - return path.toFile(); - } - - return writeToFile(filename, bytes); - } else { - LOGGER.error("can't open '{}' for input; cannot write '{}'", templateFile, filename); - if (dryRun) { - Path path = java.nio.file.Paths.get(filename); - dryRunStatusMap.put(filename, new DryRunStatus(path, DryRunStatus.State.Error)); - } - - return null; - } - } - - /** - * Write bytes to a file - * - * @param filename The name of file to write - * @param contents The contents bytes. Typically this is a UTF-8 formatted string. - * @return File representing the written file. - * @throws IOException If file cannot be written. - */ - @Override - public File writeToFile(String filename, byte[] contents) throws IOException { - if (dryRun) { - Path path = java.nio.file.Paths.get(filename); - DryRunStatus status = new DryRunStatus(path); - if (getEnableMinimalUpdate()) { - status.setState(DryRunStatus.State.WriteIfNewer); - } else { - status.setState(DryRunStatus.State.Write); - } - - dryRunStatusMap.put(filename, status); - return path.toFile(); - } else { - return super.writeToFile(filename, contents); - } - } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunTemplateManager.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunTemplateManager.java new file mode 100644 index 000000000000..64b7d4177ca1 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunTemplateManager.java @@ -0,0 +1,107 @@ +package org.openapitools.codegen; + +import com.google.common.collect.ImmutableMap; +import org.openapitools.codegen.api.TemplateProcessor; +import org.openapitools.codegen.templating.TemplateManagerOptions; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +/** + * Manages templates for a generator "dry run" + */ +public class DryRunTemplateManager implements TemplateProcessor { + private final TemplateManagerOptions options; + private final Map dryRunStatusMap = new HashMap<>(); + + /** + * Constructs a new instance of {@link DryRunTemplateManager} for the provided options + * + * @param options Options pertaining to templates (reads and writes) + */ + public DryRunTemplateManager(TemplateManagerOptions options) { + this.options = options; + } + + /** + * Gets the full status of this dry run. + * + * @return An immutable copy of the dry run status. + */ + public Map getDryRunStatusMap() { + return ImmutableMap.copyOf(dryRunStatusMap); + } + + /** + * Writes data to a compiled template + * + * @param data Input data + * @param template Input template location + * @param target The targeted file output location + * @return The actual file + */ + @Override + public File write(Map data, String template, File target) throws IOException { + if (this.options.isSkipOverwrite() && target.exists()) { + dryRunStatusMap.put(target.toString(), + new DryRunStatus( + target.toPath(), + DryRunStatus.State.SkippedOverwrite, + "File exists and skip overwrite option is enabled." + )); + } + + return null; + } + + @Override + public File writeToFile(String filename, byte[] contents) throws IOException { + Path path = java.nio.file.Paths.get(filename); + DryRunStatus status = new DryRunStatus(path); + if (this.options.isMinimalUpdate()) { + status.setState(DryRunStatus.State.WriteIfNewer); + } else { + status.setState(DryRunStatus.State.Write); + } + dryRunStatusMap.put(filename, status); + return path.toFile(); + } + + @Override + public void ignore(Path path, String context) { + dryRunStatusMap.put(path.toString(), + new DryRunStatus( + path, + DryRunStatus.State.Ignored, + context + )); + } + + @Override + public void skip(Path path, String context) { + if (this.options.isSkipOverwrite() && path.toFile().exists()) { + dryRunStatusMap.put(path.toString(), + new DryRunStatus( + path, + DryRunStatus.State.SkippedOverwrite, + context + )); + return; + } + + dryRunStatusMap.put(path.toString(), + new DryRunStatus( + path, + DryRunStatus.State.Skipped, + context + )); + } + + @Override + public void error(Path path, String context) { + dryRunStatusMap.put(path.toString(), new DryRunStatus(path, DryRunStatus.State.Error)); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/GlobalSupportingFile.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/GlobalSupportingFile.java deleted file mode 100644 index 06b6e9cfab75..000000000000 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/GlobalSupportingFile.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) - * Copyright 2018 SmartBear Software - * - * Licensed 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 - * - * https://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.openapitools.codegen; - -public class GlobalSupportingFile extends SupportingFile { - - GlobalSupportingFile(String templateFile, String folder, String destinationFilename) { - super(templateFile, folder, destinationFilename); - } - - GlobalSupportingFile(String templateFile, String destinationFilename) { - super(templateFile, destinationFilename); - } -} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/TemplateManager.java similarity index 52% rename from modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java rename to modules/openapi-generator/src/main/java/org/openapitools/codegen/TemplateManager.java index 3a2fecbc5411..d2765062b4ee 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/TemplateManager.java @@ -1,117 +1,119 @@ -/* - * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) - * Copyright 2018 SmartBear Software - * - * Licensed 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 - * - * https://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.openapitools.codegen; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.Arrays; import org.apache.commons.lang3.StringUtils; -import org.openapitools.codegen.api.TemplatingGenerator; +import org.openapitools.codegen.api.TemplatePathLocator; +import org.openapitools.codegen.api.TemplateProcessor; +import org.openapitools.codegen.api.TemplatingEngineAdapter; +import org.openapitools.codegen.api.TemplatingExecutor; +import org.openapitools.codegen.templating.TemplateManagerOptions; +import org.openapitools.codegen.templating.TemplateNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; import java.util.Map; +import java.util.Objects; import java.util.Scanner; import java.util.regex.Pattern; -public abstract class AbstractGenerator implements TemplatingGenerator { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractGenerator.class); - protected boolean dryRun = false; - protected Map dryRunStatusMap = new HashMap<>(); +/** + * Manages the lookup, compilation, and writing of template files + */ +public class TemplateManager implements TemplatingExecutor, TemplateProcessor { + private final TemplateManagerOptions options; + private final TemplatingEngineAdapter engineAdapter; + private final TemplatePathLocator[] templateLoaders; + + private static final Logger LOGGER = LoggerFactory.getLogger(TemplateManager.class); /** - * Is the minimal-file-update option enabled? - * - * @return Option value + * Constructs a new instance of a {@link TemplateManager} + * + * @param options The {@link TemplateManagerOptions} for reading and writing templates + * @param engineAdapter The adaptor to underlying templating engine + * @param templateLoaders Loaders which define where we look for templates */ - public abstract boolean getEnableMinimalUpdate(); + public TemplateManager( + TemplateManagerOptions options, + TemplatingEngineAdapter engineAdapter, + TemplatePathLocator[] templateLoaders) { + this.options = options; + this.engineAdapter = engineAdapter; + this.templateLoaders = templateLoaders; + } + + private String getFullTemplateFile(String name) { + String template = Arrays.stream(this.templateLoaders) + .map(i -> i.getFullTemplatePath(name)) + .filter(Objects::nonNull) + .findFirst() + .orElse(""); + + if (StringUtils.isEmpty(template)) { + throw new TemplateNotFoundException(name); + } + + return template; + } /** - * Write String to a file, formatting as UTF-8 - * - * @param filename The name of file to write - * @param contents The contents string. - * @return File representing the written file. - * @throws IOException If file cannot be written. + * returns the template content by name + * + * @param name the template name (e.g. model.mustache) + * @return the contents of that template */ - public File writeToFile(String filename, String contents) throws IOException { - return writeToFile(filename, contents.getBytes(StandardCharsets.UTF_8)); + @Override + public String getFullTemplateContents(String name) { + return readTemplate(getFullTemplateFile(name)); } /** - * Write bytes to a file - * - * @param filename The name of file to write - * @param contents The contents bytes. Typically, this is a UTF-8 formatted string. - * @return File representing the written file. - * @throws IOException If file cannot be written. + * Returns the path of a template, allowing access to the template where consuming literal contents aren't desirable or possible. + * + * @param name the template name (e.g. model.mustache) + * @return The {@link Path} to the template */ - @SuppressWarnings("static-method") - public File writeToFile(String filename, byte[] contents) throws IOException { - if (getEnableMinimalUpdate()) { - String tempFilename = filename + ".tmp"; - // Use Paths.get here to normalize path (for Windows file separator, space escaping on Linux/Mac, etc) - File outputFile = Paths.get(filename).toFile(); - File tempFile = null; - try { - tempFile = writeToFileRaw(tempFilename, contents); - if (!filesEqual(tempFile, outputFile)) { - LOGGER.info("writing file " + filename); - Files.move(tempFile.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - tempFile = null; - } else { - LOGGER.info("skipping unchanged file " + filename); - } - } finally { - if (tempFile != null && tempFile.exists()) { - try { - tempFile.delete(); - } catch (Exception ex) { - LOGGER.error("Error removing temporary file " + tempFile, ex); - } - } - } - return outputFile; - } else { - LOGGER.info("writing file " + filename); - return writeToFileRaw(filename, contents); - } + @Override + public Path getFullTemplatePath(String name) { + return Paths.get(getFullTemplateFile(name)); } - private boolean filesEqual(File file1, File file2) throws IOException { - return file1.exists() && file2.exists() && Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath())); + public String readResourceContents(String resourceFilePath) { + StringBuilder sb = new StringBuilder(); + Scanner scanner = new Scanner(this.getClass().getResourceAsStream(getCPResourcePath(resourceFilePath)), "UTF-8"); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + sb.append(line).append('\n'); + } + return sb.toString(); } - - private File writeToFileRaw(String filename, byte[] contents) throws IOException { - // Use Paths.get here to normalize path (for Windows file separator, space escaping on Linux/Mac, etc) - File output = Paths.get(filename).toFile(); - if (output.getParent() != null && !new File(output.getParent()).exists()) { - File parent = Paths.get(output.getParent()).toFile(); - parent.mkdirs(); + + /** + * Gets a normalized classpath resource location according to OS-specific file separator + * + * @param name The name of the resource file/directory to find + * + * @return A normalized string according to OS-specific file separator + */ + public static String getCPResourcePath(final String name) { + if (!"/".equals(File.separator)) { + return name.replaceAll(Pattern.quote(File.separator), "/"); } - Files.write(output.toPath(), contents); - return output; + return name; } + /** + * Reads a template's contents from the specified location + * + * @param name The location of the template + * @return The raw template contents + */ public String readTemplate(String name) { try { Reader reader = getTemplateReader(name); @@ -135,79 +137,109 @@ public Reader getTemplateReader(String name) { if (is == null) { is = new FileInputStream(new File(name)); // May throw but never return a null value } - return new InputStreamReader(is, "UTF-8"); - } catch (FileNotFoundException | UnsupportedEncodingException e) { + return new InputStreamReader(is, StandardCharsets.UTF_8); + } catch (FileNotFoundException e) { LOGGER.error(e.getMessage()); throw new RuntimeException("can't load template " + name); } } - private String buildLibraryFilePath(String dir, String library, String file) { - return dir + File.separator + "libraries" + File.separator + library + File.separator + file; + /** + * Writes data to a compiled template + * + * @param data Input data + * @param template Input template location + * @param target The targeted file output location + * + * @return The actual file + */ + @Override + public File write(Map data, String template, File target) throws IOException { + String templateContent = this.engineAdapter.compileTemplate(this, data, template); + return writeToFile(target.getPath(), templateContent); + } + + @Override + public void ignore(Path path, String context) { + LOGGER.info("Ignored {} ({})", path.toString(), context); + } + + @Override + public void skip(Path path, String context) { + LOGGER.info("Skipped {} ({})", path.toString(), context); } /** - * Get the template file path with template dir prepended, and use the - * library template if exists. + * Write String to a file, formatting as UTF-8 * - * @param config Codegen config - * @param templateFile Template file - * @return String Full template file path + * @param filename The name of file to write + * @param contents The contents string. + * @return File representing the written file. + * @throws IOException If file cannot be written. */ - public String getFullTemplateFile(CodegenConfig config, String templateFile) { - //1st the code will check if there's a