Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6341,12 +6341,15 @@ private String uniqueCaseInsensitiveString(String value, Map<String, String> see
return seenValues.get(value);
}

Optional<Entry<String, String>> foundEntry = seenValues.entrySet().stream().filter(v -> v.getValue().toLowerCase(Locale.ROOT).equals(value.toLowerCase(Locale.ROOT))).findAny();
if (foundEntry.isPresent()) {
// Build the set of already-used lowercase values once, to avoid O(n) re-collection per loop iteration.
Set<String> lowercaseValues = seenValues.values().stream()
.map(v -> v.toLowerCase(Locale.ROOT))
.collect(Collectors.toSet());

if (lowercaseValues.contains(value.toLowerCase(Locale.ROOT))) {
int counter = 0;
String uniqueValue = value + "_" + counter;

while (seenValues.values().stream().map(v -> v.toLowerCase(Locale.ROOT)).collect(Collectors.toList()).contains(uniqueValue.toLowerCase(Locale.ROOT))) {
while (lowercaseValues.contains(uniqueValue.toLowerCase(Locale.ROOT))) {
counter++;
uniqueValue = value + "_" + counter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1430,7 +1430,8 @@ protected File processTemplateToFile(Map<String, Object> templateData, String te
return processTemplateToFile(templateData, templateName, outputFilename, shouldGenerate, skippedByOption, this.config.getOutputDir());
}

private final Set<String> seenFiles = new HashSet<>();
/** Stores lowercased absolute paths for O(1) case-insensitive duplicate detection. */
private final Set<String> seenFilesLower = new HashSet<>();

private File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption, String intendedOutputDir) throws IOException {
String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
Expand All @@ -1443,10 +1444,10 @@ private File processTemplateToFile(Map<String, Object> templateData, String temp
throw new RuntimeException(String.format(Locale.ROOT, "Target files must be generated within the output directory; absoluteTarget=%s outDir=%s", absoluteTarget, outDir));
}

if (seenFiles.stream().filter(f -> f.toLowerCase(Locale.ROOT).equals(absoluteTarget.toString().toLowerCase(Locale.ROOT))).findAny().isPresent()) {
LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget.toString());
// O(1) case-insensitive duplicate check via a pre-lowercased shadow set
if (!seenFilesLower.add(absoluteTarget.toString().toLowerCase(Locale.ROOT))) {
LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget);
}
seenFiles.add(absoluteTarget.toString());
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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
Expand All @@ -33,6 +34,9 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {

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

/** Cache of resolved template path -> raw template content, populated on first read per run. */
private final Map<String, String> templateContentCache = new ConcurrentHashMap<>();

/**
* Constructs a new instance of a {@link TemplateManager}
*
Expand Down Expand Up @@ -75,7 +79,8 @@ private String getFullTemplateFile(String name) {
*/
@Override
public String getFullTemplateContents(String name) {
return readTemplate(getFullTemplateFile(name));
String fullPath = getFullTemplateFile(name);
return templateContentCache.computeIfAbsent(fullPath, this::readTemplate);
}

/**
Expand Down Expand Up @@ -262,6 +267,8 @@ private File writeToFileRaw(String filename, byte[] contents) throws IOException
}

private boolean filesEqual(File file1, File file2) throws IOException {
return file1.exists() && file2.exists() && Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
if (!file1.exists() || !file2.exists()) return false;
if (file1.length() != file2.length()) return false;
return Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
final Logger LOGGER = LoggerFactory.getLogger(HandlebarsEngineAdapter.class);
Expand All @@ -50,6 +51,19 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
private boolean infiniteLoops = false;
@Setter private boolean prettyPrint = false;

/**
* Cache of (templateFile -> compiled Template). Compiled Handlebars templates are stateless after
* compilation and safe to reuse across multiple data-bundle invocations within the same run.
*/
private final Map<String, Template> compiledTemplateCache = new ConcurrentHashMap<>();

/**
* Cached Handlebars engine instance with all helpers pre-registered.
* Re-created lazily whenever the TemplatingExecutor changes (different template paths).
*/
private volatile Handlebars cachedHandlebars;
private volatile TemplatingExecutor cachedExecutor;

/**
* Provides an identifier used to load the adapter. This could be a name, uuid, or any other string.
*
Expand All @@ -63,13 +77,6 @@ public String getIdentifier() {
@Override
public String compileTemplate(TemplatingExecutor executor,
Map<String, Object> bundle, String templateFile) throws IOException {
TemplateLoader loader = new AbstractTemplateLoader() {
@Override
public TemplateSource sourceAt(String location) {
return findTemplate(executor, location);
}
};

Context context = Context
.newBuilder(bundle)
.resolver(
Expand All @@ -79,18 +86,38 @@ public TemplateSource sourceAt(String location) {
AccessAwareFieldValueResolver.INSTANCE)
.build();

Handlebars handlebars = new Handlebars(loader);
handlebars.registerHelperMissing((obj, options) -> {
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
return "";
// Reuse the Handlebars engine when the executor hasn't changed; rebuild otherwise.
if (cachedHandlebars == null || cachedExecutor != executor) {
TemplateLoader loader = new AbstractTemplateLoader() {
@Override
public TemplateSource sourceAt(String location) {
return findTemplate(executor, location);
}
};
Handlebars handlebars = new Handlebars(loader);
handlebars.registerHelperMissing((obj, options) -> {
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
return "";
});
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
StringHelpers.register(handlebars);
handlebars.registerHelpers(ConditionalHelpers.class);
handlebars.registerHelpers(org.openapitools.codegen.templating.handlebars.StringHelpers.class);
handlebars.setInfiniteLoops(infiniteLoops);
handlebars.setPrettyPrint(prettyPrint);
// Changing the executor means template content may differ; clear stale entries.
compiledTemplateCache.clear();
cachedHandlebars = handlebars;
cachedExecutor = executor;
}

Template tmpl = compiledTemplateCache.computeIfAbsent(templateFile, tf -> {
try {
return cachedHandlebars.compile(tf);
} catch (IOException e) {
throw new RuntimeException("Failed to compile Handlebars template: " + tf, e);
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
}
});
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
StringHelpers.register(handlebars);
handlebars.registerHelpers(ConditionalHelpers.class);
handlebars.registerHelpers(org.openapitools.codegen.templating.handlebars.StringHelpers.class);
handlebars.setInfiniteLoops(infiniteLoops);
handlebars.setPrettyPrint(prettyPrint);
Template tmpl = handlebars.compile(templateFile);
return tmpl.apply(context);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class MustacheEngineAdapter implements TemplatingEngineAdapter {
Expand All @@ -51,6 +52,12 @@ public String getIdentifier() {
@Getter @Setter
Mustache.Compiler compiler = Mustache.compiler();

/**
* Cache of template file name -> compiled Template object.
* Templates are stateless after compilation and safe to reuse across invocations.
*/
private final Map<String, Template> compiledTemplateCache = new ConcurrentHashMap<>();

/**
* Compiles a template into a string
*
Expand All @@ -62,10 +69,12 @@ public String getIdentifier() {
*/
@Override
public String compileTemplate(TemplatingExecutor executor, Map<String, Object> bundle, String templateFile) throws IOException {
Template tmpl = compiler
.withLoader(name -> findTemplate(executor, name))
.defaultValue("")
.compile(executor.getFullTemplateContents(templateFile));
// Compile once and cache; the compiled Template is stateless and reusable.
Template tmpl = compiledTemplateCache.computeIfAbsent(templateFile, tf ->
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
compiler
.withLoader(name -> findTemplate(executor, name))
.defaultValue("")
.compile(executor.getFullTemplateContents(tf)));
StringWriter out = new StringWriter();

// the value of bundle[MUSTACHE_PARENT_CONTEXT] is used a parent content in mustache.
Expand Down
Loading