Skip to content

Commit

Permalink
[Java/Spring] all-of and one-of Improvements and Fixes (was #12075) (#…
Browse files Browse the repository at this point in the history
…12089)

* Fix Bug in OneOfImplementorAdditionalData pulling in wrong vars to one-of-implementors.
Support parentVars in order to support fluent setter with inherited properties.

Squashed commit of the following:

commit f945c943777a1a496d7de8fc0a188842d9efb1ac
Author: Lars Uffmann <[email protected]>
Date:   Thu Apr 7 18:22:54 2022 +0200

    Polishing

commit 23ce1d0ff1faff53e85ca4362f33660962aa6a92
Author: Lars Uffmann <[email protected]>
Date:   Thu Apr 7 17:15:28 2022 +0200

    Add JavaDoc

commit fee70fde5709afa67f3aabd4f48ba496df63a884
Author: Lars Uffmann <[email protected]>
Date:   Thu Apr 7 17:11:17 2022 +0200

    Add imports for inherited Properties

commit 29509aaac51750fbd33c00a57d32cac34cbcbb90
Author: Lars Uffmann <[email protected]>
Date:   Thu Apr 7 13:40:36 2022 +0200

    Generate Samples

commit 1d19d5465137d3af712f2fd3b4ae4474c58af15e
Author: Lars Uffmann <[email protected]>
Date:   Thu Apr 7 13:21:23 2022 +0200

    SpringCodegen: Support parentVars in order to support fluent setter with inherited properties.

commit 2217a77bb747d0b07ef17407a6b5dd5c624a2551
Author: Lars Uffmann <[email protected]>
Date:   Thu Apr 7 07:18:50 2022 +0200

    Add allVars to omit list in OneOfImplementorAdditionalData

commit 90499a3b0a187971bfe25deb6355c3444dcf89a7
Author: Lars Uffmann <[email protected]>
Date:   Wed Apr 6 16:40:23 2022 +0200

    Works exactly as needed for oneOf/allOf in Java/Spring

commit b6d496d772e0d0a8d87a3b8cdba8fd3ca4db7f3f
Author: Lars Uffmann <[email protected]>
Date:   Wed Apr 6 15:16:27 2022 +0200

    Debug Session: identify critical codep path

commit 85722360038107f15841d5acc448d93dee513a06
Author: Lars Uffmann <[email protected]>
Date:   Tue Apr 5 09:56:38 2022 +0200

    Add test case to reproduce issue.

commit 14acc5cd974bb5260f3751015558807e2eb1a8a1
Author: Lars Uffmann <[email protected]>
Date:   Tue Apr 5 06:57:30 2022 +0200

    Add config to reproduce the issue

* Adjust indentation.

Co-authored-by: Lars Uffmann <[email protected]>
  • Loading branch information
