diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java index c2b1a9aa93b0..e2c37a46b509 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java @@ -66,6 +66,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen implements BeanValidationFeatures, PerformBeanValidationFeatures, GzipFeatures { static final String MEDIA_TYPE = "mediaType"; + private static final int GSON_ADAPTERS_PER_BATCH = 200; private final Logger LOGGER = LoggerFactory.getLogger(JavaClientCodegen.class); @@ -1415,6 +1416,31 @@ public void forceSerializationLibrary(String serializationLibrary) { @Override public Map postProcessSupportingFileData(Map objs) { generateYAMLSpecFile(objs); + if (isLibrary(OKHTTP_GSON)) { + // Match the models registered by JSON.mustache, preserving their generation order. + @SuppressWarnings("unchecked") + List models = (List) objs.get("models"); + List adapters = new ArrayList<>(); + if (models != null) { + for (ModelMap model : models) { + CodegenModel m = model.getModel(); + if (m != null && !m.isEnum && !m.hasChildren) { + adapters.add(m); + } + } + } + // Large initializers can exceed the JVM method-size limit; register in bounded batches. + if (adapters.size() > GSON_ADAPTERS_PER_BATCH) { + List> batches = new ArrayList<>(); + for (int i = 0; i < adapters.size(); i += GSON_ADAPTERS_PER_BATCH) { + Map batch = new HashMap<>(); + batch.put("index", batches.size()); + batch.put("models", adapters.subList(i, Math.min(i + GSON_ADAPTERS_PER_BATCH, adapters.size()))); + batches.add(batch); + } + objs.put("gsonAdapterBatches", batches); + } + } return super.postProcessSupportingFileData(objs); } diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/JSON.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/JSON.mustache index e285a2a3ef45..88783c95e805 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/JSON.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/JSON.mustache @@ -117,6 +117,14 @@ public class JSON { return clazz; } + {{#gsonAdapterBatches}} + private static void registerModelAdapters{{index}}(GsonBuilder gsonBuilder) { + {{#models}} + gsonBuilder.registerTypeAdapterFactory(new {{modelPackage}}.{{{classname}}}.CustomTypeAdapterFactory()); + {{/models}} + } + + {{/gsonAdapterBatches}} static { GsonBuilder gsonBuilder = createGson(); gsonBuilder.registerTypeAdapter(Date.class, dateTypeAdapter); @@ -131,6 +139,10 @@ public class JSON { gsonBuilder.registerTypeAdapter(LocalDateTime.class, localDateTimeTypeAdapter); {{/jsr310}} gsonBuilder.registerTypeAdapter(byte[].class, byteArrayAdapter); + {{#gsonAdapterBatches}} + registerModelAdapters{{index}}(gsonBuilder); + {{/gsonAdapterBatches}} + {{^gsonAdapterBatches}} {{#models}} {{#model}} {{^isEnum}} @@ -140,6 +152,7 @@ public class JSON { {{/isEnum}} {{/model}} {{/models}} + {{/gsonAdapterBatches}} gson = gsonBuilder.create(); } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index 256c2203428e..a212aabcf0e3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -81,6 +81,33 @@ public class JavaClientCodegenTest { private static final String JAVA_GENERATOR = "java"; + @Test + @SneakyThrows + public void testOkHttpGsonBatchesModelAdapterRegistrations() { + Path output = newTempFolder(); + Path spec = output.resolve("many-models.yaml"); + StringBuilder yaml = new StringBuilder("openapi: 3.0.0\ninfo: {title: Batch test, version: '1'}\npaths: {}\ncomponents:\n schemas:\n"); + for (int i = 0; i < 201; i++) { + yaml.append(" Model").append(i).append(":\n type: object\n properties:\n value: {type: string}\n"); + } + Files.write(spec, yaml.toString().getBytes(StandardCharsets.UTF_8)); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(JAVA_GENERATOR) + .setLibrary(OKHTTP_GSON) + .setInputSpec(spec.toString()) + .setOutputDir(output.toString()); + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + Path json = output.resolve("src/main/java/org/openapitools/client/JSON.java"); + String code = new String(Files.readAllBytes(json), StandardCharsets.UTF_8); + StaticJavaParser.parse(code); + assertThat(code).contains("private static void registerModelAdapters0(GsonBuilder gsonBuilder)", + "private static void registerModelAdapters1(GsonBuilder gsonBuilder)", + "registerModelAdapters0(gsonBuilder);", "registerModelAdapters1(gsonBuilder);"); + for (int i = 0; i < 201; i++) { + assertThat(code).containsOnlyOnce("new org.openapitools.client.model.Model" + i + ".CustomTypeAdapterFactory()"); + } + } + // This is the kind of information that ideally would be defined and available system-wide @Getter enum Library {