Skip to content

Commit d2a6dc2

Browse files
committed
improve speed of generator
1 parent 1d3ef5c commit d2a6dc2

5 files changed

Lines changed: 79 additions & 32 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6341,12 +6341,15 @@ private String uniqueCaseInsensitiveString(String value, Map<String, String> see
63416341
return seenValues.get(value);
63426342
}
63436343

6344-
Optional<Entry<String, String>> foundEntry = seenValues.entrySet().stream().filter(v -> v.getValue().toLowerCase(Locale.ROOT).equals(value.toLowerCase(Locale.ROOT))).findAny();
6345-
if (foundEntry.isPresent()) {
6344+
// Build the set of already-used lowercase values once, to avoid O(n) re-collection per loop iteration.
6345+
Set<String> lowercaseValues = seenValues.values().stream()
6346+
.map(v -> v.toLowerCase(Locale.ROOT))
6347+
.collect(Collectors.toSet());
6348+
6349+
if (lowercaseValues.contains(value.toLowerCase(Locale.ROOT))) {
63466350
int counter = 0;
63476351
String uniqueValue = value + "_" + counter;
6348-
6349-
while (seenValues.values().stream().map(v -> v.toLowerCase(Locale.ROOT)).collect(Collectors.toList()).contains(uniqueValue.toLowerCase(Locale.ROOT))) {
6352+
while (lowercaseValues.contains(uniqueValue.toLowerCase(Locale.ROOT))) {
63506353
counter++;
63516354
uniqueValue = value + "_" + counter;
63526355
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,8 @@ protected File processTemplateToFile(Map<String, Object> templateData, String te
14301430
return processTemplateToFile(templateData, templateName, outputFilename, shouldGenerate, skippedByOption, this.config.getOutputDir());
14311431
}
14321432

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

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

1446-
if (seenFiles.stream().filter(f -> f.toLowerCase(Locale.ROOT).equals(absoluteTarget.toString().toLowerCase(Locale.ROOT))).findAny().isPresent()) {
1447-
LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget.toString());
1447+
// O(1) case-insensitive duplicate check via a pre-lowercased shadow set
1448+
if (!seenFilesLower.add(absoluteTarget.toString().toLowerCase(Locale.ROOT))) {
1449+
LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget);
14481450
}
1449-
seenFiles.add(absoluteTarget.toString());
14501451
return this.templateProcessor.write(templateData, templateName, target);
14511452
} else {
14521453
this.templateProcessor.skip(target.toPath(), String.format(Locale.ROOT, "Skipped by %s options supplied by user.", skippedByOption));

modules/openapi-generator/src/main/java/org/openapitools/codegen/TemplateManager.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222
import java.util.Objects;
2323
import java.util.Scanner;
24+
import java.util.concurrent.ConcurrentHashMap;
2425
import java.util.regex.Pattern;
2526

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

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

37+
/** Cache of resolved template path -> raw template content, populated on first read per run. */
38+
private final Map<String, String> templateContentCache = new ConcurrentHashMap<>();
39+
3640
/**
3741
* Constructs a new instance of a {@link TemplateManager}
3842
*
@@ -75,7 +79,8 @@ private String getFullTemplateFile(String name) {
7579
*/
7680
@Override
7781
public String getFullTemplateContents(String name) {
78-
return readTemplate(getFullTemplateFile(name));
82+
String fullPath = getFullTemplateFile(name);
83+
return templateContentCache.computeIfAbsent(fullPath, this::readTemplate);
7984
}
8085

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

264269
private boolean filesEqual(File file1, File file2) throws IOException {
265-
return file1.exists() && file2.exists() && Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
270+
if (!file1.exists() || !file2.exists()) return false;
271+
if (file1.length() != file2.length()) return false;
272+
return Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
266273
}
267274
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/HandlebarsEngineAdapter.java

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.Arrays;
4141
import java.util.Locale;
4242
import java.util.Map;
43+
import java.util.concurrent.ConcurrentHashMap;
4344

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

54+
/**
55+
* Cache of (templateFile -> compiled Template). Compiled Handlebars templates are stateless after
56+
* compilation and safe to reuse across multiple data-bundle invocations within the same run.
57+
*/
58+
private final Map<String, Template> compiledTemplateCache = new ConcurrentHashMap<>();
59+
60+
/**
61+
* Cached Handlebars engine instance with all helpers pre-registered.
62+
* Re-created lazily whenever the TemplatingExecutor changes (different template paths).
63+
*/
64+
private volatile Handlebars cachedHandlebars;
65+
private volatile TemplatingExecutor cachedExecutor;
66+
5367
/**
5468
* Provides an identifier used to load the adapter. This could be a name, uuid, or any other string.
5569
*
@@ -63,13 +77,6 @@ public String getIdentifier() {
6377
@Override
6478
public String compileTemplate(TemplatingExecutor executor,
6579
Map<String, Object> bundle, String templateFile) throws IOException {
66-
TemplateLoader loader = new AbstractTemplateLoader() {
67-
@Override
68-
public TemplateSource sourceAt(String location) {
69-
return findTemplate(executor, location);
70-
}
71-
};
72-
7380
Context context = Context
7481
.newBuilder(bundle)
7582
.resolver(
@@ -79,18 +86,38 @@ public TemplateSource sourceAt(String location) {
7986
AccessAwareFieldValueResolver.INSTANCE)
8087
.build();
8188

82-
Handlebars handlebars = new Handlebars(loader);
83-
handlebars.registerHelperMissing((obj, options) -> {
84-
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
85-
return "";
89+
// Reuse the Handlebars engine when the executor hasn't changed; rebuild otherwise.
90+
if (cachedHandlebars == null || cachedExecutor != executor) {
91+
TemplateLoader loader = new AbstractTemplateLoader() {
92+
@Override
93+
public TemplateSource sourceAt(String location) {
94+
return findTemplate(executor, location);
95+
}
96+
};
97+
Handlebars handlebars = new Handlebars(loader);
98+
handlebars.registerHelperMissing((obj, options) -> {
99+
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
100+
return "";
101+
});
102+
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
103+
StringHelpers.register(handlebars);
104+
handlebars.registerHelpers(ConditionalHelpers.class);
105+
handlebars.registerHelpers(org.openapitools.codegen.templating.handlebars.StringHelpers.class);
106+
handlebars.setInfiniteLoops(infiniteLoops);
107+
handlebars.setPrettyPrint(prettyPrint);
108+
// Changing the executor means template content may differ; clear stale entries.
109+
compiledTemplateCache.clear();
110+
cachedHandlebars = handlebars;
111+
cachedExecutor = executor;
112+
}
113+
114+
Template tmpl = compiledTemplateCache.computeIfAbsent(templateFile, tf -> {
115+
try {
116+
return cachedHandlebars.compile(tf);
117+
} catch (IOException e) {
118+
throw new RuntimeException("Failed to compile Handlebars template: " + tf, e);
119+
}
86120
});
87-
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
88-
StringHelpers.register(handlebars);
89-
handlebars.registerHelpers(ConditionalHelpers.class);
90-
handlebars.registerHelpers(org.openapitools.codegen.templating.handlebars.StringHelpers.class);
91-
handlebars.setInfiniteLoops(infiniteLoops);
92-
handlebars.setPrettyPrint(prettyPrint);
93-
Template tmpl = handlebars.compile(templateFile);
94121
return tmpl.apply(context);
95122
}
96123

modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/MustacheEngineAdapter.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.io.StringReader;
3232
import java.io.StringWriter;
3333
import java.util.Map;
34+
import java.util.concurrent.ConcurrentHashMap;
3435

3536

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

55+
/**
56+
* Cache of template file name -> compiled Template object.
57+
* Templates are stateless after compilation and safe to reuse across invocations.
58+
*/
59+
private final Map<String, Template> compiledTemplateCache = new ConcurrentHashMap<>();
60+
5461
/**
5562
* Compiles a template into a string
5663
*
@@ -62,10 +69,12 @@ public String getIdentifier() {
6269
*/
6370
@Override
6471
public String compileTemplate(TemplatingExecutor executor, Map<String, Object> bundle, String templateFile) throws IOException {
65-
Template tmpl = compiler
66-
.withLoader(name -> findTemplate(executor, name))
67-
.defaultValue("")
68-
.compile(executor.getFullTemplateContents(templateFile));
72+
// Compile once and cache; the compiled Template is stateless and reusable.
73+
Template tmpl = compiledTemplateCache.computeIfAbsent(templateFile, tf ->
74+
compiler
75+
.withLoader(name -> findTemplate(executor, name))
76+
.defaultValue("")
77+
.compile(executor.getFullTemplateContents(tf)));
6978
StringWriter out = new StringWriter();
7079

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

0 commit comments

Comments
 (0)