cachescrubber and Lars Uffmann authored Apr 16, 2022
1 parent e12100b commit f195a83
Show file tree
Hide file tree
Showing 76 changed files with 1,431 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -989,17 +989,17 @@ hasChildren, isMap, isDeprecated, hasOnlyReadOnly, getExternalDocumentation(), g
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("CodegenModel{");
sb.append("parent='").append(parent).append('\'');
sb.append("name='").append(name).append('\'');
sb.append(", parent='").append(parent).append('\'');
sb.append(", parentSchema='").append(parentSchema).append('\'');
sb.append(", interfaces=").append(interfaces);
sb.append(", interfaceModels=").append(interfaceModels!=null?interfaceModels.size():"[]");
sb.append(", allParents=").append(allParents);
sb.append(", parentModel=").append(parentModel);
sb.append(", interfaceModels=").append(interfaceModels);
sb.append(", children=").append(children);
sb.append(", children=").append(children!=null?children.size():"[]");
sb.append(", anyOf=").append(anyOf);
sb.append(", oneOf=").append(oneOf);
sb.append(", allOf=").append(allOf);
sb.append(", name='").append(name).append('\'');
sb.append(", classname='").append(classname).append('\'');
sb.append(", title='").append(title).append('\'');
sb.append(", description='").append(description).append('\'');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3193,7 +3193,7 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
// FIXME: for now, we assume that the discriminator property is String
discriminator.setPropertyType(typeMapping.get("string"));
discriminator.setMapping(sourceDiscriminator.getMapping());
List<MappedModel> uniqueDescendants = new ArrayList();
List<MappedModel> uniqueDescendants = new ArrayList<>();
if (sourceDiscriminator.getMapping() != null && !sourceDiscriminator.getMapping().isEmpty()) {
for (Entry<String, String> e : sourceDiscriminator.getMapping().entrySet()) {
String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
Expand Down Expand Up @@ -884,9 +887,67 @@ public CodegenModel fromModel(String name, Schema model) {
codegenModel.imports.remove("ApiModelProperty");
codegenModel.imports.remove("ApiModel");
}

return codegenModel;
}

/**
* Analyse and post process all Models.
* Add parentVars to every Model which has a parent. This allows to generate
* fluent setter methods for inherited properties.
* @param objs the models map.
* @return the processed models map.
*/
@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
objs = super.postProcessAllModels(objs);
objs = super.updateAllModels(objs);

for (ModelsMap modelsAttrs : objs.values()) {
for (ModelMap mo : modelsAttrs.getModels()) {
CodegenModel codegenModel = mo.getModel();
Set<String> inheritedImports = new HashSet<>();
Map<String, CodegenProperty> propertyHash = new HashMap<>(codegenModel.vars.size());
for (final CodegenProperty property : codegenModel.vars) {
propertyHash.put(property.name, property);
}
CodegenModel parentCodegenModel = codegenModel.parentModel;
while (parentCodegenModel != null) {
for (final CodegenProperty property : parentCodegenModel.vars) {
// helper list of parentVars simplifies templating
if (!propertyHash.containsKey(property.name)) {
propertyHash.put(property.name, property);
final CodegenProperty parentVar = property.clone();
parentVar.isInherited = true;
LOGGER.info("adding parent variable {}", property.name);
codegenModel.parentVars.add(parentVar);
Set<String> imports = parentVar.getImports(true).stream().filter(Objects::nonNull).collect(Collectors.toSet());
for (String imp: imports) {
// Avoid dupes
if (!codegenModel.getImports().contains(imp)) {
inheritedImports.add(imp);
codegenModel.getImports().add(imp);
}
}
}
}
parentCodegenModel = parentCodegenModel.getParentModel();
}
// There must be a better way ...
for (String imp: inheritedImports) {
String qimp = importMapping().get(imp);
if (qimp != null) {
Map<String,String> toAdd = new HashMap<>();
toAdd.put("import", qimp);
modelsAttrs.getImports().add(toAdd);
}
}
}
}
return objs;
}


/*
* Add dynamic imports based on the parameters and vendor extensions of an operation.
* The imports are expanded by the mustache {{import}} tag available to model and api
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package org.openapitools.codegen.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This class holds data to add to `oneOf` members. Let's consider this example:
*
Expand Down Expand Up @@ -69,15 +70,19 @@ public void addFromInterfaceModel(CodegenModel cm, List<Map<String, String>> mod
// Add all vars defined on cm
// a "oneOf" model (cm) by default inherits all properties from its "interfaceModels",
// but we only want to add properties defined on cm itself
List<CodegenProperty> toAdd = new ArrayList<CodegenProperty>(cm.vars);
List<CodegenProperty> toAdd = new ArrayList<>(cm.vars);

// note that we can't just toAdd.removeAll(m.vars) for every interfaceModel,
// as they might have different value of `hasMore` and thus are not equal
List<String> omitAdding = new ArrayList<String>();
Set<String> omitAdding = new HashSet<>();
if (cm.interfaceModels != null) {
for (CodegenModel m : cm.interfaceModels) {
for (CodegenProperty v : m.vars) {
omitAdding.add(v.baseName);
}
for (CodegenProperty v : m.allVars) {
omitAdding.add(v.baseName);
}
}
}
for (CodegenProperty v : toAdd) {
Expand All @@ -89,7 +94,7 @@ public void addFromInterfaceModel(CodegenModel cm, List<Map<String, String>> mod
// Add all imports of cm
for (Map<String, String> importMap : modelsImports) {
// we're ok with shallow clone here, because imports are strings only
additionalImports.add(new HashMap<String, String>(importMap));
additionalImports.add(new HashMap<>(importMap));
}
}

Expand Down Expand Up @@ -120,6 +125,7 @@ public void addToImplementor(CodegenConfig cc, CodegenModel implcm, List<Map<Str

// Add oneOf-containing models properties - we need to properly set the hasMore values to make rendering correct
implcm.vars.addAll(additionalProps);
implcm.hasVars = ! implcm.vars.isEmpty();

// Add imports
for (Map<String, String> oneImport : additionalImports) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,29 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}
}
{{! end feature: getter and setter }}
{{/vars}}
{{#parentVars}}

{{! begin feature: fluent setter methods for inherited properties }}
public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) {
super.{{setter}}({{name}});
return this;
}
{{#isArray}}

public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) {
super.add{{nameInCamelCase}}Item({{name}}Item);
return this;
}
{{/isArray}}
{{#isMap}}

public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) {
super.put{{nameInCamelCase}}Item({{name}}Item);
return this;
}
{{/isMap}}
{{! end feature: fluent setter methods for inherited properties }}
{{/parentVars}}

@Override
public boolean equals(Object o) {
Expand All @@ -178,23 +201,27 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}
{{/-last}}{{/vars}}{{#parent}} &&
super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}}
return true;{{/hasVars}}
}{{#vendorExtensions.x-jackson-optional-nullable-helpers}}
}
{{#vendorExtensions.x-jackson-optional-nullable-helpers}}

private static <T> boolean equalsNullable(JsonNullable<T> a, JsonNullable<T> b) {
return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
}{{/vendorExtensions.x-jackson-optional-nullable-helpers}}
}
{{/vendorExtensions.x-jackson-optional-nullable-helpers}}

@Override
public int hashCode() {
return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}});
}{{#vendorExtensions.x-jackson-optional-nullable-helpers}}
}
{{#vendorExtensions.x-jackson-optional-nullable-helpers}}

private static <T> int hashCodeNullable(JsonNullable<T> a) {
if (a == null) {
return 1;
}
return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
}{{/vendorExtensions.x-jackson-optional-nullable-helpers}}
}
{{/vendorExtensions.x-jackson-optional-nullable-helpers}}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,49 @@ public void oneOf_5381() throws IOException {
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue");
}

@Test
public void oneOf_allOf() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');
OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/oneof_polymorphism_and_inheritance.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.setUseOneOfInterfaces(true);

ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
codegen.setHateoas(true);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
// generator.setGeneratorPropertyDefault(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false");

codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");

generator.opts(input).generate();

assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public class Foo extends Entity implements FooRefOrValue");
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public class FooRef extends EntityRef implements FooRefOrValue");
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue");
// previous bugs
assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java"), "atTypesuper.hashCode");
assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java"), "private String atBaseType");
// imports for inherited properties
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal");
}

@Test
public void testTypeMappings() {
final SpringCodegen codegen = new SpringCodegen();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,15 @@ components:
- $ref: '#/components/schemas/Entity'
FooRef:
type: object
discriminator:
propertyName: '@type'
properties:
foorefPropA:
type: string
allOf:
- $ref: '#/components/schemas/EntityRef'
BarRef:
type: object
allOf:
- $ref: '#/components/schemas/EntityRef'
Bar_Create:
type: object
properties:
Expand All @@ -149,6 +151,32 @@ components:
$ref: '#/components/schemas/FooRefOrValue'
allOf:
- $ref: '#/components/schemas/Entity'
BarRefOrValue:
type: object
oneOf:
- $ref: "#/components/schemas/Bar"
- $ref: "#/components/schemas/BarRef"
Pizza:
type: object
properties:
pizzaSize:
type: number
allOf:
- $ref: '#/components/schemas/Entity'
Pasta:
type: object
properties:
vendor:
type: string
allOf:
- $ref: '#/components/schemas/Entity'
PizzaSpeziale:
type: object
properties:
toppings:
type: string
allOf:
- $ref: '#/components/schemas/Pizza'

requestBodies:
Foo:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ public void setKind(KindEnum kind) {
this.kind = kind;
}

public BigCat declawed(Boolean declawed) {
super.setDeclawed(declawed);
return this;
}

public BigCat className(String className) {
super.setClassName(className);
return this;
}

public BigCat color(String color) {
super.setColor(color);
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ public void setDeclawed(Boolean declawed) {
this.declawed = declawed;
}

public Cat className(String className) {
super.setClassName(className);
return this;
}

public Cat color(String color) {
super.setColor(color);
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public void setBreed(String breed) {
this.breed = breed;
}

public Dog className(String className) {
super.setClassName(className);
return this;
}

public Dog color(String color) {
super.setColor(color);
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Loading

0 comments on commit f195a83

Please sign in to comment.